diff --git a/NEWS b/NEWS index 4294bedb4..fdc69ac53 100644 --- a/NEWS +++ b/NEWS @@ -60,4 +60,7 @@ DFHack v0.34.11-r2 (UNRELEASED) and restricting operator skill range like with ordinary workshops. Disclaimer: not in any way to undermine the future siege update from Toady, but the aiming logic of existing engines hasn't been updated since 2D, and is almost useless as/is. - + New Add Spatter plugin: + Detects reactions with certain names in the raws, and changes them from adding + improvements to adding item contaminants. This allows directly covering items + with poisons. The added spatters are immune both to water and 'clean items'. diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 76c89de30..fb5a6353c 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -131,6 +131,11 @@ namespace DFHack bool findPlant(const std::string &token, const std::string &subtoken); bool findCreature(const std::string &token, const std::string &subtoken); + bool findProduct(df::material *material, const std::string &name); + bool findProduct(const MaterialInfo &info, const std::string &name) { + return findProduct(info.material, name); + } + std::string getToken(); std::string toString(uint16_t temp = 10015, bool named = true); diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 50cf21a9c..db9c9c7df 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -283,6 +283,19 @@ bool MaterialInfo::findCreature(const std::string &token, const std::string &sub return decode(-1); } +bool MaterialInfo::findProduct(df::material *material, const std::string &name) +{ + if (!material || name.empty()) + return decode(-1); + + auto &pids = material->reaction_product.id; + for (size_t i = 0; i < pids.size(); i++) + if ((*pids[i]) == name) + return decode(material->reaction_product.material, i); + + return decode(-1); +} + std::string MaterialInfo::getToken() { if (isNone()) diff --git a/library/xml b/library/xml index a6b95f1c4..260ff4a1d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a6b95f1c42991e485f7e0bb5d029a5eca14ce9ae +Subproject commit 260ff4a1ddcfd54d0143aa6d908a93c4ff709c87 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8511d86c6..0b0ad0461 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -47,6 +47,9 @@ install(DIRECTORY lua/ install(DIRECTORY raw/ DESTINATION ${DFHACK_DATA_DESTINATION}/raw FILES_MATCHING PATTERN "*.txt") +install(DIRECTORY raw/ + DESTINATION ${DFHACK_DATA_DESTINATION}/raw + FILES_MATCHING PATTERN "*.diff") # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) @@ -120,6 +123,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(steam-engine steam-engine.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(add-spatter add-spatter.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp new file mode 100644 index 000000000..ed5f47f7b --- /dev/null +++ b/plugins/add-spatter.cpp @@ -0,0 +1,409 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "df/item_liquid_miscst.h" +#include "df/item_constructed.h" +#include "df/builtin_mats.h" +#include "df/world.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/job_item_ref.h" +#include "df/ui.h" +#include "df/report.h" +#include "df/reaction.h" +#include "df/reaction_reagent_itemst.h" +#include "df/reaction_product_item_improvementst.h" +#include "df/reaction_product_improvement_flags.h" +#include "df/matter_state.h" +#include "df/contaminant.h" + +#include "MiscUtils.h" + +using std::vector; +using std::string; +using std::stack; +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; +using df::global::world; +using df::global::ui; + +typedef df::reaction_product_item_improvementst improvement_product; + +DFHACK_PLUGIN("add-spatter"); + +struct ReagentSource { + int idx; + df::reaction_reagent *reagent; + + ReagentSource() : idx(-1), reagent(NULL) {} +}; + +struct MaterialSource : ReagentSource { + bool product; + std::string product_name; + + int mat_type, mat_index; + + MaterialSource() : product(false), mat_type(-1), mat_index(-1) {} +}; + +struct ProductInfo { + df::reaction *react; + improvement_product *product; + + ReagentSource object; + MaterialSource material; + + bool isValid() { + return object.reagent && (material.mat_type >= 0 || material.reagent); + } +}; + +struct ReactionInfo { + df::reaction *react; + + std::vector products; +}; + +static std::map reactions; +static std::map products; + +static ReactionInfo *find_reaction(const std::string &name) +{ + auto it = reactions.find(name); + return (it != reactions.end()) ? &it->second : NULL; +} + +static bool is_add_spatter(const std::string &name) +{ + return name.size() > 12 && memcmp(name.data(), "SPATTER_ADD_", 12) == 0; +} + +static void find_material(int *type, int *index, df::item *input, MaterialSource &mat) +{ + if (input && mat.reagent) + { + MaterialInfo info(input); + + if (mat.product) + { + if (!info.findProduct(info, mat.product_name)) + { + color_ostream_proxy out(Core::getInstance().getConsole()); + out.printerr("Cannot find product '%s'\n", mat.product_name.c_str()); + } + } + + *type = info.type; + *index = info.index; + } + else + { + *type = mat.mat_type; + *index = mat.mat_index; + } +} + +static bool has_contaminant(df::item_actual *item, int type, int index) +{ + auto cont = item->contaminants; + if (!cont) + return false; + + for (size_t i = 0; i < cont->size(); i++) + { + auto cur = (*cont)[i]; + if (cur->mat_type == type && cur->mat_index == index) + return true; + } + + return false; +} + +/* + * Hooks + */ + +typedef std::map > item_table; + +static void index_items(item_table &table, df::job *job, ReactionInfo *info) +{ + for (int i = job->items.size()-1; i >= 0; i--) + { + auto iref = job->items[i]; + if (iref->job_item_idx < 0) continue; + auto iitem = job->job_items[iref->job_item_idx]; + + if (iitem->contains.empty()) + { + table[iitem->reagent_index].push_back(iref->item); + } + else + { + std::vector contents; + Items::getContainedItems(iref->item, &contents); + + for (int j = contents.size()-1; j >= 0; j--) + { + for (int k = iitem->contains.size()-1; k >= 0; k--) + { + int ridx = iitem->contains[k]; + auto reag = info->react->reagents[ridx]; + + if (reag->matches(contents[j], info->react, iitem->reaction_id)) + table[ridx].push_back(contents[j]); + } + } + } + } +} + +df::item* find_item(ReagentSource &info, item_table &table) +{ + if (!info.reagent) + return NULL; + if (table[info.idx].empty()) + return NULL; + return table[info.idx].back(); +} + +struct item_hook : df::item_constructed { + typedef df::item_constructed interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isImprovable, (df::job *job, int16_t mat_type, int32_t mat_index)) + { + ReactionInfo *info; + + if (job && job->job_type == job_type::CustomReaction && + (info = find_reaction(job->reaction_name)) != NULL) + { + if (!contaminants || contaminants->empty()) + return true; + + item_table table; + index_items(table, job, info); + + for (size_t i = 0; i < info->products.size(); i++) + { + auto &product = info->products[i]; + + int mattype, matindex; + auto material = find_item(info->products[i].material, table); + + find_material(&mattype, &matindex, material, product.material); + + if (mattype < 0 || has_contaminant(this, mattype, matindex)) + return false; + } + + return true; + } + + return INTERPOSE_NEXT(isImprovable)(job, mat_type, mat_index); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(item_hook, isImprovable); + +df::item* find_item( + ReagentSource &info, + std::vector *in_reag, + std::vector *in_items +) { + if (!info.reagent) + return NULL; + for (int i = in_items->size(); i >= 0; i--) + if ((*in_reag)[i] == info.reagent) + return (*in_items)[i]; + return NULL; +} + +struct product_hook : improvement_product { + typedef improvement_product interpose_base; + + DEFINE_VMETHOD_INTERPOSE( + void, produce, + (df::unit *unit, std::vector *out_items, + std::vector *in_reag, + std::vector *in_items, + int32_t quantity, int16_t skill, + df::historical_entity *entity, df::world_site *site) + ) { + if (auto product = products[this]) + { + auto object = find_item(product->object, in_reag, in_items); + auto material = find_item(product->material, in_reag, in_items); + + if (object && (material || !product->material.reagent)) + { + int mattype, matindex; + find_material(&mattype, &matindex, material, product->material); + + object->addContaminant( + mattype, matindex, + matter_state::Liquid, // TODO: heuristics or by reagent name + object->getTemperature(), + probability, // used as size + -1, + 0x8000 // not washed by water, and 'clean items' safe. + ); + } + + return; + } + + INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce); + +/* + * Scan raws for matching reactions. + */ + +static void find_reagent( + color_ostream &out, ReagentSource &info, df::reaction *react, std::string name +) { + for (size_t i = 0; i < react->reagents.size(); i++) + { + if (react->reagents[i]->code != name) + continue; + + info.idx = i; + info.reagent = react->reagents[i]; + return; + } + + out.printerr("Invalid reagent name '%s' in '%s'\n", name.c_str(), react->code.c_str()); +} + +static void parse_product( + color_ostream &out, ProductInfo &info, df::reaction *react, improvement_product *prod +) { + using namespace df::enums::reaction_product_improvement_flags; + + info.react = react; + info.product = prod; + + find_reagent(out, info.object, react, prod->target_reagent); + + auto ritem = strict_virtual_cast(info.object.reagent); + if (ritem) + ritem->flags1.bits.improvable = true; + + info.material.mat_type = prod->mat_type; + info.material.mat_index = prod->mat_index; + + if (prod->flags.is_set(GET_MATERIAL_PRODUCT)) + { + find_reagent(out, info.material, react, prod->get_material.reagent_code); + + info.material.product = true; + info.material.product_name = prod->get_material.product_code; + } + else if (prod->flags.is_set(GET_MATERIAL_SAME)) + { + find_reagent(out, info.material, react, prod->get_material.reagent_code); + } +} + +static bool find_reactions(color_ostream &out) +{ + reactions.clear(); + products.clear(); + + auto &rlist = world->raws.reactions; + + for (size_t i = 0; i < rlist.size(); i++) + { + if (!is_add_spatter(rlist[i]->code)) + continue; + + reactions[rlist[i]->code].react = rlist[i]; + } + + for (auto it = reactions.begin(); it != reactions.end(); ++it) + { + auto &prod = it->second.react->products; + auto &out_prod = it->second.products; + + for (size_t i = 0; i < prod.size(); i++) + { + auto itprod = strict_virtual_cast(prod[i]); + if (!itprod) continue; + + out_prod.push_back(ProductInfo()); + parse_product(out, out_prod.back(), it->second.react, itprod); + } + + for (size_t i = 0; i < prod.size(); i++) + { + if (out_prod[i].isValid()) + products[out_prod[i].product] = &out_prod[i]; + } + } + + return !products.empty(); +} + +static void enable_hooks(bool enable) +{ + INTERPOSE_HOOK(item_hook, isImprovable).apply(enable); + INTERPOSE_HOOK(product_hook, produce).apply(enable); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + if (find_reactions(out)) + { + out.print("Detected spatter add reactions - enabling plugin.\n"); + enable_hooks(true); + } + else + enable_hooks(false); + break; + case SC_MAP_UNLOADED: + enable_hooks(false); + reactions.clear(); + products.clear(); + break; + default: + break; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (Core::getInstance().isMapLoaded()) + plugin_onstatechange(out, SC_MAP_LOADED); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + enable_hooks(false); + return CR_OK; +} diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index c0301de7b..319b83c1f 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -81,11 +81,18 @@ command_result cleanitems (color_ostream &out) df::item_actual *item = (df::item_actual *)world->items.all[i]; if (item->contaminants && item->contaminants->size()) { + std::vector saved; for (size_t j = 0; j < item->contaminants->size(); j++) - delete item->contaminants->at(j); + { + auto obj = (*item->contaminants)[j]; + if (obj->flags.whole & 0x8000) // DFHack-generated contaminant + saved.push_back(obj); + else + delete obj; + } cleaned_items++; - cleaned_total += item->contaminants->size(); - item->contaminants->clear(); + cleaned_total += item->contaminants->size() - saved.size(); + item->contaminants->swap(saved); } } if (cleaned_total) diff --git a/plugins/raw/entity_default.diff b/plugins/raw/entity_default.diff new file mode 100644 index 000000000..a99f8ebba --- /dev/null +++ b/plugins/raw/entity_default.diff @@ -0,0 +1,29 @@ +--- ../objects.old/entity_default.txt 2012-09-17 17:59:28.853898702 +0400 ++++ entity_default.txt 2012-09-17 17:59:28.684899429 +0400 +@@ -49,6 +49,7 @@ + [TRAPCOMP:ITEM_TRAPCOMP_SPIKEDBALL] + [TRAPCOMP:ITEM_TRAPCOMP_LARGESERRATEDDISC] + [TRAPCOMP:ITEM_TRAPCOMP_MENACINGSPIKE] ++ [TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON] + [TOY:ITEM_TOY_PUZZLEBOX] + [TOY:ITEM_TOY_BOAT] + [TOY:ITEM_TOY_HAMMER] +@@ -204,6 +205,8 @@ + [PERMITTED_JOB:WAX_WORKER] + [PERMITTED_BUILDING:SOAP_MAKER] + [PERMITTED_BUILDING:SCREW_PRESS] ++ [PERMITTED_BUILDING:STEAM_ENGINE] ++ [PERMITTED_BUILDING:MAGMA_STEAM_ENGINE] + [PERMITTED_REACTION:TAN_A_HIDE] + [PERMITTED_REACTION:RENDER_FAT] + [PERMITTED_REACTION:MAKE_SOAP_FROM_TALLOW] +@@ -248,6 +251,9 @@ + [PERMITTED_REACTION:ROSE_GOLD_MAKING] + [PERMITTED_REACTION:BISMUTH_BRONZE_MAKING] + [PERMITTED_REACTION:ADAMANTINE_WAFERS] ++ [PERMITTED_REACTION:STOKE_BOILER] ++ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_WEAPON] ++ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_AMMO] + [WORLD_CONSTRUCTION:TUNNEL] + [WORLD_CONSTRUCTION:BRIDGE] + [WORLD_CONSTRUCTION:ROAD] diff --git a/plugins/raw/material_template_default.diff b/plugins/raw/material_template_default.diff new file mode 100644 index 000000000..8b6ef327b --- /dev/null +++ b/plugins/raw/material_template_default.diff @@ -0,0 +1,10 @@ +--- ../objects.old/material_template_default.txt 2012-09-17 17:59:28.907898469 +0400 ++++ material_template_default.txt 2012-09-17 17:59:28.695899382 +0400 +@@ -2374,6 +2374,7 @@ + [MAX_EDGE:500] + [ABSORPTION:100] + [LIQUID_MISC_CREATURE] ++ [REACTION_CLASS:CREATURE_EXTRACT] + [ROTS] + + This is for creatures that are "made of fire". Right now there isn't a good format for that. diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt new file mode 100644 index 000000000..b31d82fa0 --- /dev/null +++ b/plugins/raw/reaction_spatter.txt @@ -0,0 +1,41 @@ +reaction_spatter + +[OBJECT:REACTION] + +Reaction name must start with 'SPATTER_ADD_': + +[REACTION:SPATTER_ADD_EXTRACT_WEAPON] + [NAME:cover weapon with extract] + [BUILDING:CRAFTSMAN:CUSTOM_ALT_V] + [SKILL:DYER] + [ADVENTURE_MODE_ENABLED] + [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:10] + [REACTION_CLASS:CREATURE_EXTRACT] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be the last reagent: + [REAGENT:object:1:WEAPON:NONE:NONE:NONE] + [PRESERVE_REAGENT] + The probability is used as spatter size instead: + [IMPROVEMENT:100:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] + +[REACTION:SPATTER_ADD_EXTRACT_AMMO] + [NAME:cover ammo with extract] + [BUILDING:CRAFTSMAN:CUSTOM_ALT_M] + [SKILL:DYER] + [ADVENTURE_MODE_ENABLED] + [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:10] + [REACTION_CLASS:CREATURE_EXTRACT] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be the last reagent: + [REAGENT:object:1:AMMO:NONE:NONE:NONE] + [PRESERVE_REAGENT] + The probability is used as spatter size instead: + [IMPROVEMENT:100:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]