Add a plugin implementing 'add spatter to item' reactions.

develop
Alexander Gavrilov 2012-09-17 21:15:51 +04:00
parent 613063cef4
commit 36e44c682c
10 changed files with 526 additions and 5 deletions

@ -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'.

@ -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);

@ -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())

@ -1 +1 @@
Subproject commit a6b95f1c42991e485f7e0bb5d029a5eca14ce9ae
Subproject commit 260ff4a1ddcfd54d0143aa6d908a93c4ff709c87

@ -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()

@ -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;
}

@ -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)

@ -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]

@ -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.

@ -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]