summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-09-17 21:15:51 +0400
committerAlexander Gavrilov2012-09-17 21:15:51 +0400
commit36e44c682cc2cecb552eca8dfc75ad1a436086cc (patch)
tree60793f2c17ceeb9a2849e60e819551e21fbf8c90
parent613063cef4d87b3b4307144b85da60dc40daceb3 (diff)
downloaddfhack-36e44c682cc2cecb552eca8dfc75ad1a436086cc.tar.gz
dfhack-36e44c682cc2cecb552eca8dfc75ad1a436086cc.tar.bz2
dfhack-36e44c682cc2cecb552eca8dfc75ad1a436086cc.tar.xz
Add a plugin implementing 'add spatter to item' reactions.
-rw-r--r--NEWS5
-rw-r--r--library/include/modules/Materials.h5
-rw-r--r--library/modules/Materials.cpp13
m---------library/xml0
-rw-r--r--plugins/CMakeLists.txt4
-rw-r--r--plugins/add-spatter.cpp409
-rw-r--r--plugins/cleaners.cpp13
-rw-r--r--plugins/raw/entity_default.diff29
-rw-r--r--plugins/raw/material_template_default.diff10
-rw-r--r--plugins/raw/reaction_spatter.txt41
10 files changed, 525 insertions, 4 deletions
diff --git a/NEWS b/NEWS
index 4294bedb..fdc69ac5 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 76c89de3..fb5a6353 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 50cf21a9..db9c9c7d 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
-Subproject a6b95f1c42991e485f7e0bb5d029a5eca14ce9a
+Subproject 260ff4a1ddcfd54d0143aa6d908a93c4ff709c8
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 8511d86c..0b0ad046 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 00000000..ed5f47f7
--- /dev/null
+++ b/plugins/add-spatter.cpp
@@ -0,0 +1,409 @@
+#include "Core.h"
+#include <Console.h>
+#include <Export.h>
+#include <PluginManager.h>
+#include <modules/Gui.h>
+#include <modules/Screen.h>
+#include <modules/Maps.h>
+#include <modules/Job.h>
+#include <modules/Items.h>
+#include <TileTypes.h>
+#include <vector>
+#include <cstdio>
+#include <stack>
+#include <string>
+#include <cmath>
+#include <string.h>
+
+#include <VTableInterpose.h>
+#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<ProductInfo> products;
+};
+
+static std::map<std::string, ReactionInfo> reactions;
+static std::map<df::reaction_product*, ProductInfo*> 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<int, std::vector<df::item*> > 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<df::item*> 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<df::reaction_reagent*> *in_reag,
+ std::vector<df::item*> *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<df::item*> *out_items,
+ std::vector<df::reaction_reagent*> *in_reag,
+ std::vector<df::item*> *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<df::reaction_reagent_itemst>(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<improvement_product>(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 <PluginCommand> &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 c0301de7..319b83c1 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<df::contaminant*> 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 00000000..a99f8ebb
--- /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 00000000..8b6ef327
--- /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 00000000..b31d82fa
--- /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]