// Create arbitrary items
#include "Console.h"
#include "DataDefs.h"
#include "Export.h"
#include "MiscUtils.h"
#include "PluginManager.h"
#include "modules/Gui.h"
#include "modules/Items.h"
#include "modules/Maps.h"
#include "modules/Materials.h"
#include "modules/Units.h"
#include "modules/World.h"
#include "df/building.h"
#include "df/caste_raw.h"
#include "df/creature_raw.h"
#include "df/game_type.h"
#include "df/item.h"
#include "df/plant_growth.h"
#include "df/plant_raw.h"
#include "df/tool_uses.h"
#include "df/unit.h"
#include "df/world.h"
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("createitem");
REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(gametype);
REQUIRE_GLOBAL(cur_year_tick);
int dest_container = -1, dest_building = -1;
command_result df_createitem(color_ostream &out, vector ¶meters);
DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) {
commands.push_back(
PluginCommand("createitem",
"Create arbitrary items.",
df_createitem));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
return CR_OK;
}
bool makeItem(df::unit *unit, df::item_type type, int16_t subtype, int16_t mat_type, int32_t mat_index,
bool move_to_cursor = false, bool second_item = false)
{ // Special logic for making Gloves and Shoes in pairs
bool is_gloves = (type == item_type::GLOVES);
bool is_shoes = (type == item_type::SHOES);
df::item *container = NULL;
df::building *building = NULL;
if (dest_container != -1)
container = df::item::find(dest_container);
if (dest_building != -1)
building = df::building::find(dest_building);
bool on_floor = (container == NULL) && (building == NULL) && !move_to_cursor;
vector<:item> out_items;
if (!Items::createItem(out_items, unit, type, subtype, mat_type, mat_index, !on_floor))
return false;
for (size_t i = 0; i < out_items.size(); i++) {
if (container)
{
out_items[i]->flags.bits.removed = 1;
if (!Items::moveToContainer(out_items[i], container))
out_items[i]->moveToGround(container->pos.x, container->pos.y, container->pos.z);
}
else if (building)
{
out_items[i]->flags.bits.removed = 1;
if (!Items::moveToBuilding(out_items[i], (df::building_actual *)building, df::building_item_role_type::TEMP))
out_items[i]->moveToGround(building->centerx, building->centery, building->z);
}
else if (move_to_cursor)
{
auto pos = Gui::getCursorPos();
out_items[i]->moveToGround(pos.x, pos.y, pos.z);
}
// else createItem() already put it on the floor at the unit's feet, so we're good
// Special logic for creating proper gloves in pairs
if (is_gloves)
{ // If the gloves have non-zero handedness, then disable special pair-making logic
// ("reaction-gloves" tweak is active, or Toady fixed glove-making reactions)
// Otherwise, set them to be either Left or Right-handed
if (out_items[i]->getGloveHandedness() > 0)
is_gloves = false;
else
out_items[i]->setGloveHandedness(second_item ? 2 : 1);
}
}
// If we asked to make a Shoe and we got more than one, then disable special pair-making logic
if (is_shoes && out_items.size() > 1)
is_shoes = false;
// If we asked for gloves/shoes and only got one (and we're making the first one), make another
if ((is_gloves || is_shoes) && !second_item)
return makeItem(unit, type, subtype, mat_type, mat_index, move_to_cursor, true);
return true;
}
static inline bool select_caste_mat(color_ostream &out, vector &tokens,
int16_t &mat_type, int32_t &mat_index, const string &material_str)
{
split_string(&tokens, material_str, ":");
if (tokens.size() == 1)
{ // Default to empty caste to display a list of valid castes later
tokens.push_back("");
}
else if (tokens.size() != 2) {
out.printerr("You must specify a creature ID and caste for this item type!\n");
return CR_FAILURE;
}
for (size_t i = 0; i < world->raws.creatures.all.size(); i++)
{
string castes = "";
auto creature = world->raws.creatures.all[i];
if (creature->creature_id == tokens[0])
{
for (size_t j = 0; j < creature->caste.size(); j++)
{
auto caste = creature->caste[j];
castes += " " + caste->caste_id;
if (caste->caste_id == tokens[1])
{
mat_type = i;
mat_index = j;
break;
}
}
if (mat_type == -1) {
if (tokens[1].empty())
out.printerr("You must also specify a caste.\n");
else
out.printerr("The creature you specified has no such caste!\n");
out.printerr("Valid castes:%s\n", castes.c_str());
return false;
}
}
}
if (mat_type == -1) {
out.printerr("Unrecognized creature ID!\n");
return false;
}
return true;
}
static inline bool select_plant_growth(color_ostream &out, vector &tokens, df::item_type &item_type,
int16_t &item_subtype, int16_t &mat_type, int32_t &mat_index, const string &material_str)
{
split_string(&tokens, material_str, ":");
if (tokens.size() == 1)
{ // Default to empty to display a list of valid growths later
tokens.push_back("");
}
else if (tokens.size() == 3 && tokens[0] == "PLANT")
tokens.erase(tokens.begin());
else if (tokens.size() != 2) {
out.printerr("You must specify a plant and growth ID for this item type!\n");
return false;
}
for (size_t i = 0; i < world->raws.plants.all.size(); i++)
{
string growths = "";
auto plant = world->raws.plants.all[i];
if (plant->id != tokens[0])
continue;
for (size_t j = 0; j < plant->growths.size(); j++)
{
auto growth = plant->growths[j];
growths += " " + growth->id;
if (growth->id != tokens[1])
continue;
// Plant growths specify the actual item type/subtype
// so that certain growths can drop as SEEDS items
item_type = growth->item_type;
item_subtype = growth->item_subtype;
mat_type = growth->mat_type;
mat_index = growth->mat_index;
break;
}
if (mat_type == -1) {
if (tokens[1].empty())
out.printerr("You must also specify a growth ID.\n");
else
out.printerr("The plant you specified has no such growth!\n");
out.printerr("Valid growths:%s\n", growths.c_str());
return false;
}
}
if (mat_type == -1) {
out.printerr("Unrecognized plant ID!\n");
return false;
}
return true;
}
command_result df_createitem (color_ostream &out, vector ¶meters) {
string item_str, material_str;
auto item_type = item_type::NONE;
int16_t item_subtype = -1;
int16_t mat_type = -1;
int32_t mat_index = -1;
int count = 1;
bool move_to_cursor = false;
if (parameters.size() == 1) {
if (parameters[0] == "inspect") {
auto item = Gui::getSelectedItem(out);
if (!item)
return CR_FAILURE;
ItemTypeInfo iinfo(item->getType(), item->getSubtype());
MaterialInfo minfo(item->getMaterial(), item->getMaterialIndex());
out.print("%s %s\n", iinfo.getToken().c_str(), minfo.getToken().c_str());
return CR_OK;
}
else if (parameters[0] == "floor") {
dest_container = -1;
dest_building = -1;
out.print("Items created will be placed on the floor.\n");
return CR_OK;
}
else if (parameters[0] == "item") {
dest_building = -1;
auto item = Gui::getSelectedItem(out);
if (!item) {
out.printerr("You must select a container!\n");
return CR_FAILURE;
}
switch (item->getType())
{ using namespace df::enums::item_type;
case FLASK:
case BARREL:
case BUCKET:
case ANIMALTRAP:
case BOX:
case BAG:
case BIN:
case BACKPACK:
case QUIVER:
break;
case TOOL:
if (item->hasToolUse(tool_uses::LIQUID_CONTAINER) ||
item->hasToolUse(tool_uses::FOOD_STORAGE) ||
item->hasToolUse(tool_uses::SMALL_OBJECT_STORAGE) ||
item->hasToolUse(tool_uses::TRACK_CART)
)
break;
default:
out.printerr("The selected item cannot be used for item storage!\n");
return CR_FAILURE;
}
dest_container = item->id;
string name;
item->getItemDescription(&name, 0);
out.print("Items created will be placed inside %s.\n", name.c_str());
return CR_OK;
}
else if (parameters[0] == "building") {
dest_container = -1;
auto building = Gui::getSelectedBuilding(out);
if (!building) {
out.printerr("You must select a building!\n");
return CR_FAILURE;
}
switch (building->getType())
{ using namespace df::enums::building_type;
case Coffin:
case Furnace:
case TradeDepot:
case Shop:
case Box:
case Weaponrack:
case Armorstand:
case Workshop:
case Cabinet:
case SiegeEngine:
case Trap:
case AnimalTrap:
case Cage:
case Wagon:
case NestBox:
case Hive:
break;
default:
out.printerr("The selected building cannot be used for item storage!\n");
return CR_FAILURE;
}
if (building->getBuildStage() != building->getMaxBuildStage()) {
out.printerr("The selected building has not yet been fully constructed!\n");
return CR_FAILURE;
}
dest_building = building->id;
string name;
building->getName(&name);
out.print("Items created will be placed inside %s.\n", name.c_str());
return CR_OK;
}
else
return CR_WRONG_USAGE;
}
if ((parameters.size() < 2) || (parameters.size() > 3))
return CR_WRONG_USAGE;
item_str = parameters[0];
material_str = parameters[1];
if (parameters.size() == 3) {
std::stringstream ss(parameters[2]);
ss >> count;
if (count < 1) {
out.printerr("You cannot produce less than one item!\n");
return CR_FAILURE;
}
}
ItemTypeInfo item;
MaterialInfo material;
vector tokens;
if (item.find(item_str)) {
item_type = item.type;
item_subtype = item.subtype;
}
if (item_type == item_type::NONE) {
out.printerr("You must specify a valid item type to create!\n");
return CR_FAILURE;
}
switch (item.type)
{ using namespace df::enums::item_type;
case REMAINS:
case FISH:
case FISH_RAW:
case VERMIN:
case PET:
case EGG:
if (!select_caste_mat(out, tokens, mat_type, mat_index, material_str))
return CR_FAILURE;
break;
case PLANT_GROWTH:
if (!select_plant_growth(out, tokens, item_type, item_subtype,
mat_type, mat_index, material_str)
)
return CR_FAILURE;
break;
case CORPSE:
case CORPSEPIECE:
case FOOD:
out.printerr("Cannot create that type of item!\n");
return CR_FAILURE;
case INSTRUMENT:
case TOY:
case WEAPON:
case ARMOR:
case SHOES:
case SHIELD:
case HELM:
case GLOVES:
case AMMO:
case PANTS:
case SIEGEAMMO:
case TRAPCOMP:
case TOOL:
if (item_subtype == -1) {
out.printerr("You must specify a subtype!\n");
return CR_FAILURE;
}
default:
if (!material.find(material_str)) {
out.printerr("Unrecognized material!\n");
return CR_FAILURE;
}
mat_type = material.type;
mat_index = material.index;
}
if (!Maps::IsValid()) {
out.printerr("Map is not available.\n");
return CR_FAILURE;
}
auto unit = Gui::getSelectedUnit(out, true);
if (!unit) {
auto pos = Gui::getCursorPos();
if (*gametype == game_type::ADVENTURE_ARENA || World::isAdventureMode())
{ // Use the adventurer unit
unit = World::getAdventurer();
move_to_cursor = pos.isValid();
}
else if (pos.isValid())
{ // Use the first possible citizen if possible, otherwise the first unit
for (auto u : Units::citizensRange(world->units.active)) {
unit = u;
break;
}
if (!unit)
unit = vector_get(world->units.active, 0);
move_to_cursor = true;
}
if (!unit) {
out.printerr("No unit selected!\n");
return CR_FAILURE;
}
}
auto block = Maps::getTileBlock(unit->pos);
if (!block) {
out.printerr("Unit does not reside in a valid map block, somehow?\n");
return CR_FAILURE;
}
if (dest_container != -1 && !df::item::find(dest_container))
{
dest_container = -1;
out.printerr("Previously selected container no longer exists - item will be placed on the floor.\n");
}
if (dest_building != -1 && !df::building::find(dest_building))
{
dest_building = -1;
out.printerr("Previously selected building no longer exists - item will be placed on the floor.\n");
}
for (int i = 0; i < count; i++) {
if (!makeItem(unit, item_type, item_subtype, mat_type, mat_index, move_to_cursor, false))
{
out.printerr("Failed to create item!\n");
return CR_FAILURE;
}
}
return CR_OK;
}