From 604cf808321382286619042b1edbf81558086cb5 Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Thu, 30 Aug 2012 09:23:11 -0500
Subject: [PATCH 01/19] Repurpose the nestboxes plugin as a watcher that
automatically forbids fertile eggs.
---
plugins/devel/nestboxes.cpp | 111 ++++++++++++++++++------------------
1 file changed, 57 insertions(+), 54 deletions(-)
diff --git a/plugins/devel/nestboxes.cpp b/plugins/devel/nestboxes.cpp
index b3d24cd92..42c3c0660 100644
--- a/plugins/devel/nestboxes.cpp
+++ b/plugins/devel/nestboxes.cpp
@@ -31,6 +31,40 @@ static command_result nestboxes(color_ostream &out, vector & parameters
DFHACK_PLUGIN("nestboxes");
+static bool enabled = false;
+
+static void eggscan(color_ostream &out)
+{
+ CoreSuspender suspend;
+
+ for (int i = 0; i < world->buildings.all.size(); ++i)
+ {
+ df::building *build = world->buildings.all[i];
+ auto type = build->getType();
+ if (df::enums::building_type::NestBox == type)
+ {
+ bool fertile = false;
+ df::building_nest_boxst *nb = virtual_cast(build);
+ if (nb->claimed_by != -1)
+ {
+ df::unit* u = df::unit::find(nb->claimed_by);
+ if (u && u->relations.pregnancy_timer > 0)
+ fertile = true;
+ }
+ for (int j = 1; j < nb->contained_items.size(); j++)
+ {
+ df::item* item = nb->contained_items[j]->item;
+ if (item->flags.bits.forbid != fertile)
+ {
+ item->flags.bits.forbid = fertile;
+ out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl;
+ }
+ }
+ }
+ }
+}
+
+
DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands)
{
if (world && ui) {
@@ -49,6 +83,19 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
+DFhackCExport command_result plugin_onupdate(color_ostream &out)
+{
+ if (!enabled)
+ return CR_OK;
+
+ static unsigned cnt = 0;
+ if ((++cnt % 5) != 0)
+ return CR_OK;
+
+ eggscan(out);
+
+ return CR_OK;
+}
static command_result nestboxes(color_ostream &out, vector & parameters)
{
@@ -57,60 +104,16 @@ static command_result nestboxes(color_ostream &out, vector & parameters
int dump_count = 0;
int good_egg = 0;
- if (parameters.size() == 1 && parameters[0] == "clean")
- {
- clean = true;
- }
- for (int i = 0; i < world->buildings.all.size(); ++i)
- {
- df::building *build = world->buildings.all[i];
- auto type = build->getType();
- if (df::enums::building_type::NestBox == type)
- {
- bool needs_clean = false;
- df::building_nest_boxst *nb = virtual_cast(build);
- out << "Nestbox at (" << nb->x1 << "," << nb->y1 << ","<< nb->z << "): claimed-by " << nb->claimed_by << ", contained item count " << nb->contained_items.size() << " (" << nb->anon_1 << ")" << endl;
- if (nb->contained_items.size() > 1)
- needs_clean = true;
- if (nb->claimed_by != -1)
- {
- df::unit* u = df::unit::find(nb->claimed_by);
- if (u)
- {
- out << " Claimed by ";
- if (u->name.has_name)
- out << u->name.first_name << ", ";
- df::creature_raw *raw = df::global::world->raws.creatures.all[u->race];
- out << raw->creature_id
- << ", pregnancy timer " << u->relations.pregnancy_timer << endl;
- if (u->relations.pregnancy_timer > 0)
- needs_clean = false;
- }
- }
- for (int j = 1; j < nb->contained_items.size(); j++)
- {
- df::item* item = nb->contained_items[j]->item;
- if (needs_clean) {
- if (clean && !item->flags.bits.dump)
- {
- item->flags.bits.dump = 1;
- dump_count += item->getStackSize();
-
- }
- } else {
- good_egg += item->getStackSize();
- }
- }
- }
- }
-
- if (clean)
- {
- out << dump_count << " eggs dumped." << endl;
- }
- out << good_egg << " fertile eggs found." << endl;
-
-
+ if (parameters.size() == 1) {
+ if (parameters[0] == "enable")
+ enabled = true;
+ else if (parameters[0] == "disable")
+ enabled = false;
+ else
+ return CR_WRONG_USAGE;
+ } else {
+ out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl;
+ }
return CR_OK;
}
From 24772f4dbcfcfbf0ac3d29f72d3bda19566d8530 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 14 Sep 2012 18:49:02 +0400
Subject: [PATCH 02/19] Add an api function for destroying items.
---
LUA_API.rst | 4 ++++
Lua API.html | 3 +++
library/LuaApi.cpp | 7 +++++++
library/include/modules/Items.h | 3 +++
library/modules/Items.cpp | 32 ++++++++++++++++++++++++++++++++
5 files changed, 49 insertions(+)
diff --git a/LUA_API.rst b/LUA_API.rst
index 1ffdada0a..c5f9a1c58 100644
--- a/LUA_API.rst
+++ b/LUA_API.rst
@@ -999,6 +999,10 @@ Items module
Move the item to the unit inventory. Returns *false* if impossible.
+* ``dfhack.items.remove(item[, no_uncat])``
+
+ Removes the item, and marks it for garbage collection unless ``no_uncat`` is true.
+
* ``dfhack.items.makeProjectile(item)``
Turns the item into a projectile, and returns the new object, or *nil* if impossible.
diff --git a/Lua API.html b/Lua API.html
index 168f7dcc6..07f038bc4 100644
--- a/Lua API.html
+++ b/Lua API.html
@@ -1213,6 +1213,9 @@ Returns false in case of error.
dfhack.items.moveToInventory(item,unit,use_mode,body_part)
Move the item to the unit inventory. Returns false if impossible.
+dfhack.items.remove(item[, no_uncat])
+Removes the item, and marks it for garbage collection unless no_uncat is true.
+
dfhack.items.makeProjectile(item)
Turns the item into a projectile, and returns the new object, or nil if impossible.
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index f69fa7a1b..f8497569e 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -886,6 +886,12 @@ static bool items_moveToInventory
return Items::moveToInventory(mc, item, unit, mode, body_part);
}
+static bool items_remove(df::item *item, bool no_uncat)
+{
+ MapExtras::MapCache mc;
+ return Items::remove(mc, item, no_uncat);
+}
+
static df::proj_itemst *items_makeProjectile(df::item *item)
{
MapExtras::MapCache mc;
@@ -904,6 +910,7 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = {
WRAPN(moveToBuilding, items_moveToBuilding),
WRAPN(moveToInventory, items_moveToInventory),
WRAPN(makeProjectile, items_makeProjectile),
+ WRAPN(remove, items_remove),
{ NULL, NULL }
};
diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h
index 7493d22fc..81c8e1285 100644
--- a/library/include/modules/Items.h
+++ b/library/include/modules/Items.h
@@ -157,6 +157,9 @@ DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::b
DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit,
df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1);
+/// Makes the item removed and marked for garbage collection
+DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat = false);
+
/// Detaches the items from its current location and turns it into a projectile
DFHACK_EXPORT df::proj_itemst *makeProjectile(MapExtras::MapCache &mc, df::item *item);
}
diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp
index 751797f06..b8c697a48 100644
--- a/library/modules/Items.cpp
+++ b/library/modules/Items.cpp
@@ -730,6 +730,18 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item)
item->flags.bits.in_inventory = false;
return true;
}
+ else if (item->flags.bits.removed)
+ {
+ item->flags.bits.removed = false;
+
+ if (item->flags.bits.garbage_collect)
+ {
+ item->flags.bits.garbage_collect = false;
+ item->categorize(true);
+ }
+
+ return true;
+ }
else
return false;
}
@@ -871,6 +883,26 @@ bool DFHack::Items::moveToInventory(
return true;
}
+bool Items::remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat)
+{
+ CHECK_NULL_POINTER(item);
+
+ auto pos = getPosition(item);
+
+ if (!detachItem(mc, item))
+ return false;
+
+ if (pos.isValid())
+ item->pos = pos;
+
+ if (!no_uncat)
+ item->uncategorize();
+
+ item->flags.bits.removed = true;
+ item->flags.bits.garbage_collect = !no_uncat;
+ return true;
+}
+
df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item)
{
CHECK_NULL_POINTER(item);
From 811c096c0ecdcde1d0d19be8e4996996bde27995 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 14 Sep 2012 20:22:49 +0400
Subject: [PATCH 03/19] Vaporize liquids from barrels, and destroy bin contents
in siege engine.
---
library/xml | 2 +-
plugins/devel/dumpmats.cpp | 53 +++++---------------
plugins/devel/siege-engine.cpp | 89 ++++++++++++++++++++++++++++++++--
3 files changed, 99 insertions(+), 45 deletions(-)
diff --git a/library/xml b/library/xml
index 2bc8fbdf7..ee2b63a8f 160000
--- a/library/xml
+++ b/library/xml
@@ -1 +1 @@
-Subproject commit 2bc8fbdf71143398817d31e06e169a01cce37c50
+Subproject commit ee2b63a8ffdbce66489148ca2a9803db1d0b9090
diff --git a/plugins/devel/dumpmats.cpp b/plugins/devel/dumpmats.cpp
index ba888e7cf..0af1fce50 100644
--- a/plugins/devel/dumpmats.cpp
+++ b/plugins/devel/dumpmats.cpp
@@ -11,6 +11,7 @@
#include "df/matter_state.h"
#include "df/descriptor_color.h"
#include "df/item_type.h"
+#include "df/strain_type.h"
using std::string;
using std::vector;
@@ -195,47 +196,17 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters)
if (mat->molar_mass != 0xFBBC7818)
out.print("\t[MOLAR_MASS:%i]\n", mat->molar_mass);
- if (mat->strength.impact_yield != 10000)
- out.print("\t[IMPACT_YIELD:%i]\n", mat->strength.impact_yield);
- if (mat->strength.impact_fracture != 10000)
- out.print("\t[IMPACT_FRACTURE:%i]\n", mat->strength.impact_fracture);
- if (mat->strength.impact_strain_at_yield != 0)
- out.print("\t[IMPACT_STRAIN_AT_YIELD:%i]\n", mat->strength.impact_strain_at_yield);
-
- if (mat->strength.compressive_yield != 10000)
- out.print("\t[COMPRESSIVE_YIELD:%i]\n", mat->strength.compressive_yield);
- if (mat->strength.compressive_fracture != 10000)
- out.print("\t[COMPRESSIVE_FRACTURE:%i]\n", mat->strength.compressive_fracture);
- if (mat->strength.compressive_strain_at_yield != 0)
- out.print("\t[COMPRESSIVE_STRAIN_AT_YIELD:%i]\n", mat->strength.compressive_strain_at_yield);
-
- if (mat->strength.tensile_yield != 10000)
- out.print("\t[TENSILE_YIELD:%i]\n", mat->strength.tensile_yield);
- if (mat->strength.tensile_fracture != 10000)
- out.print("\t[TENSILE_FRACTURE:%i]\n", mat->strength.tensile_fracture);
- if (mat->strength.tensile_strain_at_yield != 0)
- out.print("\t[TENSILE_STRAIN_AT_YIELD:%i]\n", mat->strength.tensile_strain_at_yield);
-
- if (mat->strength.torsion_yield != 10000)
- out.print("\t[TORSION_YIELD:%i]\n", mat->strength.torsion_yield);
- if (mat->strength.torsion_fracture != 10000)
- out.print("\t[TORSION_FRACTURE:%i]\n", mat->strength.torsion_fracture);
- if (mat->strength.torsion_strain_at_yield != 0)
- out.print("\t[TORSION_STRAIN_AT_YIELD:%i]\n", mat->strength.torsion_strain_at_yield);
-
- if (mat->strength.shear_yield != 10000)
- out.print("\t[SHEAR_YIELD:%i]\n", mat->strength.shear_yield);
- if (mat->strength.shear_fracture != 10000)
- out.print("\t[SHEAR_FRACTURE:%i]\n", mat->strength.shear_fracture);
- if (mat->strength.shear_strain_at_yield != 0)
- out.print("\t[SHEAR_STRAIN_AT_YIELD:%i]\n", mat->strength.shear_strain_at_yield);
-
- if (mat->strength.bending_yield != 10000)
- out.print("\t[BENDING_YIELD:%i]\n", mat->strength.bending_yield);
- if (mat->strength.bending_fracture != 10000)
- out.print("\t[BENDING_FRACTURE:%i]\n", mat->strength.bending_fracture);
- if (mat->strength.bending_strain_at_yield != 0)
- out.print("\t[BENDING_STRAIN_AT_YIELD:%i]\n", mat->strength.bending_strain_at_yield);
+ FOR_ENUM_ITEMS(strain_type, strain)
+ {
+ auto name = ENUM_KEY_STR(strain_type,strain);
+
+ if (mat->strength.yield[strain] != 10000)
+ out.print("\t[%s_YIELD:%i]\n", name.c_str(), mat->strength.yield[strain]);
+ if (mat->strength.fracture[strain] != 10000)
+ out.print("\t[%s_FRACTURE:%i]\n", name.c_str(), mat->strength.fracture[strain]);
+ if (mat->strength.strain_at_yield[strain] != 0)
+ out.print("\t[%s_STRAIN_AT_YIELD:%i]\n", name.c_str(), mat->strength.strain_at_yield[strain]);
+ }
if (mat->strength.max_edge != 0)
out.print("\t[MAX_EDGE:%i]\n", mat->strength.max_edge);
diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp
index a41bfe5f7..d1e34ced9 100644
--- a/plugins/devel/siege-engine.cpp
+++ b/plugins/devel/siege-engine.cpp
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -45,11 +46,14 @@
#include "df/unit_misc_trait.h"
#include "df/job.h"
#include "df/job_item.h"
-#include "df/item.h"
+#include "df/item_actual.h"
#include "df/items_other_id.h"
#include "df/building_stockpilest.h"
#include "df/stockpile_links.h"
#include "df/workshop_profile.h"
+#include "df/strain_type.h"
+#include "df/material.h"
+#include "df/flow_type.h"
#include "MiscUtils.h"
@@ -162,6 +166,63 @@ static void random_direction(float &x, float &y, float &z)
z = 1.0f - 2.0f*d;
}
+static const int WEAR_TICKS = 806400;
+
+static bool apply_impact_damage(df::item *item, int minv, int maxv)
+{
+ MaterialInfo info(item);
+ if (!info.isValid())
+ {
+ item->setWear(3);
+ return false;
+ }
+
+ auto &strength = info.material->strength;
+
+ // Use random strain type excluding COMPRESSIVE (conveniently last)
+ int type = random_int(strain_type::COMPRESSIVE);
+ int power = minv + random_int(maxv-minv+1);
+
+ // High elasticity materials just bend
+ if (strength.strain_at_yield[type] >= 5000)
+ return true;
+
+ // Instant fracture?
+ int fracture = strength.fracture[type];
+ if (fracture <= power)
+ {
+ item->setWear(3);
+ return false;
+ }
+
+ // Impact within elastic strain range?
+ int yield = strength.yield[type];
+ if (yield > power)
+ return true;
+
+ // Can wear?
+ auto actual = virtual_cast(item);
+ if (!actual)
+ return false;
+
+ // Transform plastic deformation to wear
+ int max_wear = WEAR_TICKS * 4;
+ int cur_wear = WEAR_TICKS * actual->wear + actual->wear_timer;
+ cur_wear += int64_t(power - yield)*max_wear/(fracture - yield);
+
+ if (cur_wear >= max_wear)
+ {
+ actual->wear = 3;
+ return false;
+ }
+ else
+ {
+ actual->wear = cur_wear / WEAR_TICKS;
+ actual->wear_timer = cur_wear % WEAR_TICKS;
+ return true;
+ }
+}
+
/*
* Configuration object
*/
@@ -1363,6 +1424,10 @@ struct projectile_hook : df::proj_itemst {
float speed = 100000.0f / (fall_delay + 1);
int min_zspeed = (fall_delay+1)*4900;
+ float bonus = 1.0f + 0.1f*(origin_pos.z -cur_pos.z);
+ bonus *= 1.0f + (distance_flown - 60) / 200.0f;
+ speed *= bonus;
+
// Flight direction vector
df::coord dist = target_pos - origin_pos;
float vx = dist.x, vy = dist.y, vz = fabs(dist.z);
@@ -1383,10 +1448,28 @@ struct projectile_hook : df::proj_itemst {
for (size_t i = 0; i < contents.size(); i++)
{
auto child = contents[i];
+
+ // Liquids are vaporized so that they cover nearby units
+ if (child->isLiquid())
+ {
+ auto flow = Maps::spawnFlow(
+ cur_pos,
+ flow_type::MaterialVapor,
+ child->getMaterial(), child->getMaterialIndex(),
+ 100
+ );
+
+ // should it leave a puddle too?..
+ if (flow && Items::remove(mc, child))
+ continue;
+ }
+
auto proj = Items::makeProjectile(mc, child);
if (!proj) continue;
- proj->flags.bits.no_impact_destroy = true;
+ bool keep = apply_impact_damage(child, 50000, int(250000*bonus));
+
+ proj->flags.bits.no_impact_destroy = keep;
//proj->flags.bits.bouncing = true;
proj->flags.bits.piercing = true;
proj->flags.bits.parabolic = true;
@@ -1403,7 +1486,7 @@ struct projectile_hook : df::proj_itemst {
proj->speed_x = int(speed * sx);
proj->speed_y = int(speed * sy);
- proj->speed_z = int(speed * sz);
+ proj->speed_z = std::max(min_zspeed, int(speed * sz));
}
}
From 000e3baf27e3d811e673bca08e4381c1f2c632b7 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 14 Sep 2012 20:57:03 +0400
Subject: [PATCH 04/19] Implement skill-based miss probability in siege engine.
---
plugins/devel/siege-engine.cpp | 83 +++++++++++++++++++++++++++++-----
1 file changed, 72 insertions(+), 11 deletions(-)
diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp
index d1e34ced9..b8a2f087b 100644
--- a/plugins/devel/siege-engine.cpp
+++ b/plugins/devel/siege-engine.cpp
@@ -1286,6 +1286,12 @@ static int proposeUnitHits(lua_State *L)
* Projectile hook
*/
+static const int offsets[8][2] = {
+ { -1, -1 }, { 0, -1 }, { 1, -1 },
+ { -1, 0 }, { 1, 0 },
+ { -1, 1 }, { 0, 1 }, { 1, 1 }
+};
+
struct projectile_hook : df::proj_itemst {
typedef df::proj_itemst interpose_base;
@@ -1293,6 +1299,9 @@ struct projectile_hook : df::proj_itemst {
{
target_pos = path.target;
+ // Debug
+ Maps::getTileOccupancy(path.goal)->bits.arrow_color = COLOR_LIGHTMAGENTA;
+
PathMetrics raytrace(path);
// Materialize map blocks, or the projectile will crash into them
@@ -1320,7 +1329,53 @@ struct projectile_hook : df::proj_itemst {
fall_threshold = std::min(fall_threshold, engine->fire_range.second);
}
- void aimAtArea(EngineInfo *engine)
+ void aimAtPoint(EngineInfo *engine, int skill, const ProjectilePath &path)
+ {
+ df::coord fail_target = path.goal;
+
+ orient_engine(engine->bld, path.goal);
+
+ // Debug
+ Maps::getTileOccupancy(path.goal)->bits.arrow_color = COLOR_LIGHTRED;
+
+ // Dabbling always hit in 7x7 area
+ if (skill < skill_rating::Novice)
+ {
+ fail_target.x += random_int(7)-3;
+ fail_target.y += random_int(7)-3;
+ aimAtPoint(engine, ProjectilePath(path.origin, fail_target));
+ return;
+ }
+
+ // Exact hit chance
+ float hit_chance = 1.04f - powf(0.8f, skill);
+
+ if (float(rand())/RAND_MAX < hit_chance)
+ {
+ aimAtPoint(engine, path);
+ return;
+ }
+
+ // Otherwise perturb
+ if (skill <= skill_rating::Proficient)
+ {
+ // 5x5
+ fail_target.x += random_int(5)-2;
+ fail_target.y += random_int(5)-2;
+ }
+ else
+ {
+ // 3x3
+ int idx = random_int(8);
+ fail_target.x += offsets[idx][0];
+ fail_target.y += offsets[idx][1];
+ }
+
+ ProjectilePath fail(path.origin, fail_target, path.fudge_delta, path.fudge_factor);
+ aimAtPoint(engine, fail);
+ }
+
+ void aimAtArea(EngineInfo *engine, int skill)
{
df::coord target, last_passable;
df::coord tbase = engine->target.first;
@@ -1343,7 +1398,7 @@ struct projectile_hook : df::proj_itemst {
if (raytrace.hits() && engine->isInRange(raytrace.goal_step))
{
- aimAtPoint(engine, path);
+ aimAtPoint(engine, skill, path);
return;
}
}
@@ -1351,7 +1406,7 @@ struct projectile_hook : df::proj_itemst {
if (!last_passable.isValid())
last_passable = target;
- aimAtPoint(engine, ProjectilePath(engine->center, last_passable));
+ aimAtPoint(engine, skill, ProjectilePath(engine->center, last_passable));
}
static int safeAimProjectile(lua_State *L)
@@ -1373,9 +1428,9 @@ struct projectile_hook : df::proj_itemst {
lua_call(L, 5, 1);
if (lua_isnil(L, -1))
- proj->aimAtArea(engine);
+ proj->aimAtArea(engine, skill);
else
- proj->aimAtPoint(engine, decode_path(L, -1, engine->center));
+ proj->aimAtPoint(engine, skill, decode_path(L, -1, engine->center));
return 0;
}
@@ -1396,13 +1451,19 @@ struct projectile_hook : df::proj_itemst {
int skill = getOperatorSkill(engine->bld, true);
- lua_pushcfunction(L, safeAimProjectile);
- lua_pushlightuserdata(L, this);
- lua_pushlightuserdata(L, engine);
- lua_pushinteger(L, skill);
+ // Dabbling can't aim
+ if (skill < skill_rating::Novice)
+ aimAtArea(engine, skill);
+ else
+ {
+ lua_pushcfunction(L, safeAimProjectile);
+ lua_pushlightuserdata(L, this);
+ lua_pushlightuserdata(L, engine);
+ lua_pushinteger(L, skill);
- if (!Lua::Core::SafeCall(out, 3, 0))
- aimAtArea(engine);
+ if (!Lua::Core::SafeCall(out, 3, 0))
+ aimAtArea(engine, skill);
+ }
switch (item->getType())
{
From 58fda716e6c1feee85ce7fb15d913c87444c3feb Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Sun, 16 Sep 2012 17:06:31 -0500
Subject: [PATCH 05/19] Explicit cast is required for MSVC.
---
plugins/devel/siege-engine.cpp | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp
index b8a2f087b..5e5cf5d74 100644
--- a/plugins/devel/siege-engine.cpp
+++ b/plugins/devel/siege-engine.cpp
@@ -1491,7 +1491,7 @@ struct projectile_hook : df::proj_itemst {
// Flight direction vector
df::coord dist = target_pos - origin_pos;
- float vx = dist.x, vy = dist.y, vz = fabs(dist.z);
+ float vx = dist.x, vy = dist.y, vz = fabs((float)dist.z);
normalize(vx, vy, vz);
int start_z = 0;
From c1e20c6f0565007c47cce9aaa06199a061dac20e Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 12:47:18 +0400
Subject: [PATCH 06/19] Follow changes to structures.
---
library/include/DataFuncs.h | 88 +++++++++++++++++++++++--------------
library/modules/Gui.cpp | 4 +-
library/xml | 2 +-
plugins/sort.cpp | 2 +-
4 files changed, 60 insertions(+), 36 deletions(-)
diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h
index 52039566c..01a798e34 100644
--- a/library/include/DataFuncs.h
+++ b/library/include/DataFuncs.h
@@ -85,7 +85,7 @@ namespace df {
static const bool is_method = true; \
};
-#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \
+#define INSTANTIATE_WRAPPERS2(Count, FArgs, Args, Loads) \
template struct function_wrapper { \
static const int num_args = Count; \
static void execute(lua_State *state, int base, void (*cb) FArgs) { Loads; INVOKE_VOID(cb Args); } \
@@ -105,79 +105,103 @@ namespace df {
LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \
};
+#define INSTANTIATE_WRAPPERS(Count, FArgs, OFArgs, Args, OArgs, Loads) \
+ INSTANTIATE_WRAPPERS2(Count, FArgs, Args, Loads) \
+ INSTANTIATE_WRAPPERS2(Count, OFArgs, OArgs, LOAD_OSTREAM(out); Loads)
+
#define FW_TARGSC
#define FW_TARGS
INSTANTIATE_RETURN_TYPE(())
-INSTANTIATE_WRAPPERS(0, (), (), ;)
-INSTANTIATE_WRAPPERS(0, (OSTREAM_ARG), (out), LOAD_OSTREAM(out);)
+INSTANTIATE_WRAPPERS(0, (), (OSTREAM_ARG), (), (out), ;)
#undef FW_TARGS
#undef FW_TARGSC
#define FW_TARGSC FW_TARGS,
#define FW_TARGS class A1
INSTANTIATE_RETURN_TYPE((A1))
-INSTANTIATE_WRAPPERS(1, (A1), (vA1), LOAD_ARG(A1);)
-INSTANTIATE_WRAPPERS(1, (OSTREAM_ARG,A1), (out,vA1), LOAD_OSTREAM(out); LOAD_ARG(A1);)
+INSTANTIATE_WRAPPERS(1, (A1), (OSTREAM_ARG,A1), (vA1), (out,vA1), LOAD_ARG(A1);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2
INSTANTIATE_RETURN_TYPE((A1,A2))
-INSTANTIATE_WRAPPERS(2, (A1,A2), (vA1,vA2), LOAD_ARG(A1); LOAD_ARG(A2);)
-INSTANTIATE_WRAPPERS(2, (OSTREAM_ARG,A1,A2), (out,vA1,vA2),
- LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2);)
+INSTANTIATE_WRAPPERS(2, (A1,A2), (OSTREAM_ARG,A1,A2), (vA1,vA2), (out,vA1,vA2),
+ LOAD_ARG(A1); LOAD_ARG(A2);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3
INSTANTIATE_RETURN_TYPE((A1,A2,A3))
-INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (vA1,vA2,vA3), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);)
-INSTANTIATE_WRAPPERS(3, (OSTREAM_ARG,A1,A2,A3), (out,vA1,vA2,vA3),
- LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);)
+INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (OSTREAM_ARG,A1,A2,A3), (vA1,vA2,vA3), (out,vA1,vA2,vA3),
+ LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4))
-INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4),
+INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (OSTREAM_ARG,A1,A2,A3,A4),
+ (vA1,vA2,vA3,vA4), (out,vA1,vA2,vA3,vA4),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);)
-INSTANTIATE_WRAPPERS(4, (OSTREAM_ARG,A1,A2,A3,A4), (out,vA1,vA2,vA3,vA4),
- LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5))
-INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5),
- LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);)
-INSTANTIATE_WRAPPERS(5, (OSTREAM_ARG,A1,A2,A3,A4,A5), (out,vA1,vA2,vA3,vA4,vA5),
- LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2);
- LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);)
+INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (OSTREAM_ARG,A1,A2,A3,A4,A5),
+ (vA1,vA2,vA3,vA4,vA5), (out,vA1,vA2,vA3,vA4,vA5),
+ LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
+ LOAD_ARG(A5);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6))
-INSTANTIATE_WRAPPERS(6, (A1,A2,A3,A4,A5,A6), (vA1,vA2,vA3,vA4,vA5,vA6),
- LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
- LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);)
-INSTANTIATE_WRAPPERS(6, (OSTREAM_ARG,A1,A2,A3,A4,A5,A6), (out,vA1,vA2,vA3,vA4,vA5,vA6),
- LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
- LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);)
+INSTANTIATE_WRAPPERS(6, (A1,A2,A3,A4,A5,A6), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6),
+ (vA1,vA2,vA3,vA4,vA5,vA6), (out,vA1,vA2,vA3,vA4,vA5,vA6),
+ LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
+ LOAD_ARG(A5); LOAD_ARG(A6);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7))
-INSTANTIATE_WRAPPERS(7, (A1,A2,A3,A4,A5,A6,A7), (vA1,vA2,vA3,vA4,vA5,vA6,vA7),
- LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
- LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);
- LOAD_ARG(A7);)
-INSTANTIATE_WRAPPERS(7, (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7),
- LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);
- LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7);)
+INSTANTIATE_WRAPPERS(7, (A1,A2,A3,A4,A5,A6,A7), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7),
+ (vA1,vA2,vA3,vA4,vA5,vA6,vA7), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7),
+ LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
+ LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8))
+INSTANTIATE_WRAPPERS(8, (A1,A2,A3,A4,A5,A6,A7,A8), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8),
+ (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8),
+ LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
+ LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);)
+#undef FW_TARGS
+
+#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9
+INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9))
+INSTANTIATE_WRAPPERS(9, (A1,A2,A3,A4,A5,A6,A7,A8,A9),
+ (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9),
+ (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9),
+ (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9),
+ LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
+ LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);
+ LOAD_ARG(A9);)
+#undef FW_TARGS
+
+#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10
+INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10))
+INSTANTIATE_WRAPPERS(10, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10),
+ (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10),
+ (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10),
+ (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10),
+ LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
+ LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);
+ LOAD_ARG(A9); LOAD_ARG(A10);)
+#undef FW_TARGS
+
+#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11
+INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11))
#undef FW_TARGS
#undef FW_TARGSC
#undef INSTANTIATE_WRAPPERS
+#undef INSTANTIATE_WRAPPERS2
#undef INVOKE_VOID
#undef INVOKE_RV
#undef LOAD_CLASS
diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp
index 91df14eaf..b0cfda670 100644
--- a/library/modules/Gui.cpp
+++ b/library/modules/Gui.cpp
@@ -54,7 +54,7 @@ using namespace DFHack;
#include "df/viewscreen_joblistst.h"
#include "df/viewscreen_unitlistst.h"
#include "df/viewscreen_itemst.h"
-#include "df/viewscreen_layerst.h"
+#include "df/viewscreen_layer.h"
#include "df/viewscreen_layer_workshop_profilest.h"
#include "df/viewscreen_layer_noblelistst.h"
#include "df/viewscreen_layer_overall_healthst.h"
@@ -95,7 +95,7 @@ using df::global::selection_rect;
using df::global::ui_menu_width;
using df::global::ui_area_map_width;
-static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx)
+static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx)
{
return virtual_cast(vector_get(layer->layer_objects,idx));
}
diff --git a/library/xml b/library/xml
index ee2b63a8f..a6b95f1c4 160000
--- a/library/xml
+++ b/library/xml
@@ -1 +1 @@
-Subproject commit ee2b63a8ffdbce66489148ca2a9803db1d0b9090
+Subproject commit a6b95f1c42991e485f7e0bb5d029a5eca14ce9ae
diff --git a/plugins/sort.cpp b/plugins/sort.cpp
index ff51fc773..4b2bf7bbd 100644
--- a/plugins/sort.cpp
+++ b/plugins/sort.cpp
@@ -228,7 +228,7 @@ static void sort_null_first(vector ¶meters)
vector_insert_at(parameters, 0, std::string("(vector_get(layer->layer_objects,idx));
}
From f2fde21b10c087fd95948a5f40ef972ac6688718 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 14:45:22 +0400
Subject: [PATCH 07/19] Implement a slightly more sensible aiming AI in siege
engine.
---
plugins/devel/siege-engine.cpp | 76 +++++++++++-
plugins/lua/siege-engine.lua | 208 +++++++++++++++++++++++++++++++--
2 files changed, 270 insertions(+), 14 deletions(-)
diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp
index 5e5cf5d74..3b95aba35 100644
--- a/plugins/devel/siege-engine.cpp
+++ b/plugins/devel/siege-engine.cpp
@@ -112,7 +112,7 @@ static bool is_in_range(const coord_range &target, df::coord pos)
static std::pair get_engine_range(df::building_siegeenginest *bld)
{
if (bld->type == siegeengine_type::Ballista)
- return std::make_pair(0, 200);
+ return std::make_pair(1, 200);
else
return std::make_pair(30, 100);
}
@@ -291,7 +291,7 @@ static EngineInfo *find_engine(df::building *bld, bool create = false)
);
obj->is_catapult = (ebld->type == siegeengine_type::Catapult);
obj->proj_speed = 2;
- obj->hit_delay = 3;
+ obj->hit_delay = obj->is_catapult ? 2 : -1;
obj->fire_range = get_engine_range(ebld);
obj->ammo_vector_id = job_item_vector_id::BOULDER;
@@ -1107,6 +1107,9 @@ struct UnitPath {
float time = unit->counters.job_counter+0.5f;
float speed = Units::computeMovementSpeed(unit)/100.0f;
+ if (unit->counters.unconscious > 0)
+ time += unit->counters.unconscious;
+
for (size_t i = 0; i < upath.size(); i++)
{
df::coord new_pos = upath[i];
@@ -1282,6 +1285,74 @@ static int proposeUnitHits(lua_State *L)
return 1;
}
+static int computeNearbyWeight(lua_State *L)
+{
+ auto engine = find_engine(L, 1);
+ luaL_checktype(L, 2, LUA_TTABLE);
+ luaL_checktype(L, 3, LUA_TTABLE);
+ const char *fname = luaL_optstring(L, 4, "nearby_weight");
+
+ std::vector units;
+ std::vector weights;
+
+ lua_pushnil(L);
+
+ while (lua_next(L, 3))
+ {
+ df::unit *unit;
+ if (lua_isnumber(L, -2))
+ unit = df::unit::find(lua_tointeger(L, -2));
+ else
+ unit = Lua::CheckDFObject(L, -2);
+ if (!unit)
+ continue;
+ units.push_back(UnitPath::get(unit));
+ weights.push_back(lua_tonumber(L, -1));
+ lua_pop(L, 1);
+ }
+
+ lua_pushnil(L);
+
+ while (lua_next(L, 2))
+ {
+ Lua::StackUnwinder frame(L, 1);
+
+ lua_getfield(L, frame[1], "unit");
+ df::unit *unit = Lua::CheckDFObject(L, -1);
+
+ lua_getfield(L, frame[1], "time");
+ float time = luaL_checknumber(L, lua_gettop(L));
+
+ df::coord pos;
+
+ lua_getfield(L, frame[1], "pos");
+ if (lua_isnil(L, -1))
+ {
+ if (!unit) luaL_error(L, "either unit or pos is required");
+ pos = UnitPath::get(unit)->posAtTime(time);
+ }
+ else
+ Lua::CheckDFAssign(L, &pos, -1);
+
+ float sum = 0.0f;
+
+ for (size_t i = 0; i < units.size(); i++)
+ {
+ if (units[i]->unit == unit)
+ continue;
+
+ auto diff = units[i]->posAtTime(time) - pos;
+ float dist = 1 + sqrtf(diff.x*diff.x + diff.y*diff.y + diff.z*diff.z);
+ sum += weights[i]/(dist*dist);
+ }
+
+ lua_pushnumber(L, sum);
+ lua_setfield(L, frame[1], fname);
+ }
+
+ return 0;
+}
+
/*
* Projectile hook
*/
@@ -1698,6 +1769,7 @@ DFHACK_PLUGIN_LUA_COMMANDS {
DFHACK_LUA_COMMAND(traceUnitPath),
DFHACK_LUA_COMMAND(unitPosAtTime),
DFHACK_LUA_COMMAND(proposeUnitHits),
+ DFHACK_LUA_COMMAND(computeNearbyWeight),
DFHACK_LUA_END
};
diff --git a/plugins/lua/siege-engine.lua b/plugins/lua/siege-engine.lua
index 89c47659d..33e120feb 100644
--- a/plugins/lua/siege-engine.lua
+++ b/plugins/lua/siege-engine.lua
@@ -8,37 +8,221 @@ local _ENV = mkmodule('plugins.siege-engine')
* clearTargetArea(building)
* setTargetArea(building, point1, point2) -> true/false
---]]
+ * isLinkedToPile(building,pile) -> true/false
+ * getStockpileLinks(building) -> {pile}
+ * addStockpileLink(building,pile) -> true/false
+ * removeStockpileLink(building,pile) -> true/false
+
+ * saveWorkshopProfile(building) -> profile
+
+ * getAmmoItem(building) -> item_type
+ * setAmmoItem(building,item_type) -> true/false
+
+ * isPassableTile(pos) -> true/false
+ * isTreeTile(pos) -> true/false
+ * isTargetableTile(pos) -> true/false
+
+ * getTileStatus(building,pos) -> 'invalid/ok/out_of_range/blocked/semiblocked'
+ * paintAimScreen(building,view_pos_xyz,left_top_xy,size_xy)
+
+ * canTargetUnit(unit) -> true/false
+
+ proj_info = { target = pos, [delta = float/pos], [factor = int] }
+
+ * projPosAtStep(building,proj_info,step) -> pos
+ * projPathMetrics(building,proj_info) -> {
+ hit_type = 'wall/floor/ceiling/map_edge/tree',
+ collision_step = int,
+ collision_z_step = int,
+ goal_distance = int,
+ goal_step = int/nil,
+ goal_z_step = int/nil,
+ status = 'ok/out_of_range/blocked'
+ }
+
+ * adjustToTarget(building,pos) -> pos,ok=true/false
+
+ * traceUnitPath(unit) -> { {x=int,y=int,z=int[,from=time][,to=time]} }
+ * unitPosAtTime(unit, time) -> pos
+
+ * proposeUnitHits(building) -> { {
+ pos=pos, unit=unit, time=float, dist=int,
+ [lmargin=float,] [rmargin=float,]
+ } }
+
+ * computeNearbyWeight(building,hits,{[id/unit]=score}[,fname])
+
+]]
Z_STEP_COUNT = 15
Z_STEP = 1/31
+function getMetrics(engine, path)
+ path.metrics = path.metrics or projPathMetrics(engine, path)
+ return path.metrics
+end
+
function findShotHeight(engine, target)
local path = { target = target, delta = 0.0 }
- if projPathMetrics(engine, path).goal_step then
+ if getMetrics(engine, path).goal_step then
return path
end
- for i = 1,Z_STEP_COUNT do
- path.delta = i*Z_STEP
- if projPathMetrics(engine, path).goal_step then
- return path
+ local tpath = { target = target, delta = Z_STEP_COUNT*Z_STEP }
+
+ if getMetrics(engine, tpath).goal_step then
+ for i = 1,Z_STEP_COUNT-1 do
+ path = { target = target, delta = i*Z_STEP }
+ if getMetrics(engine, path).goal_step then
+ return path
+ end
+ end
+
+ return tpath
+ end
+
+ tpath = { target = target, delta = -Z_STEP_COUNT*Z_STEP }
+
+ if getMetrics(engine, tpath).goal_step then
+ for i = 1,Z_STEP_COUNT-1 do
+ path = { target = target, delta = -i*Z_STEP }
+ if getMetrics(engine, path).goal_step then
+ return path
+ end
+ end
+
+ return tpath
+ end
+end
+
+function findReachableTargets(engine, targets)
+ local reachable = {}
+ for _,tgt in ipairs(targets) do
+ tgt.path = findShotHeight(engine, tgt.pos)
+ if tgt.path then
+ table.insert(reachable, tgt)
+ end
+ end
+ return reachable
+end
+
+recent_targets = recent_targets or {}
+
+if dfhack.is_core_context then
+ dfhack.onStateChange[_ENV] = function(code)
+ if code == SC_MAP_LOADED then
+ recent_targets = {}
+ end
+ end
+end
+
+function saveRecent(unit)
+ local id = unit.id
+ local tgt = recent_targets
+ tgt[id] = (tgt[id] or 0) + 1
+ dfhack.timeout(3, 'days', function()
+ tgt[id] = math.max(0, tgt[id]-1)
+ end)
+end
+
+function getBaseUnitWeight(unit)
+ if dfhack.units.isCitizen(unit) then
+ return -10
+ elseif unit.flags1.diplomat or unit.flags1.merchant then
+ return -2
+ elseif unit.flags1.tame and unit.civ_id == df.global.ui.civ_id then
+ return -1
+ else
+ local rv = 1
+ if unit.flags1.marauder then rv = rv + 0.5 end
+ if unit.flags1.active_invader then rv = rv + 1 end
+ if unit.flags1.invader_origin then rv = rv + 1 end
+ if unit.flags1.invades then rv = rv + 1 end
+ if unit.flags1.hidden_ambusher then rv = rv + 1 end
+ return rv
+ end
+end
+
+function getUnitWeight(unit)
+ local base = getBaseUnitWeight(unit)
+ return base * math.pow(0.7, recent_targets[unit.id] or 0)
+end
+
+function unitWeightCache()
+ local cache = {}
+ return cache, function(unit)
+ local id = unit.id
+ cache[id] = cache[id] or getUnitWeight(unit)
+ return cache[id]
+ end
+end
+
+function scoreTargets(engine, reachable)
+ local ucache, get_weight = unitWeightCache()
+
+ for _,tgt in ipairs(reachable) do
+ tgt.score = get_weight(tgt.unit)
+ if tgt.lmargin and tgt.lmargin < 3 then
+ tgt.score = tgt.score * tgt.lmargin / 3
+ end
+ if tgt.rmargin and tgt.rmargin < 3 then
+ tgt.score = tgt.score * tgt.rmargin / 3
end
+ end
+
+ computeNearbyWeight(engine, reachable, ucache)
+
+ for _,tgt in ipairs(reachable) do
+ tgt.score = (tgt.score + tgt.nearby_weight*0.7) * math.pow(0.995, tgt.time/3)
+ end
+
+ table.sort(reachable, function(a,b)
+ return a.score > b.score or (a.score == b.score and a.time < b.time)
+ end)
+end
+
+function pickUniqueTargets(reachable)
+ local unique = {}
- path.delta = -i*Z_STEP
- if projPathMetrics(engine, path).goal_step then
- return path
+ if #reachable > 0 then
+ local pos_table = {}
+ local first_score = reachable[1].score
+
+ for i,tgt in ipairs(reachable) do
+ if tgt.score < 0 or tgt.score < 0.1*first_score then
+ break
+ end
+ local x,y,z = pos2xyz(tgt.pos)
+ local key = x..':'..y..':'..z
+ if pos_table[key] then
+ table.insert(pos_table[key].units, tgt.unit)
+ else
+ table.insert(unique, tgt)
+ pos_table[key] = tgt
+ tgt.units = { tgt.unit }
+ end
end
end
+
+ return unique
end
function doAimProjectile(engine, item, target_min, target_max, skill)
print(item, df.skill_rating[skill])
+
local targets = proposeUnitHits(engine)
- if #targets > 0 then
- local rnd = math.random(#targets)
- return findShotHeight(engine, targets[rnd].pos)
+ local reachable = findReachableTargets(engine, targets)
+ scoreTargets(engine, reachable)
+ local unique = pickUniqueTargets(reachable)
+
+ if #unique > 0 then
+ local cnt = math.max(math.min(#unique,5), math.min(10, math.floor(#unique/2)))
+ local rnd = math.random(cnt)
+ for _,u in ipairs(unique[rnd].units) do
+ saveRecent(u)
+ end
+ return unique[rnd].path
end
end
From 82e870c8ddc9b64370c3828d1d4807306ac72887 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 14:59:59 +0400
Subject: [PATCH 08/19] Move siege engine out of devel.
---
NEWS | 10 ++++++----
plugins/CMakeLists.txt | 1 +
plugins/devel/CMakeLists.txt | 1 -
plugins/{devel => }/siege-engine.cpp | 0
scripts/gui/siege-engine.lua | 1 +
5 files changed, 8 insertions(+), 5 deletions(-)
rename plugins/{devel => }/siege-engine.cpp (100%)
diff --git a/NEWS b/NEWS
index 43707f9a7..4294bedb4 100644
--- a/NEWS
+++ b/NEWS
@@ -53,9 +53,11 @@ DFHack v0.34.11-r2 (UNRELEASED)
When activated, implements a pressure plate modification that detects power in gear
boxes built on the four adjacent N/S/W/E tiles. The gui/power-meter script implements
the build configuration UI.
- New Siege Engine plugin (INCOMPLETE):
+ New Siege Engine plugin:
When enabled and configured via gui/siege-engine, allows aiming siege engines
- at a designated rectangular area across Z levels. Also supports loading catapults
- with non-boulder projectiles, taking from a stockpile, and restricting operator
- skill range, like with ordinary workshops.
+ at a designated rectangular area with 360 degree fire range and across Z levels.
+ Also supports loading catapults with non-boulder projectiles, taking from a stockpile,
+ 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.
diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt
index 6e207385e..8511d86c6 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -119,6 +119,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua)
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)
# not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp)
endif()
diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt
index 39e8f7b60..134d5cb67 100644
--- a/plugins/devel/CMakeLists.txt
+++ b/plugins/devel/CMakeLists.txt
@@ -18,7 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp)
DFHACK_PLUGIN(rprobe rprobe.cpp)
DFHACK_PLUGIN(nestboxes nestboxes.cpp)
DFHACK_PLUGIN(vshook vshook.cpp)
-DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
IF(UNIX)
DFHACK_PLUGIN(ref-index ref-index.cpp)
ENDIF()
diff --git a/plugins/devel/siege-engine.cpp b/plugins/siege-engine.cpp
similarity index 100%
rename from plugins/devel/siege-engine.cpp
rename to plugins/siege-engine.cpp
diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua
index 47043cbb1..7a76d7673 100644
--- a/scripts/gui/siege-engine.lua
+++ b/scripts/gui/siege-engine.lua
@@ -21,6 +21,7 @@ local item_choices = {
{ caption = 'trap components', item_type = df.item_type.TRAPCOMP },
{ caption = 'bins', item_type = df.item_type.BIN },
{ caption = 'barrels', item_type = df.item_type.BARREL },
+ { caption = 'cages', item_type = df.item_type.CAGE },
{ caption = 'anything', item_type = -1 },
}
From 613063cef4d87b3b4307144b85da60dc40daceb3 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 17:19:24 +0400
Subject: [PATCH 09/19] Add a tweak to fix subtractDimension of small amounts.
---
dfhack.init-example | 7 ++++
plugins/tweak.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 98 insertions(+)
diff --git a/dfhack.init-example b/dfhack.init-example
index a9b69b826..b8b53cad7 100644
--- a/dfhack.init-example
+++ b/dfhack.init-example
@@ -59,6 +59,9 @@ keybinding add Alt-L@dwarfmode/LookAround gui/liquids
# machine power sensitive pressure plate construction
keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter
+# siege engine control
+keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine
+
############################
# UI and game logic tweaks #
############################
@@ -79,3 +82,7 @@ tweak stable-temp
# capping the rate to no less than 1 degree change per 500 frames
# Note: will also cause stuff to melt faster in magma etc
tweak fast-heat 500
+
+# stop stacked liquid/bar/thread/cloth items from lasting forever
+# if used in reactions that use only a fraction of the dimension.
+tweak fix-dimensions
diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp
index bebc346c5..4fef285f9 100644
--- a/plugins/tweak.cpp
+++ b/plugins/tweak.cpp
@@ -33,6 +33,10 @@
#include "df/ui_build_selector.h"
#include "df/building_trapst.h"
#include "df/item_actual.h"
+#include "df/item_liquipowder.h"
+#include "df/item_barst.h"
+#include "df/item_threadst.h"
+#include "df/item_clothst.h"
#include "df/contaminant.h"
#include
@@ -93,6 +97,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector stack_size <= 1) return;
+ int rem = delta % dim;
+ if (rem == 0) return;
+ // If destroys, pass through
+ int intv = delta / dim;
+ if (intv >= self->stack_size) return;
+ // Subtract int part
+ delta = rem;
+ self->stack_size -= intv;
+ if (self->stack_size <= 1) return;
+
+ // If kills the item or cannot split, round up.
+ if (!self->flags.bits.in_inventory || !Items::getContainer(self))
+ {
+ delta = dim;
+ return;
+ }
+
+ // Otherwise split the stack
+ color_ostream_proxy out(Core::getInstance().getConsole());
+ out.print("fix-dimensions: splitting stack #%d for delta %d.\n", self->id, delta);
+
+ auto copy = self->splitStack(self->stack_size-1, true);
+ if (copy) copy->categorize(true);
+}
+
+struct dimension_lqp_hook : df::item_liquipowder {
+ typedef df::item_liquipowder interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta))
+ {
+ correct_dimension(this, delta, dimension);
+ return INTERPOSE_NEXT(subtractDimension)(delta);
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(dimension_lqp_hook, subtractDimension);
+
+struct dimension_bar_hook : df::item_barst {
+ typedef df::item_barst interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta))
+ {
+ correct_dimension(this, delta, dimension);
+ return INTERPOSE_NEXT(subtractDimension)(delta);
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(dimension_bar_hook, subtractDimension);
+
+struct dimension_thread_hook : df::item_threadst {
+ typedef df::item_threadst interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta))
+ {
+ correct_dimension(this, delta, dimension);
+ return INTERPOSE_NEXT(subtractDimension)(delta);
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(dimension_thread_hook, subtractDimension);
+
+struct dimension_cloth_hook : df::item_clothst {
+ typedef df::item_clothst interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta))
+ {
+ correct_dimension(this, delta, dimension);
+ return INTERPOSE_NEXT(subtractDimension)(delta);
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(dimension_cloth_hook, subtractDimension);
+
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters)
{
if (vector_get(parameters, 1) == "disable")
@@ -491,6 +575,13 @@ static command_result tweak(color_ostream &out, vector ¶meters)
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTemperature), parameters);
enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, adjustTemperature), parameters);
}
+ else if (cmd == "fix-dimensions")
+ {
+ enable_hook(out, INTERPOSE_HOOK(dimension_lqp_hook, subtractDimension), parameters);
+ enable_hook(out, INTERPOSE_HOOK(dimension_bar_hook, subtractDimension), parameters);
+ enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters);
+ enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters);
+ }
else
return CR_WRONG_USAGE;
From 36e44c682cc2cecb552eca8dfc75ad1a436086cc Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 21:15:51 +0400
Subject: [PATCH 10/19] Add a plugin implementing 'add spatter to item'
reactions.
---
NEWS | 5 +-
library/include/modules/Materials.h | 5 +
library/modules/Materials.cpp | 13 +
library/xml | 2 +-
plugins/CMakeLists.txt | 4 +
plugins/add-spatter.cpp | 409 +++++++++++++++++++++
plugins/cleaners.cpp | 13 +-
plugins/raw/entity_default.diff | 29 ++
plugins/raw/material_template_default.diff | 10 +
plugins/raw/reaction_spatter.txt | 41 +++
10 files changed, 526 insertions(+), 5 deletions(-)
create mode 100644 plugins/add-spatter.cpp
create mode 100644 plugins/raw/entity_default.diff
create mode 100644 plugins/raw/material_template_default.diff
create mode 100644 plugins/raw/reaction_spatter.txt
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]
From 2c0a8a9544f81834b0a6e7ac9bf78db8aa5008d1 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 00:24:59 +0400
Subject: [PATCH 11/19] Tweak new plugin descriptions in the NEWS document.
---
NEWS | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/NEWS b/NEWS
index fdc69ac53..68551d385 100644
--- a/NEWS
+++ b/NEWS
@@ -44,23 +44,30 @@ DFHack v0.34.11-r2 (UNRELEASED)
New Dwarf Manipulator plugin:
Open the unit list, and press 'l' to access a Dwarf Therapist like UI in the game.
New Steam Engine plugin:
- Dwarven Water Reactors don't make any sense whatsoever, so this is a potential
- replacement for those concerned by it. The plugin detects if a workshop with a
+ Dwarven Water Reactors don't make any sense whatsoever and cause lag, so this may be
+ a replacement for those concerned by it. The plugin detects if a workshop with a
certain name is in the raws used by the current world, and provides the necessary
behavior. See hack/raw/*_steam_engine.txt for the necessary raw definitions.
- Note: Stuff like animal treadmills might be more period, but can't be done with dfhack.
+ Note: Stuff like animal treadmills might be more period, but absolutely can't be
+ done with tools dfhack has access to.
New Power Meter plugin:
When activated, implements a pressure plate modification that detects power in gear
boxes built on the four adjacent N/S/W/E tiles. The gui/power-meter script implements
- the build configuration UI.
+ the necessary build configuration UI.
New Siege Engine plugin:
When enabled and configured via gui/siege-engine, allows aiming siege engines
- at a designated rectangular area with 360 degree fire range and across Z levels.
+ at a designated rectangular area with 360 degree fire range and across Z levels;
+ this works by rewriting the projectile trajectory immediately after it appears.
Also supports loading catapults with non-boulder projectiles, taking from a stockpile,
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.
+ 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 above ground :(. Again, things like making siegers bring their own engines
+ is totally out of the scope of dfhack and can only be done by Toady.
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'.
+ Intended to give some use to all those giant cave spider poison barrels brought
+ by the caravans.
+
From be928a9dc537290522577cde41211637f3b6f165 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 10:40:14 +0400
Subject: [PATCH 12/19] Fix a data structure integrity bug in
VMethodInterposeLinkBase.
This causes assertion failure and abort later on.
---
library/VTableInterpose.cpp | 2 ++
1 file changed, 2 insertions(+)
diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp
index 583ef5184..046425653 100644
--- a/library/VTableInterpose.cpp
+++ b/library/VTableInterpose.cpp
@@ -438,6 +438,8 @@ void VMethodInterposeLinkBase::remove()
if (next)
prev->child_next.insert(next);
+ else
+ prev->child_hosts.insert(host);
}
}
From d70a79deb99a3c4ae6458317ee1111928f3db401 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 13:11:11 +0400
Subject: [PATCH 13/19] Follow changes in XML defs.
---
library/modules/Gui.cpp | 14 +++++++-------
library/xml | 2 +-
plugins/add-spatter.cpp | 2 +-
3 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp
index b0cfda670..1662f4467 100644
--- a/library/modules/Gui.cpp
+++ b/library/modules/Gui.cpp
@@ -332,9 +332,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_military)
focus += "/" + enum_item_key(screen->page);
int cur_list;
- if (list1->bright) cur_list = 0;
- else if (list2->bright) cur_list = 1;
- else if (list3->bright) cur_list = 2;
+ if (list1->active) cur_list = 0;
+ else if (list2->active) cur_list = 1;
+ else if (list3->active) cur_list = 2;
else return;
switch (screen->page)
@@ -420,7 +420,7 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_assigntrade)
if (unsigned(list_idx) >= num_lists)
return;
- if (list1->bright)
+ if (list1->active)
focus += "/Groups";
else
focus += "/Items";
@@ -458,10 +458,10 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile)
focus += "/On";
- if (list2->bright || list3->bright || screen->list_ids.empty()) {
+ if (list2->active || list3->active || screen->list_ids.empty()) {
focus += "/" + enum_item_key(screen->cur_list);
- if (list3->bright)
+ if (list3->active)
focus += (screen->item_names.empty() ? "/None" : "/Item");
}
}
@@ -844,7 +844,7 @@ static df::item *getAnyItem(df::viewscreen *top)
{
auto list1 = getLayerList(screen, 0);
auto list2 = getLayerList(screen, 1);
- if (!list1 || !list2 || !list2->bright)
+ if (!list1 || !list2 || !list2->active)
return NULL;
int list_idx = vector_get(screen->visible_lists, list1->cursor, (int16_t)-1);
diff --git a/library/xml b/library/xml
index 260ff4a1d..8a78bfa21 160000
--- a/library/xml
+++ b/library/xml
@@ -1 +1 @@
-Subproject commit 260ff4a1ddcfd54d0143aa6d908a93c4ff709c87
+Subproject commit 8a78bfa218817765b0a80431e0cf25435ffb2179
diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp
index ed5f47f7b..dda4ca2ba 100644
--- a/plugins/add-spatter.cpp
+++ b/plugins/add-spatter.cpp
@@ -167,7 +167,7 @@ static void index_items(item_table &table, df::job *job, ReactionInfo *info)
int ridx = iitem->contains[k];
auto reag = info->react->reagents[ridx];
- if (reag->matches(contents[j], info->react, iitem->reaction_id))
+ if (reag->matchesChild(contents[j], info->react, iitem->reaction_id))
table[ridx].push_back(contents[j]);
}
}
From f2e7ee4756813e1f60043d47942d18d4b7b814b5 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 13:15:25 +0400
Subject: [PATCH 14/19] Tweak the add spatter plugin.
---
plugins/add-spatter.cpp | 42 +++++++--
plugins/raw/reaction_spatter.txt | 141 ++++++++++++++++++++++++++-----
2 files changed, 155 insertions(+), 28 deletions(-)
diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp
index dda4ca2ba..35ea11ef5 100644
--- a/plugins/add-spatter.cpp
+++ b/plugins/add-spatter.cpp
@@ -7,6 +7,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -121,20 +122,22 @@ static void find_material(int *type, int *index, df::item *input, MaterialSource
}
}
-static bool has_contaminant(df::item_actual *item, int type, int index)
+static int has_contaminant(df::item_actual *item, int type, int index)
{
auto cont = item->contaminants;
if (!cont)
- return false;
+ return 0;
+
+ int size = 0;
for (size_t i = 0; i < cont->size(); i++)
{
auto cur = (*cont)[i];
if (cur->mat_type == type && cur->mat_index == index)
- return true;
+ size += cur->size;
}
- return false;
+ return size;
}
/*
@@ -209,7 +212,7 @@ struct item_hook : df::item_constructed {
find_material(&mattype, &matindex, material, product.material);
- if (mattype < 0 || has_contaminant(this, mattype, matindex))
+ if (mattype < 0 || has_contaminant(this, mattype, matindex) >= 50)
return false;
}
@@ -253,15 +256,36 @@ struct product_hook : improvement_product {
if (object && (material || !product->material.reagent))
{
+ using namespace df::enums::improvement_type;
+
int mattype, matindex;
find_material(&mattype, &matindex, material, product->material);
+ df::matter_state state = matter_state::Liquid;
+
+ switch (improvement_type)
+ {
+ case COVERED:
+ if (flags.is_set(reaction_product_improvement_flags::GLAZED))
+ state = matter_state::Solid;
+ break;
+ case BANDS:
+ state = matter_state::Paste;
+ break;
+ case SPIKES:
+ state = matter_state::Powder;
+ break;
+ default:
+ break;
+ }
+
+ int rating = unit ? Units::getEffectiveSkill(unit, df::job_skill(skill)) : 0;
+ int size = int(probability*(1.0f + 0.06f*rating)); // +90% at legendary
+
object->addContaminant(
- mattype, matindex,
- matter_state::Liquid, // TODO: heuristics or by reagent name
+ mattype, matindex, state,
object->getTemperature(),
- probability, // used as size
- -1,
+ size, -1,
0x8000 // not washed by water, and 'clean items' safe.
);
}
diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt
index b31d82fa0..229e531c8 100644
--- a/plugins/raw/reaction_spatter.txt
+++ b/plugins/raw/reaction_spatter.txt
@@ -4,38 +4,141 @@ reaction_spatter
Reaction name must start with 'SPATTER_ADD_':
-[REACTION:SPATTER_ADD_EXTRACT_WEAPON]
- [NAME:cover weapon with extract]
- [BUILDING:CRAFTSMAN:CUSTOM_ALT_V]
- [SKILL:DYER]
+[REACTION:SPATTER_ADD_OBJECT_LIQUID]
+ [NAME:coat object with liquid]
[ADVENTURE_MODE_ENABLED]
- [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE]
- [MIN_DIMENSION:10]
+ [SKILL:WAX_WORKING]
+ [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE]
+ [MIN_DIMENSION:150]
+ [REAGENT:extract container:1:NONE:NONE:NONE:NONE]
+ [CONTAINS:extract]
+ [PRESERVE_REAGENT]
+ [DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
+ The object to improve must be after the input mat, so that it is known:
+ [REAGENT:object:1:NONE:NONE:NONE:NONE]
+ [PRESERVE_REAGENT]
+ Need some excuse why the spatter is water-resistant:
+ [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:FAT][UNROTTEN]
+ The probability is used as spatter size; Legendary gives +90%:
+ COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
+ [IMPROVEMENT:800:object:BANDS:GET_MATERIAL_FROM_REAGENT:extract:NONE]
+
+[REACTION:SPATTER_ADD_WEAPON_EXTRACT]
+ [NAME:coat weapon with extract]
+ [BUILDING:CRAFTSMAN:NONE]
+ [SKILL:WAX_WORKING]
+ [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE]
+ [MIN_DIMENSION:150]
[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:
+ The object to improve must be after the input mat, so that it is known:
[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]
+ Need some excuse why the spatter is water-resistant:
+ [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
+ The probability is used as spatter size; Legendary gives +90%:
+ COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
+ [IMPROVEMENT:800: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:SPATTER_ADD_AMMO_EXTRACT]
+ [NAME:coat ammo with extract]
+ [BUILDING:CRAFTSMAN:NONE]
+ [SKILL:WAX_WORKING]
+ [REAGENT:extract:50:LIQUID_MISC:NONE:NONE:NONE]
+ [MIN_DIMENSION:50]
+ [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 after the input mat, so that it is known:
+ [REAGENT:object:1:AMMO:NONE:NONE:NONE]
+ [PRESERVE_REAGENT]
+ Need some excuse why the spatter is water-resistant:
+ [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
+ The probability is used as spatter size; Legendary gives +90%:
+ COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
+ [IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
+
+[REACTION:SPATTER_ADD_WEAPON_GCS]
+ [NAME:coat weapon with GCS venom]
+ [BUILDING:CRAFTSMAN:NONE]
+ [SKILL:WAX_WORKING]
+ [REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON]
+ [MIN_DIMENSION:150]
+ [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 after the input mat, so that it is known:
+ [REAGENT:object:1:WEAPON:NONE:NONE:NONE]
+ [PRESERVE_REAGENT]
+ Need some excuse why the spatter is water-resistant:
+ [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
+ The probability is used as spatter size; Legendary gives +90%:
+ COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
+ [IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
+
+[REACTION:SPATTER_ADD_AMMO_GCS]
+ [NAME:coat ammo with GCS venom]
+ [BUILDING:CRAFTSMAN:NONE]
+ [SKILL:WAX_WORKING]
+ [REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON]
+ [MIN_DIMENSION:50]
+ [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 after the input mat, so that it is known:
+ [REAGENT:object:1:AMMO:NONE:NONE:NONE]
+ [PRESERVE_REAGENT]
+ Need some excuse why the spatter is water-resistant:
+ [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
+ The probability is used as spatter size; Legendary gives +90%:
+ COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
+ [IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
+
+[REACTION:SPATTER_ADD_WEAPON_GDS]
+ [NAME:coat weapon with GDS venom]
+ [BUILDING:CRAFTSMAN:NONE]
+ [SKILL:WAX_WORKING]
+ [REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON]
+ [MIN_DIMENSION:150]
+ [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 after the input mat, so that it is known:
+ [REAGENT:object:1:WEAPON:NONE:NONE:NONE]
+ [PRESERVE_REAGENT]
+ Need some excuse why the spatter is water-resistant:
+ [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
+ The probability is used as spatter size; Legendary gives +90%:
+ COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
+ [IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
+
+[REACTION:SPATTER_ADD_AMMO_GDS]
+ [NAME:coat ammo with GDS venom]
+ [BUILDING:CRAFTSMAN:NONE]
+ [SKILL:WAX_WORKING]
+ [REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON]
+ [MIN_DIMENSION:50]
[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:
+ The object to improve must be after the input mat, so that it is known:
[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]
+ Need some excuse why the spatter is water-resistant:
+ [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN]
+ The probability is used as spatter size; Legendary gives +90%:
+ COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
+ [IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
From a7998f71a2ee95d2d21f34468761118fd6b8585f Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 17:39:37 +0400
Subject: [PATCH 15/19] Add a tweak workaround for the issue with container
reactions in advmode.
---
NEWS | 2 +
dfhack.init-example | 4 ++
plugins/raw/reaction_spatter.txt | 2 +-
plugins/tweak.cpp | 71 ++++++++++++++++++++++++++++++++
4 files changed, 78 insertions(+), 1 deletion(-)
diff --git a/NEWS b/NEWS
index 68551d385..22f64d7d6 100644
--- a/NEWS
+++ b/NEWS
@@ -25,6 +25,8 @@ DFHack v0.34.11-r2 (UNRELEASED)
- tweak readable-build-plate: fix unreadable truncation in unit pressure plate build ui.
- tweak stable-temp: fixes bug 6012; may improve FPS by 50-100% on a slow item-heavy fort.
- tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster.
+ - tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc.
+ - tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode.
New scripts:
- fixnaked: removes thoughts about nakedness.
- setfps: set FPS cap at runtime, in case you want slow motion or speed-up.
diff --git a/dfhack.init-example b/dfhack.init-example
index b8b53cad7..83c3641b6 100644
--- a/dfhack.init-example
+++ b/dfhack.init-example
@@ -86,3 +86,7 @@ tweak fast-heat 500
# stop stacked liquid/bar/thread/cloth items from lasting forever
# if used in reactions that use only a fraction of the dimension.
tweak fix-dimensions
+
+# make reactions requiring containers usable in advmode - the issue is
+# that the screen asks for those reagents to be selected directly
+tweak advmode-contained
diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt
index 229e531c8..085be7fdd 100644
--- a/plugins/raw/reaction_spatter.txt
+++ b/plugins/raw/reaction_spatter.txt
@@ -21,7 +21,7 @@ Reaction name must start with 'SPATTER_ADD_':
[REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:FAT][UNROTTEN]
The probability is used as spatter size; Legendary gives +90%:
COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder
- [IMPROVEMENT:800:object:BANDS:GET_MATERIAL_FROM_REAGENT:extract:NONE]
+ [IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
[REACTION:SPATTER_ADD_WEAPON_EXTRACT]
[NAME:coat weapon with extract]
diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp
index 4fef285f9..fb286e0d7 100644
--- a/plugins/tweak.cpp
+++ b/plugins/tweak.cpp
@@ -29,6 +29,7 @@
#include "df/criminal_case.h"
#include "df/unit_inventory_item.h"
#include "df/viewscreen_dwarfmodest.h"
+#include "df/viewscreen_layer_unit_actionst.h"
#include "df/squad_order_trainst.h"
#include "df/ui_build_selector.h"
#include "df/building_trapst.h"
@@ -38,6 +39,10 @@
#include "df/item_threadst.h"
#include "df/item_clothst.h"
#include "df/contaminant.h"
+#include "df/layer_object.h"
+#include "df/reaction.h"
+#include "df/reaction_reagent_itemst.h"
+#include "df/reaction_reagent_flags.h"
#include
@@ -100,6 +105,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector *input))
+ {
+ auto old_reaction = cur_reaction;
+ auto old_reagent = reagent;
+
+ INTERPOSE_NEXT(feed)(input);
+
+ if (cur_reaction && (cur_reaction != old_reaction || reagent != old_reagent))
+ {
+ old_reagent = reagent;
+
+ // Skip reagents already contained by others
+ while (reagent < (int)cur_reaction->reagents.size()-1)
+ {
+ if (!cur_reaction->reagents[reagent]->flags.bits.IN_CONTAINER)
+ break;
+ reagent++;
+ }
+
+ if (old_reagent != reagent)
+ {
+ // Reproduces a tiny part of the orginal screen code
+ choice_items.clear();
+
+ auto preagent = cur_reaction->reagents[reagent];
+ reagent_amnt_left = preagent->quantity;
+
+ for (int i = held_items.size()-1; i >= 0; i--)
+ {
+ if (!preagent->matchesRoot(held_items[i], cur_reaction->index))
+ continue;
+ if (linear_index(sel_items, held_items[i]) >= 0)
+ continue;
+ choice_items.push_back(held_items[i]);
+ }
+
+ layer_objects[6]->setListLength(choice_items.size());
+
+ if (!choice_items.empty())
+ {
+ layer_objects[4]->active = layer_objects[5]->active = false;
+ layer_objects[6]->active = true;
+ }
+ else if (layer_objects[6]->active)
+ {
+ layer_objects[6]->active = false;
+ layer_objects[5]->active = true;
+ }
+ }
+ }
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(advmode_contained_hook, feed);
+
static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters)
{
if (vector_get(parameters, 1) == "disable")
@@ -582,6 +649,10 @@ static command_result tweak(color_ostream &out, vector ¶meters)
enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters);
enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters);
}
+ else if (cmd == "advmode-contained")
+ {
+ enable_hook(out, INTERPOSE_HOOK(advmode_contained_hook, feed), parameters);
+ }
else
return CR_WRONG_USAGE;
From 57b72831ca700ab556566a85f2245e014ca96c30 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 20:30:25 +0400
Subject: [PATCH 16/19] Overhaul the concept of lua 'class' initialization yet
again.
---
library/lua/class.lua | 150 ++++++++++++++++++++++++++++++++++
library/lua/dfhack.lua | 24 ++----
library/lua/gui.lua | 33 +++++---
library/lua/gui/dialogs.lua | 99 +++++++++++-----------
library/lua/gui/dwarfmode.lua | 4 +-
scripts/gui/hello-world.lua | 20 +++--
scripts/gui/liquids.lua | 25 ++----
scripts/gui/mechanisms.lua | 10 +--
scripts/gui/power-meter.lua | 8 +-
scripts/gui/room-list.lua | 23 +++---
scripts/gui/siege-engine.lua | 39 +++++----
11 files changed, 285 insertions(+), 150 deletions(-)
create mode 100644 library/lua/class.lua
diff --git a/library/lua/class.lua b/library/lua/class.lua
new file mode 100644
index 000000000..7b142e499
--- /dev/null
+++ b/library/lua/class.lua
@@ -0,0 +1,150 @@
+-- A trivial reloadable class system
+
+local _ENV = mkmodule('class')
+
+-- Metatable template for a class
+class_obj = {} or class_obj
+
+-- Methods shared by all classes
+common_methods = {} or common_methods
+
+-- Forbidden names for class fields and methods.
+reserved_names = { super = true, ATTRS = true }
+
+-- Attribute table metatable
+attrs_meta = {} or attrs_meta
+
+-- Create or updates a class; a class has metamethods and thus own metatable.
+function defclass(class,parent)
+ class = class or {}
+
+ local meta = getmetatable(class)
+ if not meta then
+ meta = {}
+ setmetatable(class, meta)
+ end
+
+ for k,v in pairs(class_obj) do meta[k] = v end
+
+ meta.__index = parent or common_methods
+
+ local attrs = rawget(class, 'ATTRS') or {}
+ setmetatable(attrs, attrs_meta)
+
+ rawset(class, 'super', parent)
+ rawset(class, 'ATTRS', attrs)
+ rawset(class, '__index', rawget(class, '__index') or class)
+
+ return class
+end
+
+-- An instance uses the class as metatable
+function mkinstance(class,table)
+ table = table or {}
+ setmetatable(table, class)
+ return table
+end
+
+-- Patch the stubs in the global environment
+dfhack.BASE_G.defclass = _ENV.defclass
+dfhack.BASE_G.mkinstance = _ENV.mkinstance
+
+-- Just verify the name, and then set.
+function class_obj:__newindex(name,val)
+ if reserved_names[name] or common_methods[name] then
+ error('Method name '..name..' is reserved.')
+ end
+ rawset(self, name, val)
+end
+
+function attrs_meta:__call(attrs)
+ for k,v in pairs(attrs) do
+ self[k] = v
+ end
+end
+
+local function apply_attrs(obj, attrs, init_table)
+ for k,v in pairs(attrs) do
+ if v == DEFAULT_NIL then
+ v = nil
+ end
+ obj[k] = init_table[k] or v
+ end
+end
+
+local function invoke_before_rec(self, class, method, ...)
+ local meta = getmetatable(class)
+ if meta then
+ local fun = rawget(class, method)
+ if fun then
+ fun(self, ...)
+ end
+
+ invoke_before_rec(self, meta.__index, method, ...)
+ end
+end
+
+local function invoke_after_rec(self, class, method, ...)
+ local meta = getmetatable(class)
+ if meta then
+ invoke_after_rec(self, meta.__index, method, ...)
+
+ local fun = rawget(class, method)
+ if fun then
+ fun(self, ...)
+ end
+ end
+end
+
+local function init_attrs_rec(obj, class, init_table)
+ local meta = getmetatable(class)
+ if meta then
+ init_attrs_rec(obj, meta.__index, init_table)
+ apply_attrs(obj, rawget(class, 'ATTRS'), init_table)
+ end
+end
+
+-- Call metamethod constructs the object
+function class_obj:__call(init_table)
+ -- The table is assumed to be a scratch temporary.
+ -- If it is not, copy it yourself before calling.
+ init_table = init_table or {}
+
+ local obj = mkinstance(self)
+
+ -- This initialization sequence is broadly based on how the
+ -- Common Lisp initialize-instance generic function works.
+
+ -- preinit screens input arguments in subclass to superclass order
+ invoke_before_rec(obj, self, 'preinit', init_table)
+ -- initialize the instance table from init table
+ init_attrs_rec(obj, self, init_table)
+ -- init in superclass -> subclass
+ invoke_after_rec(obj, self, 'init', init_table)
+ -- postinit in superclass -> subclass
+ invoke_after_rec(obj, self, 'postinit', init_table)
+
+ return obj
+end
+
+-- Common methods for all instances:
+
+function common_methods:callback(method, ...)
+ return dfhack.curry(self[method], self, ...)
+end
+
+function common_methods:assign(data)
+ for k,v in pairs(data) do
+ self[k] = v
+ end
+end
+
+function common_methods:invoke_before(method, ...)
+ return invoke_before_rec(self, getmetatable(self), method, ...)
+end
+
+function common_methods:invoke_after(method, ...)
+ return invoke_after_rec(self, getmetatable(self), method, ...)
+end
+
+return _ENV
diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua
index baf0d42e0..ce3be5a87 100644
--- a/library/lua/dfhack.lua
+++ b/library/lua/dfhack.lua
@@ -113,26 +113,14 @@ function rawset_default(target,source)
end
end
-function defclass(class,parent)
- class = class or {}
- rawset_default(class, { __index = class })
- if parent then
- setmetatable(class, parent)
- else
- rawset_default(class, {
- init_fields = rawset_default,
- callback = function(self, name, ...)
- return dfhack.curry(self[name], self, ...)
- end
- })
- end
- return class
+DEFAULT_NIL = DEFAULT_NIL or {} -- Unique token
+
+function defclass(...)
+ return require('class').defclass(...)
end
-function mkinstance(class,table)
- table = table or {}
- setmetatable(table, class)
- return table
+function mkinstance(...)
+ return require('class').mkinstance(...)
end
-- Misc functions
diff --git a/library/lua/gui.lua b/library/lua/gui.lua
index f9b6ab6d2..6eaa98606 100644
--- a/library/lua/gui.lua
+++ b/library/lua/gui.lua
@@ -74,18 +74,23 @@ end
Painter = defclass(Painter, nil)
-function Painter.new(rect, pen)
- rect = rect or mkdims_wh(0,0,dscreen.getWindowSize())
- local self = {
- x1 = rect.x1, clip_x1 = rect.x1,
- y1 = rect.y1, clip_y1 = rect.y1,
- x2 = rect.x2, clip_x2 = rect.x2,
- y2 = rect.y2, clip_y2 = rect.y2,
+function Painter:init(args)
+ local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize())
+ local crect = args.clip_rect or rect
+ self:assign{
+ x = rect.x1, y = rect.y1,
+ x1 = rect.x1, clip_x1 = crect.x1,
+ y1 = rect.y1, clip_y1 = crect.y1,
+ x2 = rect.x2, clip_x2 = crect.x2,
+ y2 = rect.y2, clip_y2 = crect.y2,
width = rect.x2-rect.x1+1,
height = rect.y2-rect.y1+1,
- cur_pen = to_pen(nil, pen or COLOR_GREY)
+ cur_pen = to_pen(nil, args.pen or COLOR_GREY)
}
- return mkinstance(Painter, self):seek(0,0)
+end
+
+function Painter.new(rect, pen)
+ return Painter{ rect = rect, pen = pen }
end
function Painter:isValidPos()
@@ -213,9 +218,8 @@ Screen = defclass(Screen)
Screen.text_input_mode = false
-function Screen:init()
+function Screen:postinit()
self:updateLayout()
- return self
end
Screen.isDismissed = dscreen.isDismissed
@@ -344,7 +348,12 @@ end
FramedScreen = defclass(FramedScreen, Screen)
-FramedScreen.frame_style = BOUNDARY_FRAME
+FramedScreen.ATTRS{
+ frame_style = BOUNDARY_FRAME,
+ frame_title = DEFAULT_NIL,
+ frame_width = DEFAULT_NIL,
+ frame_height = DEFAULT_NIL,
+}
local function hint_coord(gap,hint)
if hint and hint > 0 then
diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua
index eb883465f..b1a96a558 100644
--- a/library/lua/gui/dialogs.lua
+++ b/library/lua/gui/dialogs.lua
@@ -10,24 +10,21 @@ local dscreen = dfhack.screen
MessageBox = defclass(MessageBox, gui.FramedScreen)
MessageBox.focus_path = 'MessageBox'
-MessageBox.frame_style = gui.GREY_LINE_FRAME
-
-function MessageBox:init(info)
- info = info or {}
- self:init_fields{
- text = info.text or {},
- frame_title = info.title,
- frame_width = info.frame_width,
- on_accept = info.on_accept,
- on_cancel = info.on_cancel,
- on_close = info.on_close,
- text_pen = info.text_pen
- }
- if type(self.text) == 'string' then
- self.text = utils.split_string(self.text, "\n")
+
+MessageBox.ATTRS{
+ frame_style = gui.GREY_LINE_FRAME,
+ -- new attrs
+ text = {},
+ on_accept = DEFAULT_NIL,
+ on_cancel = DEFAULT_NIL,
+ on_close = DEFAULT_NIL,
+ text_pen = DEFAULT_NIL,
+}
+
+function MessageBox:preinit(info)
+ if type(info.text) == 'string' then
+ info.text = utils.split_string(info.text, "\n")
end
- gui.FramedScreen.init(self, info)
- return self
end
function MessageBox:getWantedFrameSize()
@@ -82,9 +79,8 @@ function MessageBox:onInput(keys)
end
function showMessage(title, text, tcolor, on_close)
- mkinstance(MessageBox):init{
- text = text,
- title = title,
+ MessageBox{
+ frame_title = title,
text = text,
text_pen = tcolor,
on_close = on_close
@@ -92,8 +88,8 @@ function showMessage(title, text, tcolor, on_close)
end
function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel)
- mkinstance(MessageBox):init{
- title = title,
+ MessageBox{
+ frame_title = title,
text = text,
text_pen = tcolor,
on_accept = on_accept,
@@ -105,25 +101,23 @@ InputBox = defclass(InputBox, MessageBox)
InputBox.focus_path = 'InputBox'
-function InputBox:init(info)
- info = info or {}
- self:init_fields{
- input = info.input or '',
- input_pen = info.input_pen,
- on_input = info.on_input,
- }
- MessageBox.init(self, info)
- self.on_accept = nil
- return self
+InputBox.ATTRS{
+ input = '',
+ input_pen = DEFAULT_NIL,
+ on_input = DEFAULT_NIL,
+}
+
+function InputBox:preinit(info)
+ info.on_accept = nil
end
function InputBox:getWantedFrameSize()
- local mw, mh = MessageBox.getWantedFrameSize(self)
+ local mw, mh = InputBox.super.getWantedFrameSize(self)
return mw, mh+2
end
function InputBox:onRenderBody(dc)
- MessageBox.onRenderBody(self, dc)
+ InputBox.super.onRenderBody(self, dc)
dc:newline(1)
dc:pen(self.input_pen or COLOR_LIGHTCYAN)
@@ -161,8 +155,8 @@ function InputBox:onInput(keys)
end
function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width)
- mkinstance(InputBox):init{
- title = title,
+ InputBox{
+ frame_title = title,
text = text,
text_pen = tcolor,
input = input,
@@ -176,27 +170,28 @@ ListBox = defclass(ListBox, MessageBox)
ListBox.focus_path = 'ListBox'
+ListBox.ATTRS{
+ selection = 0,
+ choices = {},
+ select_pen = DEFAULT_NIL,
+ on_input = DEFAULT_NIL
+}
+
+function InputBox:preinit(info)
+ info.on_accept = nil
+end
+
function ListBox:init(info)
- info = info or {}
- self:init_fields{
- selection = info.selection or 0,
- choices = info.choices or {},
- select_pen = info.select_pen,
- on_input = info.on_input,
- page_top = 0
- }
- MessageBox.init(self, info)
- self.on_accept = nil
- return self
+ self.page_top = 0
end
function ListBox:getWantedFrameSize()
- local mw, mh = MessageBox.getWantedFrameSize(self)
+ local mw, mh = ListBox.super.getWantedFrameSize(self)
return mw, mh+#self.choices
end
function ListBox:onRenderBody(dc)
- MessageBox.onRenderBody(self, dc)
+ ListBox.super.onRenderBody(self, dc)
dc:newline(1)
@@ -220,6 +215,7 @@ function ListBox:onRenderBody(dc)
end
end
end
+
function ListBox:moveCursor(delta)
local newsel=self.selection+delta
if #self.choices ~=0 then
@@ -229,6 +225,7 @@ function ListBox:moveCursor(delta)
end
self.selection=newsel
end
+
function ListBox:onInput(keys)
if keys.SELECT then
self:dismiss()
@@ -257,8 +254,8 @@ function ListBox:onInput(keys)
end
function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width)
- mkinstance(ListBox):init{
- title = title,
+ ListBox{
+ frame_title = title,
text = text,
text_pen = tcolor,
choices = choices,
diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua
index 661e15591..ba3cfbe6c 100644
--- a/library/lua/gui/dwarfmode.lua
+++ b/library/lua/gui/dwarfmode.lua
@@ -353,7 +353,7 @@ end
MenuOverlay = defclass(MenuOverlay, DwarfOverlay)
function MenuOverlay:updateLayout()
- DwarfOverlay.updateLayout(self)
+ MenuOverlay.super.updateLayout(self)
self.frame_rect = self.df_layout.menu
end
@@ -361,7 +361,7 @@ MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize
MenuOverlay.getMousePos = gui.FramedScreen.getMousePos
function MenuOverlay:onAboutToShow(below)
- DwarfOverlay.onAboutToShow(self,below)
+ MenuOverlay.super.onAboutToShow(self,below)
self:updateLayout()
if not self.df_layout.menu then
diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua
index 80986bbf6..c8cd3bd01 100644
--- a/scripts/gui/hello-world.lua
+++ b/scripts/gui/hello-world.lua
@@ -4,19 +4,21 @@ local gui = require 'gui'
local text = 'Woohoo, lua viewscreen :)'
-local screen = mkinstance(gui.FramedScreen, {
+local screen = gui.FramedScreen{
frame_style = gui.GREY_LINE_FRAME,
frame_title = 'Hello World',
frame_width = #text+6,
frame_height = 3,
- onRenderBody = function(self, dc)
- dc:seek(3,1):string(text, COLOR_LIGHTGREEN)
- end,
- onInput = function(self,keys)
- if keys.LEAVESCREEN or keys.SELECT then
- self:dismiss()
- end
+}
+
+function screen:onRenderBody(dc)
+ dc:seek(3,1):string(text, COLOR_LIGHTGREEN)
+end
+
+function screen:onInput(keys)
+ if keys.LEAVESCREEN or keys.SELECT then
+ self:dismiss()
end
-}):init()
+end
screen:show()
diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua
index 89f08b7cf..cddb9f01d 100644
--- a/scripts/gui/liquids.lua
+++ b/scripts/gui/liquids.lua
@@ -53,13 +53,7 @@ local permaflows = {
Toggle = defclass(Toggle)
-function Toggle:init(items)
- self:init_fields{
- items = items,
- selected = 1
- }
- return self
-end
+Toggle.ATTRS{ items = {}, selected = 1 }
function Toggle:get()
return self.items[self.selected]
@@ -89,16 +83,14 @@ LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay)
LiquidsUI.focus_path = 'liquids'
function LiquidsUI:init()
- self:init_fields{
- brush = mkinstance(Toggle):init(brushes),
- paint = mkinstance(Toggle):init(paints),
- flow = mkinstance(Toggle):init(flowbits),
- set = mkinstance(Toggle):init(setmode),
- permaflow = mkinstance(Toggle):init(permaflows),
+ self:assign{
+ brush = Toggle{ items = brushes },
+ paint = Toggle{ items = paints },
+ flow = Toggle{ items = flowbits },
+ set = Toggle{ items = setmode },
+ permaflow = Toggle{ items = permaflows },
amount = 7,
}
- guidm.MenuOverlay.init(self)
- return self
end
function LiquidsUI:onDestroy()
@@ -201,6 +193,7 @@ function LiquidsUI:onRenderBody(dc)
end
function ensure_blocks(cursor, size, cb)
+ size = size or xyz2pos(1,1,1)
local cx,cy,cz = pos2xyz(cursor)
local all = true
for x=1,size.x or 1,16 do
@@ -298,5 +291,5 @@ if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then
qerror("This script requires the main dwarfmode view in 'k' mode")
end
-local list = mkinstance(LiquidsUI):init()
+local list = LiquidsUI()
list:show()
diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua
index c14bfcbe9..d1e8ec803 100644
--- a/scripts/gui/mechanisms.lua
+++ b/scripts/gui/mechanisms.lua
@@ -43,13 +43,11 @@ MechanismList = defclass(MechanismList, guidm.MenuOverlay)
MechanismList.focus_path = 'mechanisms'
-function MechanismList:init(building)
- self:init_fields{
+function MechanismList:init(info)
+ self:assign{
links = {}, selected = 1
}
- guidm.MenuOverlay.init(self)
- self:fillList(building)
- return self
+ self:fillList(info.building)
end
function MechanismList:fillList(building)
@@ -126,6 +124,6 @@ if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') t
qerror("This script requires the main dwarfmode view in 'q' mode")
end
-local list = mkinstance(MechanismList):init(df.global.world.selected_building)
+local list = MechanismList{ building = df.global.world.selected_building }
list:show()
list:changeSelected(1)
diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua
index 8baf43e7e..6c2f699ac 100644
--- a/scripts/gui/power-meter.lua
+++ b/scripts/gui/power-meter.lua
@@ -13,15 +13,13 @@ PowerMeter = defclass(PowerMeter, guidm.MenuOverlay)
PowerMeter.focus_path = 'power-meter'
function PowerMeter:init()
- self:init_fields{
+ self:assign{
min_power = 0, max_power = -1, invert = false,
}
- guidm.MenuOverlay.init(self)
- return self
end
function PowerMeter:onShow()
- guidm.MenuOverlay.onShow(self)
+ PowerMeter.super.onShow(self)
-- Send an event to update the errors
bselector.plate_info.flags.whole = 0
@@ -112,5 +110,5 @@ then
qerror("This script requires the main dwarfmode view in build pressure plate mode")
end
-local list = mkinstance(PowerMeter):init()
+local list = PowerMeter()
list:show()
diff --git a/scripts/gui/room-list.lua b/scripts/gui/room-list.lua
index a4507466f..0de82db5f 100644
--- a/scripts/gui/room-list.lua
+++ b/scripts/gui/room-list.lua
@@ -78,15 +78,17 @@ RoomList = defclass(RoomList, guidm.MenuOverlay)
RoomList.focus_path = 'room-list'
-function RoomList:init(unit)
+RoomList.ATTRS{ unit = DEFAULT_NIL }
+
+function RoomList:init(info)
+ local unit = info.unit
local base_bld = df.global.world.selected_building
- self:init_fields{
- unit = unit, base_building = base_bld,
+ self:assign{
+ base_building = base_bld,
items = {}, selected = 1,
own_rooms = {}, spouse_rooms = {}
}
- guidm.MenuOverlay.init(self)
self.old_viewport = self:getViewport()
self.old_cursor = guidm.getCursorPos()
@@ -115,8 +117,6 @@ function RoomList:init(unit)
self.items = concat_lists({self.base_item}, self.items)
::found::
end
-
- return self
end
local sex_char = { [0] = 12, [1] = 11 }
@@ -235,12 +235,13 @@ function RoomList:onInput(keys)
end
local focus = dfhack.gui.getCurFocus()
-if focus == 'dwarfmode/QueryBuilding/Some' then
- local base = df.global.world.selected_building
- mkinstance(RoomList):init(base.owner):show()
-elseif focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then
+
+if focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then
local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor]
- mkinstance(RoomList):init(unit):show()
+ RoomList{ unit = unit }:show()
+elseif string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then
+ local base = df.global.world.selected_building
+ RoomList{ unit = base.owner }:show()
else
qerror("This script requires the main dwarfmode view in 'q' mode")
end
diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua
index 7a76d7673..c98cb1676 100644
--- a/scripts/gui/siege-engine.lua
+++ b/scripts/gui/siege-engine.lua
@@ -34,30 +34,29 @@ SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay)
SiegeEngine.focus_path = 'siege-engine'
-function SiegeEngine:init(building)
- self:init_fields{
- building = building,
- center = utils.getBuildingCenter(building),
+SiegeEngine.ATTRS{ building = DEFAULT_NIL }
+
+function SiegeEngine:init()
+ self:assign{
+ center = utils.getBuildingCenter(self.building),
selected_pile = 1,
+ mode_main = {
+ render = self:callback 'onRenderBody_main',
+ input = self:callback 'onInput_main',
+ },
+ mode_aim = {
+ render = self:callback 'onRenderBody_aim',
+ input = self:callback 'onInput_aim',
+ },
+ mode_pile = {
+ render = self:callback 'onRenderBody_pile',
+ input = self:callback 'onInput_pile',
+ }
}
- guidm.MenuOverlay.init(self)
- self.mode_main = {
- render = self:callback 'onRenderBody_main',
- input = self:callback 'onInput_main',
- }
- self.mode_aim = {
- render = self:callback 'onRenderBody_aim',
- input = self:callback 'onInput_aim',
- }
- self.mode_pile = {
- render = self:callback 'onRenderBody_pile',
- input = self:callback 'onInput_pile',
- }
- return self
end
function SiegeEngine:onShow()
- guidm.MenuOverlay.onShow(self)
+ SiegeEngine.super.onShow(self)
self.old_cursor = guidm.getCursorPos()
self.old_viewport = self:getViewport()
@@ -487,5 +486,5 @@ if not df.building_siegeenginest:is_instance(building) then
qerror("A siege engine must be selected")
end
-local list = mkinstance(SiegeEngine):init(df.global.world.selected_building)
+local list = SiegeEngine{ building = building }
list:show()
From a4799a384b2f33be5b3fcf43b462175de6ce7f65 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 20:45:59 +0400
Subject: [PATCH 17/19] Catch C++ exceptions in dfhack.buildings.setSize
---
library/LuaApi.cpp | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp
index f8497569e..d73d14cf9 100644
--- a/library/LuaApi.cpp
+++ b/library/LuaApi.cpp
@@ -1084,7 +1084,9 @@ static int buildings_getCorrectSize(lua_State *state)
return 5;
}
-static int buildings_setSize(lua_State *state)
+namespace {
+
+int buildings_setSize(lua_State *state)
{
auto bld = Lua::CheckDFObject(state, 1);
df::coord2d size(luaL_optint(state, 2, 1), luaL_optint(state, 3, 1));
@@ -1105,11 +1107,13 @@ static int buildings_setSize(lua_State *state)
return 1;
}
+}
+
static const luaL_Reg dfhack_buildings_funcs[] = {
{ "findAtTile", buildings_findAtTile },
{ "findCivzonesAt", buildings_findCivzonesAt },
{ "getCorrectSize", buildings_getCorrectSize },
- { "setSize", buildings_setSize },
+ { "setSize", &Lua::CallWithCatchWrapper },
{ NULL, NULL }
};
From 69e8fcce915630b1e861544eca9701cc7a2029c5 Mon Sep 17 00:00:00 2001
From: Quietust
Date: Tue, 18 Sep 2012 13:57:06 -0500
Subject: [PATCH 18/19] Add mouse input to Manipulator, along with column
labels
---
plugins/manipulator.cpp | 104 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 104 insertions(+)
diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp
index f40969655..1ad46ab28 100644
--- a/plugins/manipulator.cpp
+++ b/plugins/manipulator.cpp
@@ -528,6 +528,105 @@ void viewscreen_unitlaborsst::feed(set *events)
if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1)
first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1;
+ // handle mouse input
+ if (enabler->tracking_on && gps->mouse_x != -1 && gps->mouse_y != -1)
+ {
+ int click_header = DISP_COLUMN_MAX; // group ID of the column header clicked
+ int click_body = DISP_COLUMN_MAX; // group ID of the column body clicked
+
+ int click_unit = -1; // Index into units[] (-1 if out of range)
+ int click_labor = -1; // Index into columns[] (-1 if out of range)
+
+ for (int i = 0; i < DISP_COLUMN_MAX; i++)
+ {
+ if ((gps->mouse_x >= col_offsets[i]) &&
+ (gps->mouse_x < col_offsets[i] + col_widths[i]))
+ {
+ if ((gps->mouse_y >= 1) && (gps->mouse_y <= 2))
+ click_header = i;
+ if ((gps->mouse_y >= 4) && (gps->mouse_y <= 4 + num_rows))
+ click_body = i;
+ }
+ }
+
+ if ((gps->mouse_x >= col_offsets[DISP_COLUMN_LABORS]) &&
+ (gps->mouse_x < col_offsets[DISP_COLUMN_LABORS] + col_widths[DISP_COLUMN_LABORS]))
+ click_labor = gps->mouse_x - col_offsets[DISP_COLUMN_LABORS] + first_column;
+ if ((gps->mouse_y >= 4) && (gps->mouse_y <= 4 + num_rows))
+ click_unit = gps->mouse_y - 4 + first_row;
+
+ switch (click_header)
+ {
+ case DISP_COLUMN_HAPPINESS:
+ if (enabler->mouse_lbut || enabler->mouse_rbut)
+ {
+ descending = enabler->mouse_lbut;
+ std::sort(units.begin(), units.end(), sortByHappiness);
+ }
+ break;
+
+ case DISP_COLUMN_NAME:
+ if (enabler->mouse_lbut || enabler->mouse_rbut)
+ {
+ descending = enabler->mouse_rbut;
+ std::sort(units.begin(), units.end(), sortByName);
+ }
+ break;
+
+ case DISP_COLUMN_PROFESSION:
+ if (enabler->mouse_lbut || enabler->mouse_rbut)
+ {
+ descending = enabler->mouse_rbut;
+ std::sort(units.begin(), units.end(), sortByProfession);
+ }
+ break;
+
+ case DISP_COLUMN_LABORS:
+ if (enabler->mouse_lbut || enabler->mouse_rbut)
+ {
+ descending = enabler->mouse_lbut;
+ sort_skill = columns[click_labor].skill;
+ sort_labor = columns[click_labor].labor;
+ std::sort(units.begin(), units.end(), sortBySkill);
+ }
+ break;
+ }
+
+ switch (click_body)
+ {
+ case DISP_COLUMN_HAPPINESS:
+ // do nothing
+ break;
+
+ case DISP_COLUMN_NAME:
+ case DISP_COLUMN_PROFESSION:
+ // left-click to view, right-click to zoom
+ if (enabler->mouse_lbut)
+ {
+ sel_row = click_unit;
+ events->insert(interface_key::UNITJOB_VIEW);
+ }
+ if (enabler->mouse_rbut)
+ {
+ sel_row = click_unit;
+ events->insert(interface_key::UNITJOB_ZOOM_CRE);
+ }
+ break;
+
+ case DISP_COLUMN_LABORS:
+ // left-click to toggle, right-click to just highlight
+ if (enabler->mouse_lbut || enabler->mouse_rbut)
+ {
+ sel_row = click_unit;
+ sel_column = click_labor;
+ if (enabler->mouse_lbut)
+ events->insert(interface_key::SELECT);
+ }
+ break;
+ }
+ enabler->mouse_lbut = enabler->mouse_rbut = 0;
+ }
+
UnitInfo *cur = units[sel_row];
if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE))
{
@@ -647,6 +746,11 @@ void viewscreen_unitlaborsst::render()
Screen::clear();
Screen::drawBorder(" Dwarf Manipulator - Manage Labors ");
+
+ Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_HAPPINESS], 2, "Hap.");
+ Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name");
+ Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION], 2, "Profession");
+
for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++)
{
int col_offset = col + first_column;
From 65a382a2d7b4c910fe498112687c9c584d5408cb Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Wed, 19 Sep 2012 15:55:08 +0400
Subject: [PATCH 19/19] Document some of the new stuff in the readme.
---
README.rst | 247 ++++++++-
Readme.html | 850 +++++++++++++++++++++----------
plugins/raw/entity_default.diff | 4 +-
plugins/raw/reaction_spatter.txt | 10 +
4 files changed, 818 insertions(+), 293 deletions(-)
diff --git a/README.rst b/README.rst
index 6c463911a..8b6974abf 100644
--- a/README.rst
+++ b/README.rst
@@ -707,6 +707,8 @@ Options
interface.
:rename unit-profession "custom profession": Change proffession name of the
highlighted unit/creature.
+:rename building "name": Set a custom name for the selected building.
+ The building must be one of stockpile, workshop, furnace, trap or siege engine.
reveal
======
@@ -907,6 +909,23 @@ Options
and are not flagged as tame), but you are allowed to mark them
for slaughter. Grabbing wagons results in some funny spam, then
they are scuttled.
+:stable-cursor: Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.
+:patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts.
+ Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
+:readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu.
+:stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates.
+ In very item-heavy forts with big stockpiles this can improve FPS by 50-100%
+:fast-heat: Further improves temperature update performance by ensuring that 1 degree
+ of item temperature is crossed in no more than specified number of frames
+ when updating from the environment temperature. This reduces the time it
+ takes for stable-temp to stop updates again when equilibrium is disturbed.
+:fix-dimensions: Fixes subtracting small amount of thread/cloth/liquid from a stack
+ by splitting the stack and subtracting from the remaining single item.
+ This is a necessary addition to the binary patch in bug 808.
+:advmode-contained: Works around bug 6202, i.e. custom reactions with container inputs
+ in advmode. The issue is that the screen tries to force you to select
+ the contents separately from the container. This forcefully skips child
+ reagents.
tubefill
========
@@ -1414,16 +1433,16 @@ using this mode for birds is not recommanded.)
Will target any unit on a revealed tile of the map, including ambushers.
-Ex:
-::
+Ex::
+
slayrace gob
-To kill a single creature, select the unit with the 'v' cursor and:
-::
+To kill a single creature, select the unit with the 'v' cursor and::
+
slayrace him
-To purify all elves on the map with fire (may have side-effects):
-::
+To purify all elves on the map with fire (may have side-effects)::
+
slayrace elve magma
@@ -1435,8 +1454,8 @@ This script registers a map tile as a magma source, and every 12 game ticks
that tile receives 1 new unit of flowing magma.
Place the game cursor where you want to create the source (must be a
-flow-passable tile, and not too high in the sky) and call
-::
+flow-passable tile, and not too high in the sky) and call::
+
magmasource here
To add more than 1 unit everytime, call the command again.
@@ -1446,3 +1465,215 @@ To remove all placed sources, call ``magmasource stop``.
With no argument, this command shows an help message and list existing sources.
+
+=======================
+In-game interface tools
+=======================
+
+These tools work by displaying dialogs or overlays in the game window, and
+are mostly implemented by lua scripts.
+
+
+Dwarf Manipulator
+=================
+
+Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'.
+
+This tool implements a Dwarf Therapist-like interface within the game ui.
+
+
+Liquids
+=======
+
+Implemented by the gui/liquids script. To use, bind to a key and activate in the 'k' mode.
+
+While active, use the suggested keys to switch the usual liquids parameters, and Enter
+to select the target area and apply changes.
+
+
+Mechanisms
+==========
+
+Implemented by the gui/mechanims script. To use, bind to a key and activate in the 'q' mode.
+
+Lists mechanisms connected to the building, and their links. Navigating the list centers
+the view on the relevant linked buildings.
+
+To exit, press ESC or Enter; ESC recenters on the original building, while Enter leaves
+focus on the current one. Shift-Enter has an effect equivalent to pressing Enter, and then
+re-entering the mechanisms ui.
+
+
+Power Meter
+===========
+
+Front-end to the power-meter plugin implemented by the gui/power-meter script. Bind to a
+key and activate after selecting Pressure Plate in the build menu.
+
+The script follows the general look and feel of the regular pressure plate build
+configuration page, but configures parameters relevant to the modded power meter building.
+
+
+Rename
+======
+
+Backed by the rename plugin, the gui/rename script allows entering the desired name
+via a simple dialog in the game ui.
+
+* ``gui/rename [building]`` in 'q' mode changes the name of a building.
+
+ The selected building must be one of stockpile, workshop, furnace, trap or siege engine.
+
+* ``gui/rename [unit]`` with a unit selected changes the nickname.
+
+* ``gui/rename unit-profession`` changes the selected unit's custom profession name.
+
+The ``building`` or ``unit`` are automatically assumed when in relevant ui state.
+
+
+Room List
+=========
+
+Implemented by the gui/room-list script. To use, bind to a key and activate in the 'q' mode,
+either immediately or after opening the assign owner page.
+
+The script lists other rooms owned by the same owner, or by the unit selected in the assign
+list, and allows unassigning them.
+
+
+Siege Engine
+============
+
+Front-end to the siege-engine plugin implemented by the gui/siege-engine script. Bind to a
+key and activate after selecting a siege engine in 'q' mode.
+
+The main mode displays the current target, selected ammo item type, linked stockpiles and
+the allowed operator skill range. The map tile color is changed to reflect if it can be
+hit by the selected engine.
+
+Pressing 'r' changes into the target selection mode, which works by highlighting two points
+with Enter like all designations. When a target area is set, the engine projectiles are
+aimed at that area, or units within it, instead of the vanilla four directions.
+
+Pressing 't' switches to stockpile selection.
+
+Exiting from the siege engine script via ESC reverts the view to the state prior to starting
+the script. Shift-ESC retains the current viewport, and also exits from the 'q' mode to main
+menu.
+
+**DISCLAIMER**: Siege engines are a very interesting feature, but currently nearly useless
+because they haven't been updated since 2D and can only aim in four directions. This is an
+attempt to bring them more up to date until Toady has time to work on it. Actual improvements,
+e.g. like making siegers bring their own, are something only Toady can do.
+
+
+=========
+RAW hacks
+=========
+
+These plugins detect certain structures in RAWs, and enhance them in various ways.
+
+
+Steam Engine
+============
+
+The steam-engine plugin detects custom workshops with STEAM_ENGINE in
+their token, and turns them into real steam engines.
+
+Rationale
+---------
+
+The vanilla game contains only water wheels and windmills as sources of
+power, but windmills give relatively little power, and water wheels require
+flowing water, which must either be a real river and thus immovable and
+limited in supply, or actually flowing and thus laggy.
+
+Steam engines are an alternative to water reactors that actually makes
+sense, and hopefully doesn't lag. Also, unlike e.g. animal treadmills,
+it can be done just by combining existing features of the game engine
+in a new way with some glue code and a bit of custom logic.
+
+Construction
+------------
+
+The workshop needs water as its input, which it takes via a
+passable floor tile below it, like usual magma workshops do.
+The magma version also needs magma.
+
+**ISSUE**: Since this building is a machine, and machine collapse
+code cannot be modified, it would collapse over true open space.
+As a loophole, down stair provides support to machines, while
+being passable, so use them.
+
+After constructing the building itself, machines can be connected
+to the edge tiles that look like gear boxes. Their exact position
+is extracted from the workshop raws.
+
+**ISSUE**: Like with collapse above, part of the code involved in
+machine connection cannot be modified. As a result, the workshop
+can only immediately connect to machine components built AFTER it.
+This also means that engines cannot be chained without intermediate
+short axles that can be built later.
+
+Operation
+---------
+
+In order to operate the engine, queue the Stoke Boiler job (optionally
+on repeat). A furnace operator will come, possibly bringing a bar of fuel,
+and perform it. As a result, a "boiling water" item will appear
+in the 't' view of the workshop.
+
+**NOTE**: The completion of the job will actually consume one unit
+of the appropriate liquids from below the workshop.
+
+Every such item gives 100 power, up to a limit of 300 for coal,
+and 500 for a magma engine. The building can host twice that
+amount of items to provide longer autonomous running. When the
+boiler gets filled to capacity, all queued jobs are suspended;
+once it drops back to 3+1 or 5+1 items, they are re-enabled.
+
+While the engine is providing power, steam is being consumed.
+The consumption speed includes a fixed 10% waste rate, and
+the remaining 90% are applied proportionally to the actual
+load in the machine. With the engine at nominal 300 power with
+150 load in the system, it will consume steam for actual
+300*(10% + 90%*150/300) = 165 power.
+
+Masterpiece mechanism and chain will decrease the mechanical
+power drawn by the engine itself from 10 to 5. Masterpiece
+barrel decreases waste rate by 4%. Masterpiece piston and pipe
+decrease it by further 4%, and also decrease the whole steam
+use rate by 10%.
+
+Explosions
+----------
+
+The engine must be constructed using barrel, pipe and piston
+from fire-safe, or in the magma version magma-safe metals.
+
+During operation weak parts get gradually worn out, and
+eventually the engine explodes. It should also explode if
+toppled during operation by a building destroyer, or a
+tantruming dwarf.
+
+Save files
+----------
+
+It should be safe to load and view engine-using fortresses
+from a DF version without DFHack installed, except that in such
+case the engines won't work. However actually making modifications
+to them, or machines they connect to (including by pulling levers),
+can easily result in inconsistent state once this plugin is
+available again. The effects may be as weird as negative power
+being generated.
+
+
+Add Spatter
+===========
+
+This plugin makes reactions with names starting with ``SPATTER_ADD_``
+produce contaminants on the items instead of improvements.
+
+Intended to give some use to all those poisons that can be bought from caravans.
+
+To be really useful this needs patches from bug 808 and ``tweak fix-dimensions``.
diff --git a/Readme.html b/Readme.html
index 50ceae999..001e89b1f 100644
--- a/Readme.html
+++ b/Readme.html
@@ -3,7 +3,7 @@
-
+