diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index b4bc516d0..4acf19c35 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -125,23 +125,25 @@ namespace DFHack template T *ifnull(T *a, T *b) { return a ? a : b; } + // Enums template inline T next_enum_item_(T v) { v = T(int(v) + 1); return isvalid(v) ? v : start; } - struct bitfield_item_info { - const char *name; - int size; - }; - template struct enum_list_attr { int size; const T *items; }; + // Bitfields + struct bitfield_item_info { + const char *name; + int size; + }; + DFHACK_EXPORT std::string bitfieldToString(const void *p, int size, const bitfield_item_info *items); DFHACK_EXPORT int findBitfieldField(const std::string &name, int size, const bitfield_item_info *items); @@ -156,6 +158,21 @@ namespace DFHack } } +template +int linear_index(const DFHack::enum_list_attr &lst, T val) { + for (int i = 0; i < lst.size; i++) + if (lst.items[i] == val) + return i; + return -1; +} + +inline int linear_index(const DFHack::enum_list_attr &lst, const std::string &val) { + for (int i = 0; i < lst.size; i++) + if (lst.items[i] == val) + return i; + return -1; +} + namespace df { using DFHack::virtual_ptr; diff --git a/library/include/MiscUtils.h b/library/include/MiscUtils.h index c744f587c..b3d70f6b9 100644 --- a/library/include/MiscUtils.h +++ b/library/include/MiscUtils.h @@ -257,6 +257,11 @@ DFHACK_EXPORT bool split_string(std::vector *out, DFHACK_EXPORT std::string toUpper(const std::string &str); DFHACK_EXPORT std::string toLower(const std::string &str); +inline bool bits_match(unsigned required, unsigned ok, unsigned mask) +{ + return (required & mask) == (required & mask & ok); +} + /** * Returns the amount of milliseconds elapsed since the UNIX epoch. * Works on both windows and linux. diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 4deba6cdb..1e889fca1 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -32,6 +32,15 @@ distribution. #include "Virtual.h" #include "modules/Materials.h" #include "MemAccess.h" + +#include "DataDefs.h" +#include "df/item_type.h" + +namespace df +{ + struct itemdef; +} + /** * \defgroup grp_items Items module and its types * @ingroup grp_modules @@ -39,6 +48,42 @@ distribution. namespace DFHack { + struct DFHACK_EXPORT ItemTypeInfo { + df::item_type type; + int16_t subtype; + + df::itemdef *custom; + + public: + ItemTypeInfo(df::item_type type_ = df::enums::item_type::NONE, int16_t subtype_ = -1) { + decode(type_, subtype_); + } + template ItemTypeInfo(T *ptr) { decode(ptr); } + + bool isValid() const { + return (type != df::enums::item_type::NONE) && (subtype == -1 || custom); + } + + bool decode(df::item_type type_, int16_t subtype_ = -1); + bool decode(df::item *ptr); + + template bool decode(T *ptr) { + return ptr ? decode(ptr->item_type, ptr->item_subtype) : decode(df::enums::item_type::NONE); + } + + std::string toString(); + + bool find(const std::string &token); + + bool matches(const df::job_item &item, MaterialInfo *mat = NULL); + }; + + inline bool operator== (const ItemTypeInfo &a, const ItemTypeInfo &b) { + return a.type == b.type && a.subtype == b.subtype; + } + inline bool operator!= (const ItemTypeInfo &a, const ItemTypeInfo &b) { + return a.type != b.type || a.subtype != b.subtype; + } class Context; class DFContextShared; diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index ddf6c4f29..fb2ae6655 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -42,8 +42,338 @@ using namespace std; #include "ModuleFactory.h" #include "Core.h" #include "Virtual.h" +#include "MiscUtils.h" + +#include "df/world.h" +#include "df/item.h" +#include "df/tool_uses.h" +#include "df/itemdef_weaponst.h" +#include "df/itemdef_trapcompst.h" +#include "df/itemdef_toyst.h" +#include "df/itemdef_toolst.h" +#include "df/itemdef_instrumentst.h" +#include "df/itemdef_armorst.h" +#include "df/itemdef_ammost.h" +#include "df/itemdef_siegeammost.h" +#include "df/itemdef_glovesst.h" +#include "df/itemdef_shoesst.h" +#include "df/itemdef_shieldst.h" +#include "df/itemdef_helmst.h" +#include "df/itemdef_pantsst.h" +#include "df/itemdef_foodst.h" +#include "df/trapcomp_flags.h" +#include "df/job_item.h" using namespace DFHack; +using namespace df::enums; + +#define ITEMDEF_VECTORS \ + ITEM(WEAPON, weapons) \ + ITEM(TRAPCOMP, trapcomps) \ + ITEM(TOY, toys) \ + ITEM(TOOL, tools) \ + ITEM(INSTRUMENT, instruments) \ + ITEM(ARMOR, armor) \ + ITEM(AMMO, ammo) \ + ITEM(SIEGEAMMO, siege_ammo) \ + ITEM(GLOVES, gloves) \ + ITEM(SHOES, shoes) \ + ITEM(SHIELD, shields) \ + ITEM(HELM, helms) \ + ITEM(PANTS, pants) \ + ITEM(FOOD, food) + +bool ItemTypeInfo::decode(df::item_type type_, int16_t subtype_) +{ + using namespace df::enums::item_type; + + type = type_; + subtype = subtype_; + custom = NULL; + + df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; + + switch (type_) { + case NONE: + return false; + +#define ITEM(type,vec) \ + case type: \ + custom = vector_get(defs.vec, subtype); \ + break; +ITEMDEF_VECTORS +#undef ITEM + + default: + break; + } + + return isValid(); +} + +bool ItemTypeInfo::decode(df::item *ptr) +{ + if (!ptr) + return decode(item_type::NONE); + else + return decode(ptr->getType(), ptr->getSubtype()); +} + +std::string ItemTypeInfo::toString() +{ + std::string rv = ENUM_KEY_STR(item_type, type); + if (custom) + rv += ":" + custom->id; + else if (subtype != -1) + rv += stl_sprintf(":%d", subtype); + return rv; +} + +bool ItemTypeInfo::find(const std::string &token) +{ + using namespace df::enums::item_type; + + std::vector items; + split_string(&items, token, ":"); + + type = NONE; + subtype = -1; + custom = NULL; + + if (items.size() < 1 || items.size() > 2) + return false; + + if (items[0] == "NONE") + return true; + + FOR_ENUM_ITEMS(item_type, i) + { + const char *key = ENUM_ATTR(item_type, key, i); + if (key && key == items[0]) + { + type = i; + break; + } + } + + if (type == NONE) + return false; + if (items.size() == 1) + return true; + + df::world_raws::T_itemdefs &defs = df::global::world->raws.itemdefs; + + switch (type) { +#define ITEM(type,vec) \ + case type: \ + for (int i = 0; i < defs.vec.size(); i++) { \ + if (defs.vec[i]->id == items[1]) { \ + subtype = i; custom = defs.vec[i]; return true; \ + } \ + } \ + break; +ITEMDEF_VECTORS +#undef ITEM + + default: + break; + } + + return (subtype >= 0); +} + +bool ItemTypeInfo::matches(const df::job_item &item, MaterialInfo *mat) +{ + using namespace df::enums::item_type; + + if (!isValid()) + return mat ? mat->matches(item) : true; + + df::job_item_flags1 ok1, mask1, item_ok1, item_mask1; + df::job_item_flags2 ok2, mask2, item_ok2, item_mask2; + df::job_item_flags3 ok3, mask3, item_ok3, item_mask3; + + ok1.whole = mask1.whole = item_ok1.whole = item_mask1.whole = 0; + ok2.whole = mask2.whole = item_ok2.whole = item_mask2.whole = 0; + ok3.whole = mask3.whole = item_ok3.whole = item_mask3.whole = 0; + + if (mat) { + mat->getMatchBits(ok1, mask1); + mat->getMatchBits(ok2, mask2); + mat->getMatchBits(ok3, mask3); + } + +#define OK(id,name) item_ok##id.bits.name = true +#define RQ(id,name) item_mask##id.bits.name = true + + // Set up the flag mask + + RQ(1,millable); RQ(1,sharpenable); RQ(1,distillable); RQ(1,processable); RQ(1,bag); + RQ(1,extract_bearing_plant); RQ(1,extract_bearing_fish); RQ(1,extract_bearing_vermin); + RQ(1,processable_to_vial); RQ(1,processable_to_bag); RQ(1,processable_to_barrel); + RQ(1,solid); RQ(1,tameable_vermin); RQ(1,sand_bearing); RQ(1,milk); RQ(1,milkable); + RQ(1,not_bin); RQ(1,lye_bearing); + + RQ(2,dye); RQ(2,dyeable); RQ(2,dyed); RQ(2,glass_making); RQ(2,screw); + RQ(2,building_material); RQ(2,fire_safe); RQ(2,magma_safe); RQ(2,non_economic); + RQ(2,totemable); RQ(2,plaster_containing); RQ(2,body_part); RQ(2,lye_milk_free); + RQ(2,blunt); RQ(2,unengraved); RQ(2,hair_wool); + + RQ(3,any_raw_material); RQ(3,non_pressed); RQ(3,food_storage); + + // Compute the ok mask + + OK(1,solid); + OK(1,not_bin); + + // TODO: furniture, ammo, finished good, craft + + switch (type) { + case PLANT: + OK(1,millable); OK(1,processable); + OK(1,distillable); + OK(1,extract_bearing_plant); + OK(1,processable_to_vial); + OK(1,processable_to_bag); + OK(1,processable_to_barrel); + break; + + case BOULDER: + OK(1,sharpenable); + OK(2,non_economic); + case BAR: + OK(3,any_raw_material); + case BLOCKS: + case WOOD: + OK(2,building_material); + OK(2,fire_safe); OK(2,magma_safe); + break; + + case VERMIN: + OK(1,extract_bearing_fish); + OK(1,extract_bearing_vermin); + OK(1,tameable_vermin); + OK(1,milkable); + break; + + case DRINK: + item_ok1.bits.solid = false; + break; + + case ROUGH: + OK(2,glass_making); + break; + + case ANIMALTRAP: + case CAGE: + OK(1,milk); + OK(1,milkable); + break; + + case BUCKET: + case FLASK: + OK(1,milk); + break; + + case TOOL: + OK(1,lye_bearing); + OK(1,milk); + OK(2,lye_milk_free); + OK(2,blunt); + + if (VIRTUAL_CAST_VAR(def, df::itemdef_toolst, custom)) { + df::enum_field key(tool_uses::FOOD_STORAGE); + if (linear_index(def->tool_use, key) >= 0) + OK(3,food_storage); + } else { + OK(3,food_storage); + } + break; + + case BARREL: + OK(1,lye_bearing); + OK(1,milk); + OK(2,lye_milk_free); + OK(3,food_storage); + break; + + case BOX: + OK(1,bag); OK(1,sand_bearing); OK(1,milk); + OK(2,dye); OK(2,plaster_containing); + break; + + case BIN: + item_ok1.bits.not_bin = false; + break; + + case LIQUID_MISC: + item_ok1.bits.solid = false; + OK(1,milk); + break; + + case POWDER_MISC: + OK(2,dye); + case GLOB: + OK(3,any_raw_material); + OK(3,non_pressed); + break; + + case THREAD: + case CLOTH: + OK(2,dyeable); OK(2,dyed); + break; + + case WEAPON: + case AMMO: + case ROCK: + OK(2,blunt); + break; + + case TRAPCOMP: + if (VIRTUAL_CAST_VAR(def, df::itemdef_trapcompst, custom)) { + if (def->flags.is_set(trapcomp_flags::IS_SCREW)) + OK(2,screw); + } else { + OK(2,screw); + } + OK(2,blunt); + break; + + case CORPSE: + case CORPSEPIECE: + OK(2,totemable); + OK(2,body_part); + OK(2,hair_wool); + break; + + case SLAB: + OK(2,unengraved); + break; + + case ANVIL: + OK(2,fire_safe); OK(2,magma_safe); + break; + + default: + break; + } + + if ((item_ok1.whole & ~item_mask1.whole) || + (item_ok2.whole & ~item_mask2.whole) || + (item_ok3.whole & ~item_mask3.whole)) + Core::getInstance().con.printerr("ItemTypeInfo.matches inconsistent\n"); + +#undef OK +#undef RQ + + return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && + bits_match(item.flags2.whole, ok2.whole, mask2.whole) && + bits_match(item.flags3.whole, ok3.whole, mask3.whole) && + bits_match(item.flags1.whole, item_ok1.whole, item_mask1.whole) && + bits_match(item.flags2.whole, item_ok2.whole, item_mask2.whole) && + bits_match(item.flags3.whole, item_ok3.whole, item_mask3.whole); +} + Module* DFHack::createItems() { diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 3e3016e1f..4e8094547 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -37,6 +37,7 @@ using namespace std; #include "modules/Job.h" #include "modules/Materials.h" +#include "modules/Items.h" #include "DataDefs.h" #include @@ -140,9 +141,9 @@ bool DFHack::operator== (const df::job &a, const df::job &b) static void print_job_item_details(Core *c, df::job *job, unsigned idx, df::job_item *item) { - c->con << " Input Item " << (idx+1) << ": " << ENUM_KEY_STR(item_type,item->item_type); - if (item->item_subtype != -1) - c->con << " [" << item->item_subtype << "]"; + ItemTypeInfo info(item); + c->con << " Input Item " << (idx+1) << ": " << info.toString(); + if (item->quantity != 1) c->con << "; quantity=" << item->quantity; if (item->min_dimension >= 0) @@ -179,7 +180,12 @@ void DFHack::printJobDetails(Core *c, df::job *job) c->con << " (" << bitfieldToString(job->flags) << ")"; c->con << endl; + df::item_type itype = ENUM_ATTR(job_type, item, job->job_type); + MaterialInfo mat(job); + if (itype == item_type::FOOD) + mat.decode(-1); + if (mat.isValid() || job->material_category.whole) { c->con << " material: " << mat.toString(); @@ -189,8 +195,12 @@ void DFHack::printJobDetails(Core *c, df::job *job) } if (job->item_subtype >= 0 || job->item_category.whole) - c->con << " item: " << job->item_subtype + { + ItemTypeInfo iinfo(itype, job->item_subtype); + + c->con << " item: " << iinfo.toString() << " (" << bitfieldToString(job->item_category) << ")" << endl; + } if (job->hist_figure_id >= 0) c->con << " figure: " << job->hist_figure_id << endl; diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 32145cc90..716717230 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -161,6 +161,8 @@ bool MaterialInfo::find(const std::string &token) return true; if (findInorganic(items[0])) return true; + if (findPlant(items[0], "")) + return true; } else if (items.size() == 2) { @@ -177,6 +179,12 @@ bool MaterialInfo::findBuiltin(const std::string &token) { if (token.empty()) return decode(-1); + + if (token == "NONE") { + decode(-1); + return true; + } + df::world_raws &raws = df::global::world->raws; for (int i = 1; i < NUM_BUILTIN; i++) if (raws.mat_table.builtin[i]->id == token) @@ -188,6 +196,12 @@ bool MaterialInfo::findInorganic(const std::string &token) { if (token.empty()) return decode(-1); + + if (token == "NONE") { + decode(0, -1); + return true; + } + df::world_raws &raws = df::global::world->raws; for (unsigned i = 0; i < raws.inorganics.size(); i++) { @@ -200,7 +214,7 @@ bool MaterialInfo::findInorganic(const std::string &token) bool MaterialInfo::findPlant(const std::string &token, const std::string &subtoken) { - if (token.empty() || subtoken.empty()) + if (token.empty()) return decode(-1); df::world_raws &raws = df::global::world->raws; for (unsigned i = 0; i < raws.plants.all.size(); i++) @@ -209,6 +223,10 @@ bool MaterialInfo::findPlant(const std::string &token, const std::string &subtok if (p->id != token) continue; + // As a special exception, return the structural material with empty subtoken + if (subtoken.empty()) + return decode(p->material_defs.type_basic_mat, p->material_defs.idx_basic_mat); + for (unsigned j = 0; j < p->material.size(); j++) if (p->material[j]->id == subtoken) return decode(PLANT_BASE+j, i); @@ -317,7 +335,7 @@ bool MaterialInfo::matches(const df::job_material_category &cat) bool MaterialInfo::matches(const df::job_item &item) { - if (!isValid()) return false; + if (!isValid()) return true; df::job_item_flags1 ok1, mask1; getMatchBits(ok1, mask1); @@ -328,9 +346,9 @@ bool MaterialInfo::matches(const df::job_item &item) df::job_item_flags3 ok3, mask3; getMatchBits(ok3, mask3); - return ((item.flags1.whole & mask1.whole) == (item.flags1.whole & ok1.whole)) && - ((item.flags2.whole & mask2.whole) == (item.flags2.whole & ok2.whole)) && - ((item.flags3.whole & mask3.whole) == (item.flags3.whole & ok3.whole)); + return bits_match(item.flags1.whole, ok1.whole, mask1.whole) && + bits_match(item.flags2.whole, ok2.whole, mask2.whole) && + bits_match(item.flags3.whole, ok3.whole, mask3.whole); } void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &mask) @@ -349,7 +367,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &ma TEST(sharpenable, MAT_FLAG(IS_STONE)); TEST(distillable, structural && FLAG(plant, plant_raw_flags::DRINK)); TEST(processable, structural && FLAG(plant, plant_raw_flags::THREAD)); - TEST(bag, isAnyCloth()); + TEST(bag, isAnyCloth() || MAT_FLAG(LEATHER)); TEST(cookable, MAT_FLAG(EDIBLE_COOKED)); TEST(extract_bearing_plant, structural && FLAG(plant, plant_raw_flags::EXTRACT_STILL_VIAL)); TEST(extract_bearing_fish, false); @@ -363,7 +381,7 @@ void MaterialInfo::getMatchBits(df::job_item_flags1 &ok, df::job_item_flags1 &ma MAT_FLAG(LIQUID_MISC_CREATURE) || MAT_FLAG(LIQUID_MISC_OTHER))); TEST(tameable_vermin, false); - TEST(sharpenable, MAT_FLAG(IS_GLASS)); + TEST(sharpenable, MAT_FLAG(IS_STONE)); TEST(milk, linear_index(material->reaction_product.id, std::string("CHEESE_MAT")) >= 0); //04000000 - "milkable" - vtable[107],1,1 } diff --git a/library/xml b/library/xml index b93b61a98..85dfa3550 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b93b61a988020a4aaa0103cefcee0217c37ce7b0 +Subproject commit 85dfa3550fe204c8c75279335cae1457cf03e0ed diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp index a908b7607..29d86230c 100644 --- a/plugins/jobutils.cpp +++ b/plugins/jobutils.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -60,12 +61,14 @@ DFhackCExport command_result plugin_init (Core *c, std::vector & PluginCommand( "job", "General job query and manipulation.", job_cmd, false, - " job query\n" + " job [query]\n" " Print details of the current job.\n" " job list\n" " Print details of all jobs in the workshop.\n" " job item-material \n" " Replace the exact material id in the job item.\n" + " job item-type \n" + " Replace the exact item type id in the job item.\n" ) ); @@ -154,6 +157,13 @@ static command_result job_material_in_job(Core *c, MaterialInfo &new_mat) i, item_mat.toString().c_str()); return CR_FAILURE; } + + if (!new_mat.matches(*item)) + { + c->con.printerr("Job item %d requirements not satisfied by %s.\n", + i, new_mat.toString().c_str()); + return CR_FAILURE; + } } // Apply the substitution @@ -282,14 +292,25 @@ static command_result job_duplicate(Core * c, vector & parameters) /* Main job command implementation */ +static df::job_item *getJobItem(Core *c, df::job *job, std::string idx) +{ + if (!job) + return NULL; + + int v = atoi(idx.c_str()); + if (v < 1 || v > job->job_items.size()) { + c->con.printerr("Invalid item index.\n"); + return NULL; + } + + return job->job_items[v-1]; +} + static command_result job_cmd(Core * c, vector & parameters) { CoreSuspender suspend(c); - if (parameters.empty()) - return CR_WRONG_USAGE; - - std::string cmd = parameters[0]; + std::string cmd = (parameters.empty() ? "query" : parameters[0]); if (cmd == "query" || cmd == "list") { df::job *job = getSelectedWorkshopJob(c); @@ -306,28 +327,23 @@ static command_result job_cmd(Core * c, vector & parameters) } else if (cmd == "item-material") { - if (parameters.size() < 1+1+1) + if (parameters.size() != 3) return CR_WRONG_USAGE; df::job *job = getSelectedWorkshopJob(c); - if (!job) + df::job_item *item = getJobItem(c, job, parameters[1]); + if (!item) return CR_WRONG_USAGE; - int v = atoi(parameters[1].c_str()); - if (v < 1 || v > job->job_items.size()) { - c->con.printerr("Invalid item index.\n"); - return CR_WRONG_USAGE; - } + ItemTypeInfo iinfo(item); + MaterialInfo minfo; - df::job_item *item = job->job_items[v-1]; - - MaterialInfo info; - if (!info.find(parameters[2])) { + if (!minfo.find(parameters[2])) { c->con.printerr("Could not find the specified material.\n"); return CR_FAILURE; } - if (!info.matches(*item)) { + if (!iinfo.matches(*item, &minfo)) { c->con.printerr("Material does not match the requirements.\n"); printJobDetails(c, job); return CR_FAILURE; @@ -337,19 +353,52 @@ static command_result job_cmd(Core * c, vector & parameters) job->mat_type == item->mat_type && job->mat_index == item->mat_index) { - job->mat_type = info.type; - job->mat_index = info.index; + job->mat_type = minfo.type; + job->mat_index = minfo.index; } - item->mat_type = info.type; - item->mat_index = info.index; + item->mat_type = minfo.type; + item->mat_index = minfo.index; + + c->con << "Job item updated." << endl; + + if (item->item_type < 0 && minfo.isValid()) + c->con.printerr("WARNING: Due to a probable bug, creature & plant material subtype\n" + " is ignored unless the item type is also specified.\n"); - c->con << "Job item " << v << " updated." << endl; printJobDetails(c, job); return CR_OK; } else if (cmd == "item-type") { + if (parameters.size() != 3) + return CR_WRONG_USAGE; + + df::job *job = getSelectedWorkshopJob(c); + df::job_item *item = getJobItem(c, job, parameters[1]); + if (!item) + return CR_WRONG_USAGE; + + ItemTypeInfo iinfo; + MaterialInfo minfo(item); + + if (!iinfo.find(parameters[2])) { + c->con.printerr("Could not find the specified item type.\n"); + return CR_FAILURE; + } + + if (!iinfo.matches(*item, &minfo)) { + c->con.printerr("Item type does not match the requirements.\n"); + printJobDetails(c, job); + return CR_FAILURE; + } + + item->item_type = iinfo.type; + item->item_subtype = iinfo.subtype; + + c->con << "Job item updated." << endl; + printJobDetails(c, job); + return CR_OK; } else return CR_WRONG_USAGE;