// 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)); 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) { 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) { 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 += " " + 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 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; }