// Create arbitrary items #include "Core.h" #include "Console.h" #include "Export.h" #include "PluginManager.h" #include "MiscUtils.h" #include "modules/Maps.h" #include "modules/MapCache.h" #include "modules/Gui.h" #include "modules/Items.h" #include "modules/Materials.h" #include "modules/World.h" #include "DataDefs.h" #include "df/game_type.h" #include "df/world.h" #include "df/ui.h" #include "df/unit.h" #include "df/historical_entity.h" #include "df/world_site.h" #include "df/item.h" #include "df/creature_raw.h" #include "df/caste_raw.h" #include "df/reaction_reagent.h" #include "df/reaction_product_itemst.h" #include "df/tool_uses.h" #include "df/item_plant_growthst.h" #include "df/plant_growth.h" #include "df/plant_growth_print.h" using std::string; using std::vector; using namespace DFHack; using namespace df::enums; DFHACK_PLUGIN("createitem"); REQUIRE_GLOBAL(cursor); REQUIRE_GLOBAL(world); REQUIRE_GLOBAL(ui); REQUIRE_GLOBAL(gametype); REQUIRE_GLOBAL(cur_year_tick); int dest_container = -1, dest_building = -1; command_result df_createitem (color_ostream &out, vector & parameters); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { commands.push_back(PluginCommand("createitem", "Create arbitrary items.", df_createitem, false, "Syntax: createitem [count]\n" " - Item token for what you wish to create, as specified in custom\n" " reactions. If the item has no subtype, omit the :NONE.\n" " - The material you want the item to be made of, as specified\n" " in custom reactions. For REMAINS, FISH, FISH_RAW, VERMIN,\n" " PET, and EGG, replace this with a creature ID and caste.\n" " For PLANT_GROWTH, replace this with a plant ID and growth ID.\n" " [count] - How many of the item you wish to create.\n" "\n" "To obtain the item and material of an existing item, run \n" "'createitem inspect' with that item selected in-game.\n" "\n" "To use this command, you must select which unit will create the items.\n" "By default, items created will be placed at that unit's feet.\n" "To change this, run 'createitem '.\n" "Valid destinations:\n" "* floor - Place items on floor beneath maker's feet.\n" "* item - Place items inside selected container.\n" "* building - Place items inside selected building.\n" )); return CR_OK; } DFhackCExport command_result plugin_shutdown ( color_ostream &out ) { return CR_OK; } bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_item = false, bool move_to_cursor = false, int32_t growth_print = -1) { vector out_products; vector out_items; vector in_reag; vector in_items; bool is_gloves = (prod->item_type == item_type::GLOVES); bool is_shoes = (prod->item_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); prod->produce(unit, &out_products, &out_items, &in_reag, &in_items, 1, job_skill::NONE, 0, df::historical_entity::find(unit->civ_id), (World::isFortressMode()) ? df::world_site::find(ui->site_id) : NULL, NULL); if (!out_items.size()) return false; // if we asked to make shoes and we got twice as many as we asked, then we're okay // otherwise, make a second set because shoes are normally made in pairs if (is_shoes && out_items.size() == size_t(prod->count * 2)) is_shoes = false; MapExtras::MapCache mc; for (size_t i = 0; i < out_items.size(); i++) { if (container) { out_items[i]->flags.bits.removed = 1; if (!Items::moveToContainer(mc, 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(mc, out_items[i], (df::building_actual *)building, 0)) out_items[i]->moveToGround(building->centerx, building->centery, building->z); } else if (move_to_cursor) out_items[i]->moveToGround(cursor->x, cursor->y, cursor->z); else out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z); // Special logic for making gloves if (is_gloves) { // if the reaction creates gloves without handedness, then create 2 sets (left and right) if (out_items[i]->getGloveHandedness() > 0) is_gloves = false; else out_items[i]->setGloveHandedness(second_item ? 2 : 1); } // Special logic for making plant growths auto growth = virtual_cast(out_items[i]); if (growth) growth->growth_print = growth_print; } if ((is_gloves || is_shoes) && !second_item) return makeItem(prod, unit, true, move_to_cursor, growth_print); return true; } command_result df_createitem (color_ostream &out, vector & parameters) { string item_str, material_str; df::item_type item_type = item_type::NONE; int16_t item_subtype = -1; int16_t mat_type = -1; int32_t mat_index = -1; int32_t growth_print = -1; int count = 1; bool move_to_cursor = false; if (parameters.size() == 1) { if (parameters[0] == "inspect") { CoreSuspender suspend; df::item *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; df::item *item = Gui::getSelectedItem(out); if (!item) { out.printerr("You must select a container!\n"); return CR_FAILURE; } switch (item->getType()) { case item_type::FLASK: case item_type::BARREL: case item_type::BUCKET: case item_type::ANIMALTRAP: case item_type::BOX: case item_type::BIN: case item_type::BACKPACK: case item_type::QUIVER: break; case item_type::TOOL: if (item->hasToolUse(tool_uses::LIQUID_CONTAINER)) break; if (item->hasToolUse(tool_uses::FOOD_STORAGE)) break; if (item->hasToolUse(tool_uses::SMALL_OBJECT_STORAGE)) break; if (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; df::building *building = Gui::getSelectedBuilding(out); if (!building) { out.printerr("You must select a building!\n"); return CR_FAILURE; } switch (building->getType()) { case building_type::Coffin: case building_type::Furnace: case building_type::TradeDepot: case building_type::Shop: case building_type::Box: case building_type::Weaponrack: case building_type::Armorstand: case building_type::Workshop: case building_type::Cabinet: case building_type::SiegeEngine: case building_type::Trap: case building_type::AnimalTrap: case building_type::Cage: case building_type::Wagon: case building_type::NestBox: case building_type::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) { 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) { case item_type::INSTRUMENT: case item_type::TOY: case item_type::WEAPON: case item_type::ARMOR: case item_type::SHOES: case item_type::SHIELD: case item_type::HELM: case item_type::GLOVES: case item_type::AMMO: case item_type::PANTS: case item_type::SIEGEAMMO: case item_type::TRAPCOMP: case item_type::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; break; case item_type::REMAINS: case item_type::FISH: case item_type::FISH_RAW: case item_type::VERMIN: case item_type::PET: case item_type::EGG: 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 = ""; df::creature_raw *creature = world->raws.creatures.all[i]; if (creature->creature_id == tokens[0]) { for (size_t j = 0; j < creature->caste.size(); j++) { df::caste_raw *caste = creature->caste[j]; castes += " " + creature->caste[j]->caste_id; if (creature->caste[j]->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 CR_FAILURE; } } } if (mat_type == -1) { out.printerr("Unrecognized creature ID!\n"); return CR_FAILURE; } break; case item_type::PLANT_GROWTH: 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 CR_FAILURE; } for (size_t i = 0; i < world->raws.plants.all.size(); i++) { string growths = ""; df::plant_raw *plant = world->raws.plants.all[i]; if (plant->id != tokens[0]) continue; for (size_t j = 0; j < plant->growths.size(); j++) { df::plant_growth *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; // Try and find a growth print matching the current time // (in practice, only tree leaves use this for autumn color changes) for (size_t k = 0; k < growth->prints.size(); k++) { df::plant_growth_print *print = growth->prints[k]; if (print->timing_start <= *cur_year_tick && *cur_year_tick <= print->timing_end) { growth_print = k; break; } } // If we didn't find one, then pick the first one (if it exists) if (growth_print == -1 && growth->prints.size() > 0) growth_print = 0; 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 CR_FAILURE; } } if (mat_type == -1) { out.printerr("Unrecognized plant ID!\n"); return CR_FAILURE; } break; case item_type::CORPSE: case item_type::CORPSEPIECE: case item_type::FOOD: out.printerr("Cannot create that type of item!\n"); return CR_FAILURE; break; } CoreSuspender suspend; df::unit *unit = Gui::getSelectedUnit(out, true); if (!unit) { if (*gametype == game_type::ADVENTURE_ARENA || World::isAdventureMode()) { // Use the adventurer unit unit = world->units.active[0]; } else if (!world->units.active.empty() && cursor->x >= 0) { // Use the first possible unit and the cursor position unit = world->units.active[0]; move_to_cursor = true; } else { out.printerr("No unit selected!\n"); return CR_FAILURE; } } if (!Maps::IsValid()) { out.printerr("Map is not available.\n"); return CR_FAILURE; } df::map_block *block = Maps::getTileBlock(unit->pos.x, unit->pos.y, unit->pos.z); if (block == NULL) { out.printerr("Unit does not reside in a valid map block, somehow?\n"); return CR_FAILURE; } df::reaction_product_itemst *prod = df::allocate(); prod->item_type = item_type; prod->item_subtype = item_subtype; prod->mat_type = mat_type; prod->mat_index = mat_index; prod->probability = 100; prod->count = count; switch (item_type) { case item_type::BAR: case item_type::POWDER_MISC: case item_type::LIQUID_MISC: case item_type::DRINK: prod->product_dimension = 150; break; case item_type::THREAD: prod->product_dimension = 15000; break; case item_type::CLOTH: prod->product_dimension = 10000; break; default: prod->product_dimension = 1; break; } 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"); } bool result = makeItem(prod, unit, false, move_to_cursor, growth_print); delete prod; if (!result) { out.printerr("Failed to create item!\n"); return CR_FAILURE; } return CR_OK; }