From 604cf808321382286619042b1edbf81558086cb5 Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Thu, 30 Aug 2012 09:23:11 -0500
Subject: 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 b3d24cd9..42c3c066 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;
}
--
cgit v1.2.1
From 24772f4dbcfcfbf0ac3d29f72d3bda19566d8530 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 14 Sep 2012 18:49:02 +0400
Subject: 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 1ffdada0..c5f9a1c5 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 168f7dcc..07f038bc 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 f69fa7a1..f8497569 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 7493d22f..81c8e128 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 751797f0..b8c697a4 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);
--
cgit v1.2.1
From 811c096c0ecdcde1d0d19be8e4996996bde27995 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 14 Sep 2012 20:22:49 +0400
Subject: 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 2bc8fbdf..ee2b63a8 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 ba888e7c..0af1fce5 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 a41bfe5f..d1e34ced 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));
}
}
--
cgit v1.2.1
From 000e3baf27e3d811e673bca08e4381c1f2c632b7 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Fri, 14 Sep 2012 20:57:03 +0400
Subject: 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 d1e34ced..b8a2f087 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())
{
--
cgit v1.2.1
From 58fda716e6c1feee85ce7fb15d913c87444c3feb Mon Sep 17 00:00:00 2001
From: Kelly Martin
Date: Sun, 16 Sep 2012 17:06:31 -0500
Subject: 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 b8a2f087..5e5cf5d7 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;
--
cgit v1.2.1
From c1e20c6f0565007c47cce9aaa06199a061dac20e Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 12:47:18 +0400
Subject: 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 52039566..01a798e3 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 91df14ea..b0cfda67 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 ee2b63a8..a6b95f1c 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 ff51fc77..4b2bf7bb 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));
}
--
cgit v1.2.1
From f2fde21b10c087fd95948a5f40ef972ac6688718 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 14:45:22 +0400
Subject: 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 5e5cf5d7..3b95aba3 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 89c47659..33e120fe 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
--
cgit v1.2.1
From 82e870c8ddc9b64370c3828d1d4807306ac72887 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 14:59:59 +0400
Subject: Move siege engine out of devel.
---
NEWS | 10 +-
plugins/CMakeLists.txt | 1 +
plugins/devel/CMakeLists.txt | 1 -
plugins/devel/siege-engine.cpp | 1865 ----------------------------------------
plugins/siege-engine.cpp | 1865 ++++++++++++++++++++++++++++++++++++++++
scripts/gui/siege-engine.lua | 1 +
6 files changed, 1873 insertions(+), 1870 deletions(-)
delete mode 100644 plugins/devel/siege-engine.cpp
create mode 100644 plugins/siege-engine.cpp
diff --git a/NEWS b/NEWS
index 43707f9a..4294bedb 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 6e207385..8511d86c 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 39e8f7b6..134d5cb6 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/devel/siege-engine.cpp
deleted file mode 100644
index 3b95aba3..00000000
--- a/plugins/devel/siege-engine.cpp
+++ /dev/null
@@ -1,1865 +0,0 @@
-#include "Core.h"
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include
-#include "df/graphic.h"
-#include "df/building_siegeenginest.h"
-#include "df/builtin_mats.h"
-#include "df/world.h"
-#include "df/buildings_other_id.h"
-#include "df/job.h"
-#include "df/building_drawbuffer.h"
-#include "df/ui.h"
-#include "df/viewscreen_dwarfmodest.h"
-#include "df/ui_build_selector.h"
-#include "df/flow_info.h"
-#include "df/report.h"
-#include "df/proj_itemst.h"
-#include "df/unit.h"
-#include "df/unit_soul.h"
-#include "df/unit_skill.h"
-#include "df/physical_attribute_type.h"
-#include "df/creature_raw.h"
-#include "df/caste_raw.h"
-#include "df/caste_raw_flags.h"
-#include "df/assumed_identity.h"
-#include "df/game_mode.h"
-#include "df/unit_misc_trait.h"
-#include "df/job.h"
-#include "df/job_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"
-
-using std::vector;
-using std::string;
-using std::stack;
-using namespace DFHack;
-using namespace df::enums;
-
-using df::global::gamemode;
-using df::global::gps;
-using df::global::world;
-using df::global::ui;
-using df::global::ui_build_selector;
-
-using Screen::Pen;
-
-DFHACK_PLUGIN("siege-engine");
-
-/*
- * Misc. utils
- */
-
-typedef std::pair coord_range;
-
-static void set_range(coord_range *target, df::coord p1, df::coord p2)
-{
- if (!p1.isValid() || !p2.isValid())
- {
- *target = coord_range();
- }
- else
- {
- target->first.x = std::min(p1.x, p2.x);
- target->first.y = std::min(p1.y, p2.y);
- target->first.z = std::min(p1.z, p2.z);
- target->second.x = std::max(p1.x, p2.x);
- target->second.y = std::max(p1.y, p2.y);
- target->second.z = std::max(p1.z, p2.z);
- }
-}
-
-static bool is_range_valid(const coord_range &target)
-{
- return target.first.isValid() && target.second.isValid();
-}
-
-static bool is_in_range(const coord_range &target, df::coord pos)
-{
- return target.first.isValid() && target.second.isValid() &&
- target.first.x <= pos.x && pos.x <= target.second.x &&
- target.first.y <= pos.y && pos.y <= target.second.y &&
- target.first.z <= pos.z && pos.z <= target.second.z;
-}
-
-static std::pair get_engine_range(df::building_siegeenginest *bld)
-{
- if (bld->type == siegeengine_type::Ballista)
- return std::make_pair(1, 200);
- else
- return std::make_pair(30, 100);
-}
-
-static void orient_engine(df::building_siegeenginest *bld, df::coord target)
-{
- int dx = target.x - bld->centerx;
- int dy = target.y - bld->centery;
-
- if (abs(dx) > abs(dy))
- bld->facing = (dx > 0) ?
- df::building_siegeenginest::Right :
- df::building_siegeenginest::Left;
- else
- bld->facing = (dy > 0) ?
- df::building_siegeenginest::Down :
- df::building_siegeenginest::Up;
-}
-
-static int random_int(int val)
-{
- return int(int64_t(rand())*val/RAND_MAX);
-}
-
-static int point_distance(df::coord speed)
-{
- return std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z)));
-}
-
-inline void normalize(float &x, float &y, float &z)
-{
- float dist = sqrtf(x*x + y*y + z*z);
- if (dist == 0.0f) return;
- x /= dist; y /= dist; z /= dist;
-}
-
-static void random_direction(float &x, float &y, float &z)
-{
- float a, b, d;
- for (;;) {
- a = (rand() + 0.5f)*2.0f/RAND_MAX - 1.0f;
- b = (rand() + 0.5f)*2.0f/RAND_MAX - 1.0f;
- d = a*a + b*b;
- if (d < 1.0f)
- break;
- }
-
- float sq = sqrtf(1-d);
- x = 2.0f*a*sq;
- y = 2.0f*b*sq;
- 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
- */
-
-static bool enable_plugin();
-
-struct EngineInfo {
- int id;
- df::building_siegeenginest *bld;
-
- df::coord center;
- coord_range building_rect;
-
- bool is_catapult;
- int proj_speed, hit_delay;
- std::pair fire_range;
-
- coord_range target;
-
- df::job_item_vector_id ammo_vector_id;
- df::item_type ammo_item_type;
-
- int operator_id, operator_frame;
-
- std::set stockpiles;
- df::stockpile_links links;
- df::workshop_profile profile;
-
- bool hasTarget() { return is_range_valid(target); }
- bool onTarget(df::coord pos) { return is_in_range(target, pos); }
- df::coord getTargetSize() { return target.second - target.first; }
-
- bool isInRange(int dist) {
- return dist >= fire_range.first && dist <= fire_range.second;
- }
-};
-
-static std::map engines;
-static std::map coord_engines;
-
-static EngineInfo *find_engine(df::building *bld, bool create = false)
-{
- auto ebld = strict_virtual_cast(bld);
- if (!ebld)
- return NULL;
-
- auto &obj = engines[bld];
-
- if (obj)
- {
- obj->bld = ebld;
- return obj;
- }
-
- if (!create)
- return NULL;
-
- obj = new EngineInfo();
-
- obj->id = bld->id;
- obj->bld = ebld;
- obj->center = df::coord(bld->centerx, bld->centery, bld->z);
- obj->building_rect = coord_range(
- df::coord(bld->x1, bld->y1, bld->z),
- df::coord(bld->x2, bld->y2, bld->z)
- );
- obj->is_catapult = (ebld->type == siegeengine_type::Catapult);
- obj->proj_speed = 2;
- obj->hit_delay = obj->is_catapult ? 2 : -1;
- obj->fire_range = get_engine_range(ebld);
-
- obj->ammo_vector_id = job_item_vector_id::BOULDER;
- obj->ammo_item_type = item_type::BOULDER;
-
- obj->operator_id = obj->operator_frame = -1;
-
- coord_engines[obj->center] = bld;
- return obj;
-}
-
-static EngineInfo *find_engine(lua_State *L, int idx, bool create = false, bool silent = false)
-{
- auto bld = Lua::CheckDFObject(L, idx);
-
- auto engine = find_engine(bld, create);
- if (!engine && !silent)
- luaL_error(L, "no such engine");
-
- return engine;
-}
-
-static EngineInfo *find_engine(df::coord pos)
-{
- auto engine = find_engine(coord_engines[pos]);
-
- if (engine)
- {
- auto bld0 = df::building::find(engine->id);
- auto bld = strict_virtual_cast(bld0);
- if (!bld)
- return NULL;
-
- engine->bld = bld;
- }
-
- return engine;
-}
-
-/*
- * Configuration management
- */
-
-static void clear_engines()
-{
- for (auto it = engines.begin(); it != engines.end(); ++it)
- delete it->second;
- engines.clear();
- coord_engines.clear();
-}
-
-static void load_engines()
-{
- clear_engines();
-
- auto pworld = Core::getInstance().getWorld();
- std::vector vec;
-
- pworld->GetPersistentData(&vec, "siege-engine/target/", true);
- for (auto it = vec.begin(); it != vec.end(); ++it)
- {
- auto engine = find_engine(df::building::find(it->ival(0)), true);
- if (!engine) continue;
- engine->target.first = df::coord(it->ival(1), it->ival(2), it->ival(3));
- engine->target.second = df::coord(it->ival(4), it->ival(5), it->ival(6));
- }
-
- pworld->GetPersistentData(&vec, "siege-engine/ammo/", true);
- for (auto it = vec.begin(); it != vec.end(); ++it)
- {
- auto engine = find_engine(df::building::find(it->ival(0)), true);
- if (!engine) continue;
- engine->ammo_vector_id = (df::job_item_vector_id)it->ival(1);
- engine->ammo_item_type = (df::item_type)it->ival(2);
- }
-
- pworld->GetPersistentData(&vec, "siege-engine/stockpiles/", true);
- for (auto it = vec.begin(); it != vec.end(); ++it)
- {
- auto engine = find_engine(df::building::find(it->ival(0)), true);
- if (!engine)
- continue;
- auto pile = df::building::find(it->ival(1));
- if (!pile || pile->getType() != building_type::Stockpile)
- {
- pworld->DeletePersistentData(*it);
- continue;;
- }
-
- engine->stockpiles.insert(it->ival(1));
- }
-
- pworld->GetPersistentData(&vec, "siege-engine/profiles/", true);
- for (auto it = vec.begin(); it != vec.end(); ++it)
- {
- auto engine = find_engine(df::building::find(it->ival(0)), true);
- if (!engine) continue;
- engine->profile.min_level = it->ival(1);
- engine->profile.max_level = it->ival(2);
- }
-
- pworld->GetPersistentData(&vec, "siege-engine/profile-workers/", true);
- for (auto it = vec.begin(); it != vec.end(); ++it)
- {
- auto engine = find_engine(df::building::find(it->ival(0)), true);
- if (!engine)
- continue;
- auto unit = df::unit::find(it->ival(1));
- if (!unit || !Units::isCitizen(unit))
- {
- pworld->DeletePersistentData(*it);
- continue;
- }
- engine->profile.permitted_workers.push_back(it->ival(1));
- }
-}
-
-static int getTargetArea(lua_State *L)
-{
- auto engine = find_engine(L, 1, false, true);
-
- if (engine && engine->hasTarget())
- {
- Lua::Push(L, engine->target.first);
- Lua::Push(L, engine->target.second);
- }
- else
- {
- lua_pushnil(L);
- lua_pushnil(L);
- }
-
- return 2;
-}
-
-static void clearTargetArea(df::building_siegeenginest *bld)
-{
- CHECK_NULL_POINTER(bld);
-
- if (auto engine = find_engine(bld))
- engine->target = coord_range();
-
- auto pworld = Core::getInstance().getWorld();
- auto key = stl_sprintf("siege-engine/target/%d", bld->id);
- pworld->DeletePersistentData(pworld->GetPersistentData(key));
-}
-
-static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, df::coord target_max)
-{
- CHECK_NULL_POINTER(bld);
- CHECK_INVALID_ARGUMENT(target_min.isValid() && target_max.isValid());
-
- if (!enable_plugin())
- return false;
-
- auto pworld = Core::getInstance().getWorld();
- auto key = stl_sprintf("siege-engine/target/%d", bld->id);
- auto entry = pworld->GetPersistentData(key, NULL);
- if (!entry.isValid())
- return false;
-
- auto engine = find_engine(bld, true);
-
- set_range(&engine->target, target_min, target_max);
-
- entry.ival(0) = bld->id;
- entry.ival(1) = engine->target.first.x;
- entry.ival(2) = engine->target.first.y;
- entry.ival(3) = engine->target.first.z;
- entry.ival(4) = engine->target.second.x;
- entry.ival(5) = engine->target.second.y;
- entry.ival(6) = engine->target.second.z;
-
- df::coord sum = target_min + target_max;
- orient_engine(bld, df::coord(sum.x/2, sum.y/2, sum.z/2));
-
- return true;
-}
-
-static int getAmmoItem(lua_State *L)
-{
- auto engine = find_engine(L, 1, false, true);
- if (!engine)
- Lua::Push(L, item_type::BOULDER);
- else
- Lua::Push(L, engine->ammo_item_type);
- return 1;
-}
-
-static int setAmmoItem(lua_State *L)
-{
- if (!enable_plugin())
- return 0;
-
- auto engine = find_engine(L, 1, true);
- auto item_type = (df::item_type)luaL_optint(L, 2, item_type::BOULDER);
- if (!is_valid_enum_item(item_type))
- luaL_argerror(L, 2, "invalid item type");
-
- auto pworld = Core::getInstance().getWorld();
- auto key = stl_sprintf("siege-engine/ammo/%d", engine->id);
- auto entry = pworld->GetPersistentData(key, NULL);
- if (!entry.isValid())
- return 0;
-
- engine->ammo_vector_id = job_item_vector_id::ANY_FREE;
- engine->ammo_item_type = item_type;
-
- FOR_ENUM_ITEMS(job_item_vector_id, id)
- {
- auto other = ENUM_ATTR(job_item_vector_id, other, id);
- auto type = ENUM_ATTR(items_other_id, item, other);
- if (type == item_type)
- {
- engine->ammo_vector_id = id;
- break;
- }
- }
-
- entry.ival(0) = engine->id;
- entry.ival(1) = engine->ammo_vector_id;
- entry.ival(2) = engine->ammo_item_type;
-
- lua_pushboolean(L, true);
- return 1;
-}
-
-static void forgetStockpileLink(EngineInfo *engine, int pile_id)
-{
- engine->stockpiles.erase(pile_id);
-
- auto pworld = Core::getInstance().getWorld();
- auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", engine->id, pile_id);
- pworld->DeletePersistentData(pworld->GetPersistentData(key));
-}
-
-static void update_stockpile_links(EngineInfo *engine)
-{
- engine->links.take_from_pile.clear();
-
- for (auto it = engine->stockpiles.begin(); it != engine->stockpiles.end(); )
- {
- int id = *it; ++it;
- auto pile = df::building::find(id);
-
- if (!pile || pile->getType() != building_type::Stockpile)
- forgetStockpileLink(engine, id);
- else
- // The vector is sorted, but we are iterating through a sorted set
- engine->links.take_from_pile.push_back(pile);
- }
-}
-
-static int getStockpileLinks(lua_State *L)
-{
- auto engine = find_engine(L, 1, false, true);
- if (!engine || engine->stockpiles.empty())
- return 0;
-
- update_stockpile_links(engine);
-
- auto &links = engine->links.take_from_pile;
- lua_createtable(L, links.size(), 0);
-
- for (size_t i = 0; i < links.size(); i++)
- {
- Lua::Push(L, links[i]);
- lua_rawseti(L, -2, i+1);
- }
-
- return 1;
-}
-
-static bool isLinkedToPile(df::building_siegeenginest *bld, df::building_stockpilest *pile)
-{
- CHECK_NULL_POINTER(bld);
- CHECK_NULL_POINTER(pile);
-
- auto engine = find_engine(bld);
-
- return engine && engine->stockpiles.count(pile->id);
-}
-
-static bool addStockpileLink(df::building_siegeenginest *bld, df::building_stockpilest *pile)
-{
- CHECK_NULL_POINTER(bld);
- CHECK_NULL_POINTER(pile);
-
- if (!enable_plugin())
- return false;
-
- auto pworld = Core::getInstance().getWorld();
- auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", bld->id, pile->id);
- auto entry = pworld->GetPersistentData(key, NULL);
- if (!entry.isValid())
- return false;
-
- auto engine = find_engine(bld, true);
-
- entry.ival(0) = bld->id;
- entry.ival(1) = pile->id;
-
- engine->stockpiles.insert(pile->id);
- return true;
-}
-
-static bool removeStockpileLink(df::building_siegeenginest *bld, df::building_stockpilest *pile)
-{
- CHECK_NULL_POINTER(bld);
- CHECK_NULL_POINTER(pile);
-
- if (auto engine = find_engine(bld))
- {
- forgetStockpileLink(engine, pile->id);
- return true;
- }
-
- return false;
-}
-
-static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld)
-{
- CHECK_NULL_POINTER(bld);
-
- if (!enable_plugin())
- return NULL;
-
- // Save skill limits
- auto pworld = Core::getInstance().getWorld();
- auto key = stl_sprintf("siege-engine/profiles/%d", bld->id);
- auto entry = pworld->GetPersistentData(key, NULL);
- if (!entry.isValid())
- return NULL;
-
- auto engine = find_engine(bld, true);
-
- entry.ival(0) = engine->id;
- entry.ival(1) = engine->profile.min_level;
- entry.ival(2) = engine->profile.max_level;
-
- // Save worker list
- std::vector vec;
- auto &workers = engine->profile.permitted_workers;
-
- key = stl_sprintf("siege-engine/profile-workers/%d", bld->id);
- pworld->GetPersistentData(&vec, key, true);
-
- for (auto it = vec.begin(); it != vec.end(); ++it)
- {
- if (linear_index(workers, it->ival(1)) < 0)
- pworld->DeletePersistentData(*it);
- }
-
- for (size_t i = 0; i < workers.size(); i++)
- {
- key = stl_sprintf("siege-engine/profile-workers/%d/%d", bld->id, workers[i]);
- entry = pworld->GetPersistentData(key, NULL);
- if (!entry.isValid())
- continue;
- entry.ival(0) = engine->id;
- entry.ival(1) = workers[i];
- }
-
- return &engine->profile;
-}
-
-static int getOperatorSkill(df::building_siegeenginest *bld, bool force = false)
-{
- CHECK_NULL_POINTER(bld);
-
- auto engine = find_engine(bld);
- if (!engine)
- return 0;
-
- if (engine->operator_id != -1 &&
- (world->frame_counter - engine->operator_frame) <= 5)
- {
- auto op_unit = df::unit::find(engine->operator_id);
- if (op_unit)
- return Units::getEffectiveSkill(op_unit, job_skill::SIEGEOPERATE);
- }
-
- if (force)
- {
- color_ostream_proxy out(Core::getInstance().getConsole());
- out.print("Forced siege operator search\n");
-
- auto &active = world->units.active;
- for (size_t i = 0; i < active.size(); i++)
- if (active[i]->pos == engine->center && Units::isCitizen(active[i]))
- return Units::getEffectiveSkill(active[i], job_skill::SIEGEOPERATE);
- }
-
- return 0;
-}
-
-/*
- * Trajectory raytracing
- */
-
-struct ProjectilePath {
- static const int DEFAULT_FUDGE = 31;
-
- df::coord origin, goal, target, fudge_delta;
- int divisor, fudge_factor;
- df::coord speed, direction;
-
- ProjectilePath(df::coord origin, df::coord goal) :
- origin(origin), goal(goal), fudge_factor(1)
- {
- fudge_delta = df::coord(0,0,0);
- calc_line();
- }
-
- ProjectilePath(df::coord origin, df::coord goal, df::coord delta, int factor) :
- origin(origin), goal(goal), fudge_delta(delta), fudge_factor(factor)
- {
- calc_line();
- }
-
- ProjectilePath(df::coord origin, df::coord goal, float zdelta, int factor = DEFAULT_FUDGE) :
- origin(origin), goal(goal), fudge_factor(factor)
- {
- fudge_delta = df::coord(0,0,int(factor * zdelta));
- calc_line();
- }
-
- void calc_line()
- {
- speed = goal - origin;
- speed.x *= fudge_factor;
- speed.y *= fudge_factor;
- speed.z *= fudge_factor;
- speed = speed + fudge_delta;
- target = origin + speed;
- divisor = point_distance(speed);
- if (divisor <= 0) divisor = 1;
- direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1);
- }
-
- df::coord operator[] (int i) const
- {
- int div2 = divisor * 2;
- int bias = divisor-1;
- return origin + df::coord(
- (2*speed.x*i + direction.x*bias)/div2,
- (2*speed.y*i + direction.y*bias)/div2,
- (2*speed.z*i + direction.z*bias)/div2
- );
- }
-};
-
-static ProjectilePath decode_path(lua_State *L, int idx, df::coord origin)
-{
- idx = lua_absindex(L, idx);
-
- Lua::StackUnwinder frame(L);
- df::coord goal;
-
- lua_getfield(L, idx, "target");
- Lua::CheckDFAssign(L, &goal, frame[1]);
-
- lua_getfield(L, idx, "delta");
-
- if (!lua_isnil(L, frame[2]))
- {
- lua_getfield(L, idx, "factor");
- int factor = luaL_optnumber(L, frame[3], ProjectilePath::DEFAULT_FUDGE);
-
- if (lua_isnumber(L, frame[2]))
- return ProjectilePath(origin, goal, lua_tonumber(L, frame[2]), factor);
-
- df::coord delta;
- Lua::CheckDFAssign(L, &delta, frame[2]);
-
- return ProjectilePath(origin, goal, delta, factor);
- }
-
- return ProjectilePath(origin, goal);
-}
-
-static int projPosAtStep(lua_State *L)
-{
- auto engine = find_engine(L, 1);
- auto path = decode_path(L, 2, engine->center);
- int step = luaL_checkint(L, 3);
- Lua::Push(L, path[step]);
- return 1;
-}
-
-static bool isPassableTile(df::coord pos)
-{
- auto ptile = Maps::getTileType(pos);
-
- return !ptile || FlowPassable(*ptile);
-}
-
-static bool isTargetableTile(df::coord pos)
-{
- auto ptile = Maps::getTileType(pos);
-
- return ptile && FlowPassable(*ptile) && !isOpenTerrain(*ptile);
-}
-
-static bool isTreeTile(df::coord pos)
-{
- auto ptile = Maps::getTileType(pos);
-
- return ptile && tileShape(*ptile) == tiletype_shape::TREE;
-}
-
-static bool adjustToTarget(EngineInfo *engine, df::coord *pos)
-{
- if (isTargetableTile(*pos))
- return true;
-
- for (df::coord fudge = *pos;
- fudge.z <= engine->target.second.z; fudge.z++)
- {
- if (!isTargetableTile(fudge))
- continue;
- *pos = fudge;
- return true;
- }
-
- for (df::coord fudge = *pos;
- fudge.z >= engine->target.first.z; fudge.z--)
- {
- if (!isTargetableTile(fudge))
- continue;
- *pos = fudge;
- return true;
- }
-
- return false;
-}
-
-static int adjustToTarget(lua_State *L)
-{
- auto engine = find_engine(L, 1, true);
- df::coord pos;
- Lua::CheckDFAssign(L, &pos, 2);
- bool ok = adjustToTarget(engine, &pos);
- Lua::Push(L, pos);
- Lua::Push(L, ok);
- return 2;
-}
-
-static const char* const hit_type_names[] = {
- "wall", "floor", "ceiling", "map_edge", "tree"
-};
-
-struct PathMetrics {
- enum CollisionType {
- Impassable,
- Floor,
- Ceiling,
- MapEdge,
- Tree
- } hit_type;
-
- int collision_step, collision_z_step;
- int goal_step, goal_z_step, goal_distance;
-
- bool hits() const { return collision_step > goal_step; }
-
- PathMetrics(const ProjectilePath &path)
- {
- compute(path);
- }
-
- void compute(const ProjectilePath &path)
- {
- collision_step = goal_step = goal_z_step = 1000000;
- collision_z_step = 0;
-
- goal_distance = point_distance(path.origin - path.goal);
-
- int step = 0;
- df::coord prev_pos = path.origin;
-
- for (;;) {
- df::coord cur_pos = path[++step];
- if (cur_pos == prev_pos)
- break;
-
- if (cur_pos.z == path.goal.z)
- {
- goal_z_step = std::min(step, goal_z_step);
- if (cur_pos == path.goal)
- goal_step = step;
- }
-
- if (!Maps::isValidTilePos(cur_pos))
- {
- hit_type = PathMetrics::MapEdge;
- break;
- }
-
- if (!isPassableTile(cur_pos))
- {
- if (isTreeTile(cur_pos))
- {
- // The projectile code has a bug where it will
- // hit a tree on the same tick as a Z level change.
- if (cur_pos.z != prev_pos.z)
- {
- hit_type = Tree;
- break;
- }
- }
- else
- {
- hit_type = Impassable;
- break;
- }
- }
-
- if (cur_pos.z != prev_pos.z)
- {
- int top_z = std::max(prev_pos.z, cur_pos.z);
- auto ptile = Maps::getTileType(cur_pos.x, cur_pos.y, top_z);
-
- if (ptile && !LowPassable(*ptile))
- {
- hit_type = (cur_pos.z > prev_pos.z ? Ceiling : Floor);
- break;
- }
-
- collision_z_step = step;
- }
-
- prev_pos = cur_pos;
- }
-
- collision_step = step;
- }
-};
-
-enum TargetTileStatus {
- TARGET_OK, TARGET_RANGE, TARGET_BLOCKED, TARGET_SEMIBLOCKED
-};
-static const char* const target_tile_type_names[] = {
- "ok", "out_of_range", "blocked", "semi_blocked"
-};
-
-static TargetTileStatus calcTileStatus(EngineInfo *engine, const PathMetrics &raytrace)
-{
- if (raytrace.hits())
- {
- if (engine->isInRange(raytrace.goal_step))
- return TARGET_OK;
- else
- return TARGET_RANGE;
- }
- else
- return TARGET_BLOCKED;
-}
-
-static int projPathMetrics(lua_State *L)
-{
- auto engine = find_engine(L, 1);
- auto path = decode_path(L, 2, engine->center);
-
- PathMetrics info(path);
-
- lua_createtable(L, 0, 7);
- Lua::SetField(L, hit_type_names[info.hit_type], -1, "hit_type");
- Lua::SetField(L, info.collision_step, -1, "collision_step");
- Lua::SetField(L, info.collision_z_step, -1, "collision_z_step");
- Lua::SetField(L, info.goal_distance, -1, "goal_distance");
- if (info.goal_step < info.collision_step)
- Lua::SetField(L, info.goal_step, -1, "goal_step");
- if (info.goal_z_step < info.collision_step)
- Lua::SetField(L, info.goal_z_step, -1, "goal_z_step");
- Lua::SetField(L, target_tile_type_names[calcTileStatus(engine, info)], -1, "status");
- return 1;
-}
-
-static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target, float zdelta)
-{
- ProjectilePath path(engine->center, target, zdelta);
- PathMetrics raytrace(path);
- return calcTileStatus(engine, raytrace);
-}
-
-static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target)
-{
- auto status = calcTileStatus(engine, target, 0.0f);
-
- if (status == TARGET_BLOCKED)
- {
- if (calcTileStatus(engine, target, 0.5f) < TARGET_BLOCKED)
- return TARGET_SEMIBLOCKED;
-
- if (calcTileStatus(engine, target, -0.5f) < TARGET_BLOCKED)
- return TARGET_SEMIBLOCKED;
- }
-
- return status;
-}
-
-static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos)
-{
- auto engine = find_engine(bld, true);
- if (!engine)
- return "invalid";
-
- return target_tile_type_names[calcTileStatus(engine, tile_pos)];
-}
-
-static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size)
-{
- auto engine = find_engine(bld, true);
- CHECK_NULL_POINTER(engine);
-
- for (int x = 0; x < size.x; x++)
- {
- for (int y = 0; y < size.y; y++)
- {
- df::coord tile_pos = view + df::coord(x,y,0);
- if (is_in_range(engine->building_rect, tile_pos))
- continue;
-
- Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y);
- if (!cur_tile.valid())
- continue;
-
- int color;
-
- switch (calcTileStatus(engine, tile_pos))
- {
- case TARGET_OK:
- color = COLOR_GREEN;
- break;
- case TARGET_RANGE:
- color = COLOR_CYAN;
- break;
- case TARGET_BLOCKED:
- color = COLOR_RED;
- break;
- case TARGET_SEMIBLOCKED:
- color = COLOR_BROWN;
- break;
- }
-
- if (cur_tile.fg && cur_tile.ch != ' ')
- {
- cur_tile.fg = color;
- cur_tile.bg = 0;
- }
- else
- {
- cur_tile.fg = 0;
- cur_tile.bg = color;
- }
-
- cur_tile.bold = engine->onTarget(tile_pos);
-
- if (cur_tile.tile)
- cur_tile.tile_mode = Pen::CharColor;
-
- Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y);
- }
- }
-}
-
-/*
- * Unit tracking
- */
-
-static const float MAX_TIME = 1000000.0f;
-
-struct UnitPath {
- df::unit *unit;
- std::map path;
-
- struct Hit {
- UnitPath *path;
- df::coord pos;
- int dist;
- float time, lmargin, rmargin;
- };
-
- static std::map cache;
-
- static UnitPath *get(df::unit *unit)
- {
- auto &cv = cache[unit];
- if (!cv) cv = new UnitPath(unit);
- return cv;
- };
-
- UnitPath(df::unit *unit) : unit(unit)
- {
- if (unit->flags1.bits.rider)
- {
- auto mount = df::unit::find(unit->relations.rider_mount_id);
-
- if (mount)
- {
- path = get(mount)->path;
- return;
- }
- }
-
- df::coord pos = unit->pos;
- df::coord dest = unit->path.dest;
- auto &upath = unit->path.path;
-
- if (dest.isValid() && !upath.x.empty())
- {
- 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];
- if (new_pos == pos)
- continue;
-
- float delay = speed;
- if (new_pos.x != pos.x && new_pos.y != pos.y)
- delay *= 362.0/256.0;
-
- path[time] = pos;
- pos = new_pos;
- time += delay + 1;
- }
- }
-
- path[MAX_TIME] = pos;
- }
-
- void get_margin(std::map::iterator &it, float time, float *lmargin, float *rmargin)
- {
- auto it2 = it;
- *lmargin = (it == path.begin()) ? MAX_TIME : time - (--it2)->first;
- *rmargin = (it->first == MAX_TIME) ? MAX_TIME : it->first - time;
- }
-
- df::coord posAtTime(float time, float *lmargin = NULL, float *rmargin = NULL)
- {
- CHECK_INVALID_ARGUMENT(time < MAX_TIME);
-
- auto it = path.upper_bound(time);
- if (lmargin)
- get_margin(it, time, lmargin, rmargin);
- return it->second;
- }
-
- bool findHits(EngineInfo *engine, std::vector *hit_points, float bias)
- {
- df::coord origin = engine->center;
-
- Hit info;
- info.path = this;
-
- for (auto it = path.begin(); it != path.end(); ++it)
- {
- info.pos = it->second;
- info.dist = point_distance(origin - info.pos);
- info.time = float(info.dist)*(engine->proj_speed+1) + engine->hit_delay + bias;
- get_margin(it, info.time, &info.lmargin, &info.rmargin);
-
- if (info.lmargin > 0 && info.rmargin > 0)
- {
- if (engine->onTarget(info.pos) && engine->isInRange(info.dist))
- hit_points->push_back(info);
- }
- }
-
- return !hit_points->empty();
- }
-};
-
-std::map UnitPath::cache;
-
-static void push_margin(lua_State *L, float margin)
-{
- if (margin == MAX_TIME)
- lua_pushnil(L);
- else
- lua_pushnumber(L, margin);
-}
-
-static int traceUnitPath(lua_State *L)
-{
- auto unit = Lua::CheckDFObject(L, 1);
-
- CHECK_NULL_POINTER(unit);
-
- size_t idx = 1;
- auto info = UnitPath::get(unit);
- lua_createtable(L, info->path.size(), 0);
-
- float last_time = 0.0f;
- for (auto it = info->path.begin(); it != info->path.end(); ++it)
- {
- Lua::Push(L, it->second);
- if (idx > 1)
- {
- lua_pushnumber(L, last_time);
- lua_setfield(L, -2, "from");
- }
- if (idx < info->path.size())
- {
- lua_pushnumber(L, it->first);
- lua_setfield(L, -2, "to");
- }
- lua_rawseti(L, -2, idx++);
- last_time = it->first;
- }
-
- return 1;
-}
-
-static int unitPosAtTime(lua_State *L)
-{
- auto unit = Lua::CheckDFObject(L, 1);
- float time = luaL_checknumber(L, 2);
-
- CHECK_NULL_POINTER(unit);
-
- float lmargin, rmargin;
- auto info = UnitPath::get(unit);
-
- Lua::Push(L, info->posAtTime(time, &lmargin, &rmargin));
- push_margin(L, lmargin);
- push_margin(L, rmargin);
- return 3;
-}
-
-static bool canTargetUnit(df::unit *unit)
-{
- CHECK_NULL_POINTER(unit);
-
- if (unit->flags1.bits.dead ||
- unit->flags3.bits.ghostly ||
- unit->flags1.bits.caged ||
- unit->flags1.bits.hidden_in_ambush)
- return false;
-
- return true;
-}
-
-static void proposeUnitHits(EngineInfo *engine, std::vector *hits, float bias)
-{
- auto &active = world->units.active;
-
- for (size_t i = 0; i < active.size(); i++)
- {
- auto unit = active[i];
-
- if (!canTargetUnit(unit))
- continue;
-
- UnitPath::get(unit)->findHits(engine, hits, bias);
- }
-}
-
-static int proposeUnitHits(lua_State *L)
-{
- auto engine = find_engine(L, 1);
- float bias = luaL_optnumber(L, 2, 0);
-
- if (!engine->hasTarget())
- luaL_error(L, "target not set");
-
- std::vector hits;
- proposeUnitHits(engine, &hits, bias);
-
- lua_createtable(L, hits.size(), 0);
-
- for (size_t i = 0; i < hits.size(); i++)
- {
- auto &hit = hits[i];
- lua_createtable(L, 0, 6);
- Lua::SetField(L, hit.path->unit, -1, "unit");
- Lua::SetField(L, hit.pos, -1, "pos");
- Lua::SetField(L, hit.dist, -1, "dist");
- Lua::SetField(L, hit.time, -1, "time");
- push_margin(L, hit.lmargin); lua_setfield(L, -2, "lmargin");
- push_margin(L, hit.rmargin); lua_setfield(L, -2, "rmargin");
- lua_rawseti(L, -2, i+1);
- }
-
- 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
- */
-
-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;
-
- void aimAtPoint(EngineInfo *engine, const ProjectilePath &path)
- {
- 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
- for (int i = 0; i < raytrace.collision_step; i++)
- Maps::ensureTileBlock(path[i]);
-
- // Find valid hit point for catapult stones
- if (flags.bits.high_flying)
- {
- if (raytrace.hits())
- fall_threshold = raytrace.goal_step;
- else
- fall_threshold = (raytrace.collision_z_step+raytrace.collision_step-1)/2;
-
- while (fall_threshold < raytrace.collision_step-1)
- {
- if (isTargetableTile(path[fall_threshold]))
- break;
-
- fall_threshold++;
- }
- }
-
- fall_threshold = std::max(fall_threshold, engine->fire_range.first);
- fall_threshold = std::min(fall_threshold, engine->fire_range.second);
- }
-
- 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;
- df::coord tsize = engine->getTargetSize();
- bool success = false;
-
- for (int i = 0; i < 50; i++)
- {
- target = tbase + df::coord(
- random_int(tsize.x), random_int(tsize.y), random_int(tsize.z)
- );
-
- if (adjustToTarget(engine, &target))
- last_passable = target;
- else
- continue;
-
- ProjectilePath path(engine->center, target, engine->is_catapult ? 0.5f : 0.0f);
- PathMetrics raytrace(path);
-
- if (raytrace.hits() && engine->isInRange(raytrace.goal_step))
- {
- aimAtPoint(engine, skill, path);
- return;
- }
- }
-
- if (!last_passable.isValid())
- last_passable = target;
-
- aimAtPoint(engine, skill, ProjectilePath(engine->center, last_passable));
- }
-
- static int safeAimProjectile(lua_State *L)
- {
- color_ostream &out = *Lua::GetOutput(L);
- auto proj = (projectile_hook*)lua_touserdata(L, 1);
- auto engine = (EngineInfo*)lua_touserdata(L, 2);
- int skill = lua_tointeger(L, 3);
-
- if (!Lua::PushModulePublic(out, L, "plugins.siege-engine", "doAimProjectile"))
- luaL_error(L, "Projectile aiming AI not available");
-
- Lua::PushDFObject(L, engine->bld);
- Lua::Push(L, proj->item);
- Lua::Push(L, engine->target.first);
- Lua::Push(L, engine->target.second);
- Lua::Push(L, skill);
-
- lua_call(L, 5, 1);
-
- if (lua_isnil(L, -1))
- proj->aimAtArea(engine, skill);
- else
- proj->aimAtPoint(engine, skill, decode_path(L, -1, engine->center));
-
- return 0;
- }
-
- void doCheckMovement()
- {
- if (flags.bits.parabolic || distance_flown != 0 ||
- fall_counter != fall_delay || item == NULL)
- return;
-
- auto engine = find_engine(origin_pos);
- if (!engine || !engine->hasTarget())
- return;
-
- auto L = Lua::Core::State;
- CoreSuspendClaimer suspend;
- color_ostream_proxy out(Core::getInstance().getConsole());
-
- int skill = getOperatorSkill(engine->bld, true);
-
- // 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, skill);
- }
-
- switch (item->getType())
- {
- case item_type::CAGE:
- flags.bits.bouncing = false;
- break;
- case item_type::BIN:
- case item_type::BARREL:
- flags.bits.bouncing = false;
- break;
- default:
- break;
- }
- }
-
- void doLaunchContents()
- {
- // Translate cartoon flight speed to parabolic
- 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((float)dist.z);
- normalize(vx, vy, vz);
-
- int start_z = 0;
-
- // Start at tile top, if hit a wall
- ProjectilePath path(origin_pos, target_pos);
- auto next_pos = path[distance_flown+1];
- if (next_pos.z == cur_pos.z && !isPassableTile(next_pos))
- start_z = 49000;
-
- MapExtras::MapCache mc;
- std::vector contents;
- Items::getContainedItems(item, &contents);
-
- 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;
-
- 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;
- proj->flags.bits.unk9 = true;
- proj->flags.bits.no_collide = true;
-
- proj->pos_z = start_z;
-
- float sx, sy, sz;
- random_direction(sx, sy, sz);
- sx += vx*0.7; sy += vy*0.7; sz += vz*0.7;
- if (sz < 0) sz = -sz;
- normalize(sx, sy, sz);
-
- proj->speed_x = int(speed * sx);
- proj->speed_y = int(speed * sy);
- proj->speed_z = std::max(min_zspeed, int(speed * sz));
- }
- }
-
- DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ())
- {
- if (flags.bits.high_flying || flags.bits.piercing)
- doCheckMovement();
-
- return INTERPOSE_NEXT(checkMovement)();
- }
-
- DEFINE_VMETHOD_INTERPOSE(bool, checkImpact, (bool no_damage_floor))
- {
- if (!flags.bits.has_hit_ground && !flags.bits.parabolic &&
- flags.bits.high_flying && !flags.bits.bouncing &&
- !flags.bits.no_impact_destroy && target_pos != origin_pos &&
- item && item->flags.bits.container)
- {
- doLaunchContents();
- }
-
- return INTERPOSE_NEXT(checkImpact)(no_damage_floor);
- }
-};
-
-IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement);
-IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkImpact);
-
-/*
- * Building hook
- */
-
-struct building_hook : df::building_siegeenginest {
- typedef df::building_siegeenginest interpose_base;
-
- DEFINE_VMETHOD_INTERPOSE(df::workshop_profile*, getWorkshopProfile, ())
- {
- if (auto engine = find_engine(this))
- return &engine->profile;
-
- return INTERPOSE_NEXT(getWorkshopProfile)();
- }
-
- DEFINE_VMETHOD_INTERPOSE(df::stockpile_links*, getStockpileLinks, ())
- {
- if (auto engine = find_engine(this))
- {
- update_stockpile_links(engine);
- return &engine->links;
- }
-
- return INTERPOSE_NEXT(getStockpileLinks)();
- }
-
- DEFINE_VMETHOD_INTERPOSE(void, updateAction, ())
- {
- INTERPOSE_NEXT(updateAction)();
-
- if (jobs.empty())
- return;
-
- if (auto engine = find_engine(this))
- {
- auto job = jobs[0];
- bool save_op = false;
-
- switch (job->job_type)
- {
- case job_type::LoadCatapult:
- if (!job->job_items.empty())
- {
- auto item = job->job_items[0];
- item->item_type = engine->ammo_item_type;
- item->vector_id = engine->ammo_vector_id;
-
- switch (item->item_type)
- {
- case item_type::NONE:
- case item_type::BOULDER:
- case item_type::BLOCKS:
- item->mat_type = 0;
- break;
-
- case item_type::BIN:
- case item_type::BARREL:
- item->mat_type = -1;
- // A hack to make it take objects assigned to stockpiles.
- // Since reaction_id is not set, the actual value is not used.
- item->contains.resize(1);
- break;
-
- default:
- item->mat_type = -1;
- break;
- }
- }
- // fallthrough
-
- case job_type::LoadBallista:
- case job_type::FireCatapult:
- case job_type::FireBallista:
- if (auto worker = Job::getWorker(job))
- {
- engine->operator_id = worker->id;
- engine->operator_frame = world->frame_counter;
- }
- break;
-
- default:
- break;
- }
- }
- }
-};
-
-IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getWorkshopProfile);
-IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getStockpileLinks);
-IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction);
-
-/*
- * Initialization
- */
-
-DFHACK_PLUGIN_LUA_FUNCTIONS {
- DFHACK_LUA_FUNCTION(clearTargetArea),
- DFHACK_LUA_FUNCTION(setTargetArea),
- DFHACK_LUA_FUNCTION(isLinkedToPile),
- DFHACK_LUA_FUNCTION(addStockpileLink),
- DFHACK_LUA_FUNCTION(removeStockpileLink),
- DFHACK_LUA_FUNCTION(saveWorkshopProfile),
- DFHACK_LUA_FUNCTION(getTileStatus),
- DFHACK_LUA_FUNCTION(paintAimScreen),
- DFHACK_LUA_FUNCTION(canTargetUnit),
- DFHACK_LUA_FUNCTION(isPassableTile),
- DFHACK_LUA_FUNCTION(isTreeTile),
- DFHACK_LUA_FUNCTION(isTargetableTile),
- DFHACK_LUA_END
-};
-
-DFHACK_PLUGIN_LUA_COMMANDS {
- DFHACK_LUA_COMMAND(getTargetArea),
- DFHACK_LUA_COMMAND(getAmmoItem),
- DFHACK_LUA_COMMAND(setAmmoItem),
- DFHACK_LUA_COMMAND(getStockpileLinks),
- DFHACK_LUA_COMMAND(projPosAtStep),
- DFHACK_LUA_COMMAND(projPathMetrics),
- DFHACK_LUA_COMMAND(adjustToTarget),
- DFHACK_LUA_COMMAND(traceUnitPath),
- DFHACK_LUA_COMMAND(unitPosAtTime),
- DFHACK_LUA_COMMAND(proposeUnitHits),
- DFHACK_LUA_COMMAND(computeNearbyWeight),
- DFHACK_LUA_END
-};
-
-static bool is_enabled = false;
-
-static void enable_hooks(bool enable)
-{
- is_enabled = enable;
-
- INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable);
- INTERPOSE_HOOK(projectile_hook, checkImpact).apply(enable);
-
- INTERPOSE_HOOK(building_hook, getWorkshopProfile).apply(enable);
- INTERPOSE_HOOK(building_hook, getStockpileLinks).apply(enable);
- INTERPOSE_HOOK(building_hook, updateAction).apply(enable);
-
- if (enable)
- load_engines();
- else
- clear_engines();
-}
-
-static bool enable_plugin()
-{
- if (is_enabled)
- return true;
-
- auto pworld = Core::getInstance().getWorld();
- auto entry = pworld->GetPersistentData("siege-engine/enabled", NULL);
- if (!entry.isValid())
- return false;
-
- enable_hooks(true);
- return true;
-}
-
-static void clear_caches(color_ostream &out)
-{
- if (!UnitPath::cache.empty())
- {
- for (auto it = UnitPath::cache.begin(); it != UnitPath::cache.end(); ++it)
- delete it->second;
-
- UnitPath::cache.clear();
- }
-}
-
-DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
-{
- switch (event) {
- case SC_MAP_LOADED:
- {
- auto pworld = Core::getInstance().getWorld();
- bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid();
-
- if (enable)
- {
- out.print("Enabling the siege engine plugin.\n");
- enable_hooks(true);
- }
- else
- enable_hooks(false);
- }
- break;
- case SC_MAP_UNLOADED:
- enable_hooks(false);
- 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;
-}
-
-DFhackCExport command_result plugin_onupdate ( color_ostream &out )
-{
- clear_caches(out);
- return CR_OK;
-}
diff --git a/plugins/siege-engine.cpp b/plugins/siege-engine.cpp
new file mode 100644
index 00000000..3b95aba3
--- /dev/null
+++ b/plugins/siege-engine.cpp
@@ -0,0 +1,1865 @@
+#include "Core.h"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include "df/graphic.h"
+#include "df/building_siegeenginest.h"
+#include "df/builtin_mats.h"
+#include "df/world.h"
+#include "df/buildings_other_id.h"
+#include "df/job.h"
+#include "df/building_drawbuffer.h"
+#include "df/ui.h"
+#include "df/viewscreen_dwarfmodest.h"
+#include "df/ui_build_selector.h"
+#include "df/flow_info.h"
+#include "df/report.h"
+#include "df/proj_itemst.h"
+#include "df/unit.h"
+#include "df/unit_soul.h"
+#include "df/unit_skill.h"
+#include "df/physical_attribute_type.h"
+#include "df/creature_raw.h"
+#include "df/caste_raw.h"
+#include "df/caste_raw_flags.h"
+#include "df/assumed_identity.h"
+#include "df/game_mode.h"
+#include "df/unit_misc_trait.h"
+#include "df/job.h"
+#include "df/job_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"
+
+using std::vector;
+using std::string;
+using std::stack;
+using namespace DFHack;
+using namespace df::enums;
+
+using df::global::gamemode;
+using df::global::gps;
+using df::global::world;
+using df::global::ui;
+using df::global::ui_build_selector;
+
+using Screen::Pen;
+
+DFHACK_PLUGIN("siege-engine");
+
+/*
+ * Misc. utils
+ */
+
+typedef std::pair coord_range;
+
+static void set_range(coord_range *target, df::coord p1, df::coord p2)
+{
+ if (!p1.isValid() || !p2.isValid())
+ {
+ *target = coord_range();
+ }
+ else
+ {
+ target->first.x = std::min(p1.x, p2.x);
+ target->first.y = std::min(p1.y, p2.y);
+ target->first.z = std::min(p1.z, p2.z);
+ target->second.x = std::max(p1.x, p2.x);
+ target->second.y = std::max(p1.y, p2.y);
+ target->second.z = std::max(p1.z, p2.z);
+ }
+}
+
+static bool is_range_valid(const coord_range &target)
+{
+ return target.first.isValid() && target.second.isValid();
+}
+
+static bool is_in_range(const coord_range &target, df::coord pos)
+{
+ return target.first.isValid() && target.second.isValid() &&
+ target.first.x <= pos.x && pos.x <= target.second.x &&
+ target.first.y <= pos.y && pos.y <= target.second.y &&
+ target.first.z <= pos.z && pos.z <= target.second.z;
+}
+
+static std::pair get_engine_range(df::building_siegeenginest *bld)
+{
+ if (bld->type == siegeengine_type::Ballista)
+ return std::make_pair(1, 200);
+ else
+ return std::make_pair(30, 100);
+}
+
+static void orient_engine(df::building_siegeenginest *bld, df::coord target)
+{
+ int dx = target.x - bld->centerx;
+ int dy = target.y - bld->centery;
+
+ if (abs(dx) > abs(dy))
+ bld->facing = (dx > 0) ?
+ df::building_siegeenginest::Right :
+ df::building_siegeenginest::Left;
+ else
+ bld->facing = (dy > 0) ?
+ df::building_siegeenginest::Down :
+ df::building_siegeenginest::Up;
+}
+
+static int random_int(int val)
+{
+ return int(int64_t(rand())*val/RAND_MAX);
+}
+
+static int point_distance(df::coord speed)
+{
+ return std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z)));
+}
+
+inline void normalize(float &x, float &y, float &z)
+{
+ float dist = sqrtf(x*x + y*y + z*z);
+ if (dist == 0.0f) return;
+ x /= dist; y /= dist; z /= dist;
+}
+
+static void random_direction(float &x, float &y, float &z)
+{
+ float a, b, d;
+ for (;;) {
+ a = (rand() + 0.5f)*2.0f/RAND_MAX - 1.0f;
+ b = (rand() + 0.5f)*2.0f/RAND_MAX - 1.0f;
+ d = a*a + b*b;
+ if (d < 1.0f)
+ break;
+ }
+
+ float sq = sqrtf(1-d);
+ x = 2.0f*a*sq;
+ y = 2.0f*b*sq;
+ 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
+ */
+
+static bool enable_plugin();
+
+struct EngineInfo {
+ int id;
+ df::building_siegeenginest *bld;
+
+ df::coord center;
+ coord_range building_rect;
+
+ bool is_catapult;
+ int proj_speed, hit_delay;
+ std::pair fire_range;
+
+ coord_range target;
+
+ df::job_item_vector_id ammo_vector_id;
+ df::item_type ammo_item_type;
+
+ int operator_id, operator_frame;
+
+ std::set stockpiles;
+ df::stockpile_links links;
+ df::workshop_profile profile;
+
+ bool hasTarget() { return is_range_valid(target); }
+ bool onTarget(df::coord pos) { return is_in_range(target, pos); }
+ df::coord getTargetSize() { return target.second - target.first; }
+
+ bool isInRange(int dist) {
+ return dist >= fire_range.first && dist <= fire_range.second;
+ }
+};
+
+static std::map engines;
+static std::map coord_engines;
+
+static EngineInfo *find_engine(df::building *bld, bool create = false)
+{
+ auto ebld = strict_virtual_cast(bld);
+ if (!ebld)
+ return NULL;
+
+ auto &obj = engines[bld];
+
+ if (obj)
+ {
+ obj->bld = ebld;
+ return obj;
+ }
+
+ if (!create)
+ return NULL;
+
+ obj = new EngineInfo();
+
+ obj->id = bld->id;
+ obj->bld = ebld;
+ obj->center = df::coord(bld->centerx, bld->centery, bld->z);
+ obj->building_rect = coord_range(
+ df::coord(bld->x1, bld->y1, bld->z),
+ df::coord(bld->x2, bld->y2, bld->z)
+ );
+ obj->is_catapult = (ebld->type == siegeengine_type::Catapult);
+ obj->proj_speed = 2;
+ obj->hit_delay = obj->is_catapult ? 2 : -1;
+ obj->fire_range = get_engine_range(ebld);
+
+ obj->ammo_vector_id = job_item_vector_id::BOULDER;
+ obj->ammo_item_type = item_type::BOULDER;
+
+ obj->operator_id = obj->operator_frame = -1;
+
+ coord_engines[obj->center] = bld;
+ return obj;
+}
+
+static EngineInfo *find_engine(lua_State *L, int idx, bool create = false, bool silent = false)
+{
+ auto bld = Lua::CheckDFObject(L, idx);
+
+ auto engine = find_engine(bld, create);
+ if (!engine && !silent)
+ luaL_error(L, "no such engine");
+
+ return engine;
+}
+
+static EngineInfo *find_engine(df::coord pos)
+{
+ auto engine = find_engine(coord_engines[pos]);
+
+ if (engine)
+ {
+ auto bld0 = df::building::find(engine->id);
+ auto bld = strict_virtual_cast(bld0);
+ if (!bld)
+ return NULL;
+
+ engine->bld = bld;
+ }
+
+ return engine;
+}
+
+/*
+ * Configuration management
+ */
+
+static void clear_engines()
+{
+ for (auto it = engines.begin(); it != engines.end(); ++it)
+ delete it->second;
+ engines.clear();
+ coord_engines.clear();
+}
+
+static void load_engines()
+{
+ clear_engines();
+
+ auto pworld = Core::getInstance().getWorld();
+ std::vector vec;
+
+ pworld->GetPersistentData(&vec, "siege-engine/target/", true);
+ for (auto it = vec.begin(); it != vec.end(); ++it)
+ {
+ auto engine = find_engine(df::building::find(it->ival(0)), true);
+ if (!engine) continue;
+ engine->target.first = df::coord(it->ival(1), it->ival(2), it->ival(3));
+ engine->target.second = df::coord(it->ival(4), it->ival(5), it->ival(6));
+ }
+
+ pworld->GetPersistentData(&vec, "siege-engine/ammo/", true);
+ for (auto it = vec.begin(); it != vec.end(); ++it)
+ {
+ auto engine = find_engine(df::building::find(it->ival(0)), true);
+ if (!engine) continue;
+ engine->ammo_vector_id = (df::job_item_vector_id)it->ival(1);
+ engine->ammo_item_type = (df::item_type)it->ival(2);
+ }
+
+ pworld->GetPersistentData(&vec, "siege-engine/stockpiles/", true);
+ for (auto it = vec.begin(); it != vec.end(); ++it)
+ {
+ auto engine = find_engine(df::building::find(it->ival(0)), true);
+ if (!engine)
+ continue;
+ auto pile = df::building::find(it->ival(1));
+ if (!pile || pile->getType() != building_type::Stockpile)
+ {
+ pworld->DeletePersistentData(*it);
+ continue;;
+ }
+
+ engine->stockpiles.insert(it->ival(1));
+ }
+
+ pworld->GetPersistentData(&vec, "siege-engine/profiles/", true);
+ for (auto it = vec.begin(); it != vec.end(); ++it)
+ {
+ auto engine = find_engine(df::building::find(it->ival(0)), true);
+ if (!engine) continue;
+ engine->profile.min_level = it->ival(1);
+ engine->profile.max_level = it->ival(2);
+ }
+
+ pworld->GetPersistentData(&vec, "siege-engine/profile-workers/", true);
+ for (auto it = vec.begin(); it != vec.end(); ++it)
+ {
+ auto engine = find_engine(df::building::find(it->ival(0)), true);
+ if (!engine)
+ continue;
+ auto unit = df::unit::find(it->ival(1));
+ if (!unit || !Units::isCitizen(unit))
+ {
+ pworld->DeletePersistentData(*it);
+ continue;
+ }
+ engine->profile.permitted_workers.push_back(it->ival(1));
+ }
+}
+
+static int getTargetArea(lua_State *L)
+{
+ auto engine = find_engine(L, 1, false, true);
+
+ if (engine && engine->hasTarget())
+ {
+ Lua::Push(L, engine->target.first);
+ Lua::Push(L, engine->target.second);
+ }
+ else
+ {
+ lua_pushnil(L);
+ lua_pushnil(L);
+ }
+
+ return 2;
+}
+
+static void clearTargetArea(df::building_siegeenginest *bld)
+{
+ CHECK_NULL_POINTER(bld);
+
+ if (auto engine = find_engine(bld))
+ engine->target = coord_range();
+
+ auto pworld = Core::getInstance().getWorld();
+ auto key = stl_sprintf("siege-engine/target/%d", bld->id);
+ pworld->DeletePersistentData(pworld->GetPersistentData(key));
+}
+
+static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, df::coord target_max)
+{
+ CHECK_NULL_POINTER(bld);
+ CHECK_INVALID_ARGUMENT(target_min.isValid() && target_max.isValid());
+
+ if (!enable_plugin())
+ return false;
+
+ auto pworld = Core::getInstance().getWorld();
+ auto key = stl_sprintf("siege-engine/target/%d", bld->id);
+ auto entry = pworld->GetPersistentData(key, NULL);
+ if (!entry.isValid())
+ return false;
+
+ auto engine = find_engine(bld, true);
+
+ set_range(&engine->target, target_min, target_max);
+
+ entry.ival(0) = bld->id;
+ entry.ival(1) = engine->target.first.x;
+ entry.ival(2) = engine->target.first.y;
+ entry.ival(3) = engine->target.first.z;
+ entry.ival(4) = engine->target.second.x;
+ entry.ival(5) = engine->target.second.y;
+ entry.ival(6) = engine->target.second.z;
+
+ df::coord sum = target_min + target_max;
+ orient_engine(bld, df::coord(sum.x/2, sum.y/2, sum.z/2));
+
+ return true;
+}
+
+static int getAmmoItem(lua_State *L)
+{
+ auto engine = find_engine(L, 1, false, true);
+ if (!engine)
+ Lua::Push(L, item_type::BOULDER);
+ else
+ Lua::Push(L, engine->ammo_item_type);
+ return 1;
+}
+
+static int setAmmoItem(lua_State *L)
+{
+ if (!enable_plugin())
+ return 0;
+
+ auto engine = find_engine(L, 1, true);
+ auto item_type = (df::item_type)luaL_optint(L, 2, item_type::BOULDER);
+ if (!is_valid_enum_item(item_type))
+ luaL_argerror(L, 2, "invalid item type");
+
+ auto pworld = Core::getInstance().getWorld();
+ auto key = stl_sprintf("siege-engine/ammo/%d", engine->id);
+ auto entry = pworld->GetPersistentData(key, NULL);
+ if (!entry.isValid())
+ return 0;
+
+ engine->ammo_vector_id = job_item_vector_id::ANY_FREE;
+ engine->ammo_item_type = item_type;
+
+ FOR_ENUM_ITEMS(job_item_vector_id, id)
+ {
+ auto other = ENUM_ATTR(job_item_vector_id, other, id);
+ auto type = ENUM_ATTR(items_other_id, item, other);
+ if (type == item_type)
+ {
+ engine->ammo_vector_id = id;
+ break;
+ }
+ }
+
+ entry.ival(0) = engine->id;
+ entry.ival(1) = engine->ammo_vector_id;
+ entry.ival(2) = engine->ammo_item_type;
+
+ lua_pushboolean(L, true);
+ return 1;
+}
+
+static void forgetStockpileLink(EngineInfo *engine, int pile_id)
+{
+ engine->stockpiles.erase(pile_id);
+
+ auto pworld = Core::getInstance().getWorld();
+ auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", engine->id, pile_id);
+ pworld->DeletePersistentData(pworld->GetPersistentData(key));
+}
+
+static void update_stockpile_links(EngineInfo *engine)
+{
+ engine->links.take_from_pile.clear();
+
+ for (auto it = engine->stockpiles.begin(); it != engine->stockpiles.end(); )
+ {
+ int id = *it; ++it;
+ auto pile = df::building::find(id);
+
+ if (!pile || pile->getType() != building_type::Stockpile)
+ forgetStockpileLink(engine, id);
+ else
+ // The vector is sorted, but we are iterating through a sorted set
+ engine->links.take_from_pile.push_back(pile);
+ }
+}
+
+static int getStockpileLinks(lua_State *L)
+{
+ auto engine = find_engine(L, 1, false, true);
+ if (!engine || engine->stockpiles.empty())
+ return 0;
+
+ update_stockpile_links(engine);
+
+ auto &links = engine->links.take_from_pile;
+ lua_createtable(L, links.size(), 0);
+
+ for (size_t i = 0; i < links.size(); i++)
+ {
+ Lua::Push(L, links[i]);
+ lua_rawseti(L, -2, i+1);
+ }
+
+ return 1;
+}
+
+static bool isLinkedToPile(df::building_siegeenginest *bld, df::building_stockpilest *pile)
+{
+ CHECK_NULL_POINTER(bld);
+ CHECK_NULL_POINTER(pile);
+
+ auto engine = find_engine(bld);
+
+ return engine && engine->stockpiles.count(pile->id);
+}
+
+static bool addStockpileLink(df::building_siegeenginest *bld, df::building_stockpilest *pile)
+{
+ CHECK_NULL_POINTER(bld);
+ CHECK_NULL_POINTER(pile);
+
+ if (!enable_plugin())
+ return false;
+
+ auto pworld = Core::getInstance().getWorld();
+ auto key = stl_sprintf("siege-engine/stockpiles/%d/%d", bld->id, pile->id);
+ auto entry = pworld->GetPersistentData(key, NULL);
+ if (!entry.isValid())
+ return false;
+
+ auto engine = find_engine(bld, true);
+
+ entry.ival(0) = bld->id;
+ entry.ival(1) = pile->id;
+
+ engine->stockpiles.insert(pile->id);
+ return true;
+}
+
+static bool removeStockpileLink(df::building_siegeenginest *bld, df::building_stockpilest *pile)
+{
+ CHECK_NULL_POINTER(bld);
+ CHECK_NULL_POINTER(pile);
+
+ if (auto engine = find_engine(bld))
+ {
+ forgetStockpileLink(engine, pile->id);
+ return true;
+ }
+
+ return false;
+}
+
+static df::workshop_profile *saveWorkshopProfile(df::building_siegeenginest *bld)
+{
+ CHECK_NULL_POINTER(bld);
+
+ if (!enable_plugin())
+ return NULL;
+
+ // Save skill limits
+ auto pworld = Core::getInstance().getWorld();
+ auto key = stl_sprintf("siege-engine/profiles/%d", bld->id);
+ auto entry = pworld->GetPersistentData(key, NULL);
+ if (!entry.isValid())
+ return NULL;
+
+ auto engine = find_engine(bld, true);
+
+ entry.ival(0) = engine->id;
+ entry.ival(1) = engine->profile.min_level;
+ entry.ival(2) = engine->profile.max_level;
+
+ // Save worker list
+ std::vector vec;
+ auto &workers = engine->profile.permitted_workers;
+
+ key = stl_sprintf("siege-engine/profile-workers/%d", bld->id);
+ pworld->GetPersistentData(&vec, key, true);
+
+ for (auto it = vec.begin(); it != vec.end(); ++it)
+ {
+ if (linear_index(workers, it->ival(1)) < 0)
+ pworld->DeletePersistentData(*it);
+ }
+
+ for (size_t i = 0; i < workers.size(); i++)
+ {
+ key = stl_sprintf("siege-engine/profile-workers/%d/%d", bld->id, workers[i]);
+ entry = pworld->GetPersistentData(key, NULL);
+ if (!entry.isValid())
+ continue;
+ entry.ival(0) = engine->id;
+ entry.ival(1) = workers[i];
+ }
+
+ return &engine->profile;
+}
+
+static int getOperatorSkill(df::building_siegeenginest *bld, bool force = false)
+{
+ CHECK_NULL_POINTER(bld);
+
+ auto engine = find_engine(bld);
+ if (!engine)
+ return 0;
+
+ if (engine->operator_id != -1 &&
+ (world->frame_counter - engine->operator_frame) <= 5)
+ {
+ auto op_unit = df::unit::find(engine->operator_id);
+ if (op_unit)
+ return Units::getEffectiveSkill(op_unit, job_skill::SIEGEOPERATE);
+ }
+
+ if (force)
+ {
+ color_ostream_proxy out(Core::getInstance().getConsole());
+ out.print("Forced siege operator search\n");
+
+ auto &active = world->units.active;
+ for (size_t i = 0; i < active.size(); i++)
+ if (active[i]->pos == engine->center && Units::isCitizen(active[i]))
+ return Units::getEffectiveSkill(active[i], job_skill::SIEGEOPERATE);
+ }
+
+ return 0;
+}
+
+/*
+ * Trajectory raytracing
+ */
+
+struct ProjectilePath {
+ static const int DEFAULT_FUDGE = 31;
+
+ df::coord origin, goal, target, fudge_delta;
+ int divisor, fudge_factor;
+ df::coord speed, direction;
+
+ ProjectilePath(df::coord origin, df::coord goal) :
+ origin(origin), goal(goal), fudge_factor(1)
+ {
+ fudge_delta = df::coord(0,0,0);
+ calc_line();
+ }
+
+ ProjectilePath(df::coord origin, df::coord goal, df::coord delta, int factor) :
+ origin(origin), goal(goal), fudge_delta(delta), fudge_factor(factor)
+ {
+ calc_line();
+ }
+
+ ProjectilePath(df::coord origin, df::coord goal, float zdelta, int factor = DEFAULT_FUDGE) :
+ origin(origin), goal(goal), fudge_factor(factor)
+ {
+ fudge_delta = df::coord(0,0,int(factor * zdelta));
+ calc_line();
+ }
+
+ void calc_line()
+ {
+ speed = goal - origin;
+ speed.x *= fudge_factor;
+ speed.y *= fudge_factor;
+ speed.z *= fudge_factor;
+ speed = speed + fudge_delta;
+ target = origin + speed;
+ divisor = point_distance(speed);
+ if (divisor <= 0) divisor = 1;
+ direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1);
+ }
+
+ df::coord operator[] (int i) const
+ {
+ int div2 = divisor * 2;
+ int bias = divisor-1;
+ return origin + df::coord(
+ (2*speed.x*i + direction.x*bias)/div2,
+ (2*speed.y*i + direction.y*bias)/div2,
+ (2*speed.z*i + direction.z*bias)/div2
+ );
+ }
+};
+
+static ProjectilePath decode_path(lua_State *L, int idx, df::coord origin)
+{
+ idx = lua_absindex(L, idx);
+
+ Lua::StackUnwinder frame(L);
+ df::coord goal;
+
+ lua_getfield(L, idx, "target");
+ Lua::CheckDFAssign(L, &goal, frame[1]);
+
+ lua_getfield(L, idx, "delta");
+
+ if (!lua_isnil(L, frame[2]))
+ {
+ lua_getfield(L, idx, "factor");
+ int factor = luaL_optnumber(L, frame[3], ProjectilePath::DEFAULT_FUDGE);
+
+ if (lua_isnumber(L, frame[2]))
+ return ProjectilePath(origin, goal, lua_tonumber(L, frame[2]), factor);
+
+ df::coord delta;
+ Lua::CheckDFAssign(L, &delta, frame[2]);
+
+ return ProjectilePath(origin, goal, delta, factor);
+ }
+
+ return ProjectilePath(origin, goal);
+}
+
+static int projPosAtStep(lua_State *L)
+{
+ auto engine = find_engine(L, 1);
+ auto path = decode_path(L, 2, engine->center);
+ int step = luaL_checkint(L, 3);
+ Lua::Push(L, path[step]);
+ return 1;
+}
+
+static bool isPassableTile(df::coord pos)
+{
+ auto ptile = Maps::getTileType(pos);
+
+ return !ptile || FlowPassable(*ptile);
+}
+
+static bool isTargetableTile(df::coord pos)
+{
+ auto ptile = Maps::getTileType(pos);
+
+ return ptile && FlowPassable(*ptile) && !isOpenTerrain(*ptile);
+}
+
+static bool isTreeTile(df::coord pos)
+{
+ auto ptile = Maps::getTileType(pos);
+
+ return ptile && tileShape(*ptile) == tiletype_shape::TREE;
+}
+
+static bool adjustToTarget(EngineInfo *engine, df::coord *pos)
+{
+ if (isTargetableTile(*pos))
+ return true;
+
+ for (df::coord fudge = *pos;
+ fudge.z <= engine->target.second.z; fudge.z++)
+ {
+ if (!isTargetableTile(fudge))
+ continue;
+ *pos = fudge;
+ return true;
+ }
+
+ for (df::coord fudge = *pos;
+ fudge.z >= engine->target.first.z; fudge.z--)
+ {
+ if (!isTargetableTile(fudge))
+ continue;
+ *pos = fudge;
+ return true;
+ }
+
+ return false;
+}
+
+static int adjustToTarget(lua_State *L)
+{
+ auto engine = find_engine(L, 1, true);
+ df::coord pos;
+ Lua::CheckDFAssign(L, &pos, 2);
+ bool ok = adjustToTarget(engine, &pos);
+ Lua::Push(L, pos);
+ Lua::Push(L, ok);
+ return 2;
+}
+
+static const char* const hit_type_names[] = {
+ "wall", "floor", "ceiling", "map_edge", "tree"
+};
+
+struct PathMetrics {
+ enum CollisionType {
+ Impassable,
+ Floor,
+ Ceiling,
+ MapEdge,
+ Tree
+ } hit_type;
+
+ int collision_step, collision_z_step;
+ int goal_step, goal_z_step, goal_distance;
+
+ bool hits() const { return collision_step > goal_step; }
+
+ PathMetrics(const ProjectilePath &path)
+ {
+ compute(path);
+ }
+
+ void compute(const ProjectilePath &path)
+ {
+ collision_step = goal_step = goal_z_step = 1000000;
+ collision_z_step = 0;
+
+ goal_distance = point_distance(path.origin - path.goal);
+
+ int step = 0;
+ df::coord prev_pos = path.origin;
+
+ for (;;) {
+ df::coord cur_pos = path[++step];
+ if (cur_pos == prev_pos)
+ break;
+
+ if (cur_pos.z == path.goal.z)
+ {
+ goal_z_step = std::min(step, goal_z_step);
+ if (cur_pos == path.goal)
+ goal_step = step;
+ }
+
+ if (!Maps::isValidTilePos(cur_pos))
+ {
+ hit_type = PathMetrics::MapEdge;
+ break;
+ }
+
+ if (!isPassableTile(cur_pos))
+ {
+ if (isTreeTile(cur_pos))
+ {
+ // The projectile code has a bug where it will
+ // hit a tree on the same tick as a Z level change.
+ if (cur_pos.z != prev_pos.z)
+ {
+ hit_type = Tree;
+ break;
+ }
+ }
+ else
+ {
+ hit_type = Impassable;
+ break;
+ }
+ }
+
+ if (cur_pos.z != prev_pos.z)
+ {
+ int top_z = std::max(prev_pos.z, cur_pos.z);
+ auto ptile = Maps::getTileType(cur_pos.x, cur_pos.y, top_z);
+
+ if (ptile && !LowPassable(*ptile))
+ {
+ hit_type = (cur_pos.z > prev_pos.z ? Ceiling : Floor);
+ break;
+ }
+
+ collision_z_step = step;
+ }
+
+ prev_pos = cur_pos;
+ }
+
+ collision_step = step;
+ }
+};
+
+enum TargetTileStatus {
+ TARGET_OK, TARGET_RANGE, TARGET_BLOCKED, TARGET_SEMIBLOCKED
+};
+static const char* const target_tile_type_names[] = {
+ "ok", "out_of_range", "blocked", "semi_blocked"
+};
+
+static TargetTileStatus calcTileStatus(EngineInfo *engine, const PathMetrics &raytrace)
+{
+ if (raytrace.hits())
+ {
+ if (engine->isInRange(raytrace.goal_step))
+ return TARGET_OK;
+ else
+ return TARGET_RANGE;
+ }
+ else
+ return TARGET_BLOCKED;
+}
+
+static int projPathMetrics(lua_State *L)
+{
+ auto engine = find_engine(L, 1);
+ auto path = decode_path(L, 2, engine->center);
+
+ PathMetrics info(path);
+
+ lua_createtable(L, 0, 7);
+ Lua::SetField(L, hit_type_names[info.hit_type], -1, "hit_type");
+ Lua::SetField(L, info.collision_step, -1, "collision_step");
+ Lua::SetField(L, info.collision_z_step, -1, "collision_z_step");
+ Lua::SetField(L, info.goal_distance, -1, "goal_distance");
+ if (info.goal_step < info.collision_step)
+ Lua::SetField(L, info.goal_step, -1, "goal_step");
+ if (info.goal_z_step < info.collision_step)
+ Lua::SetField(L, info.goal_z_step, -1, "goal_z_step");
+ Lua::SetField(L, target_tile_type_names[calcTileStatus(engine, info)], -1, "status");
+ return 1;
+}
+
+static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target, float zdelta)
+{
+ ProjectilePath path(engine->center, target, zdelta);
+ PathMetrics raytrace(path);
+ return calcTileStatus(engine, raytrace);
+}
+
+static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target)
+{
+ auto status = calcTileStatus(engine, target, 0.0f);
+
+ if (status == TARGET_BLOCKED)
+ {
+ if (calcTileStatus(engine, target, 0.5f) < TARGET_BLOCKED)
+ return TARGET_SEMIBLOCKED;
+
+ if (calcTileStatus(engine, target, -0.5f) < TARGET_BLOCKED)
+ return TARGET_SEMIBLOCKED;
+ }
+
+ return status;
+}
+
+static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos)
+{
+ auto engine = find_engine(bld, true);
+ if (!engine)
+ return "invalid";
+
+ return target_tile_type_names[calcTileStatus(engine, tile_pos)];
+}
+
+static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size)
+{
+ auto engine = find_engine(bld, true);
+ CHECK_NULL_POINTER(engine);
+
+ for (int x = 0; x < size.x; x++)
+ {
+ for (int y = 0; y < size.y; y++)
+ {
+ df::coord tile_pos = view + df::coord(x,y,0);
+ if (is_in_range(engine->building_rect, tile_pos))
+ continue;
+
+ Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y);
+ if (!cur_tile.valid())
+ continue;
+
+ int color;
+
+ switch (calcTileStatus(engine, tile_pos))
+ {
+ case TARGET_OK:
+ color = COLOR_GREEN;
+ break;
+ case TARGET_RANGE:
+ color = COLOR_CYAN;
+ break;
+ case TARGET_BLOCKED:
+ color = COLOR_RED;
+ break;
+ case TARGET_SEMIBLOCKED:
+ color = COLOR_BROWN;
+ break;
+ }
+
+ if (cur_tile.fg && cur_tile.ch != ' ')
+ {
+ cur_tile.fg = color;
+ cur_tile.bg = 0;
+ }
+ else
+ {
+ cur_tile.fg = 0;
+ cur_tile.bg = color;
+ }
+
+ cur_tile.bold = engine->onTarget(tile_pos);
+
+ if (cur_tile.tile)
+ cur_tile.tile_mode = Pen::CharColor;
+
+ Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y);
+ }
+ }
+}
+
+/*
+ * Unit tracking
+ */
+
+static const float MAX_TIME = 1000000.0f;
+
+struct UnitPath {
+ df::unit *unit;
+ std::map path;
+
+ struct Hit {
+ UnitPath *path;
+ df::coord pos;
+ int dist;
+ float time, lmargin, rmargin;
+ };
+
+ static std::map cache;
+
+ static UnitPath *get(df::unit *unit)
+ {
+ auto &cv = cache[unit];
+ if (!cv) cv = new UnitPath(unit);
+ return cv;
+ };
+
+ UnitPath(df::unit *unit) : unit(unit)
+ {
+ if (unit->flags1.bits.rider)
+ {
+ auto mount = df::unit::find(unit->relations.rider_mount_id);
+
+ if (mount)
+ {
+ path = get(mount)->path;
+ return;
+ }
+ }
+
+ df::coord pos = unit->pos;
+ df::coord dest = unit->path.dest;
+ auto &upath = unit->path.path;
+
+ if (dest.isValid() && !upath.x.empty())
+ {
+ 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];
+ if (new_pos == pos)
+ continue;
+
+ float delay = speed;
+ if (new_pos.x != pos.x && new_pos.y != pos.y)
+ delay *= 362.0/256.0;
+
+ path[time] = pos;
+ pos = new_pos;
+ time += delay + 1;
+ }
+ }
+
+ path[MAX_TIME] = pos;
+ }
+
+ void get_margin(std::map::iterator &it, float time, float *lmargin, float *rmargin)
+ {
+ auto it2 = it;
+ *lmargin = (it == path.begin()) ? MAX_TIME : time - (--it2)->first;
+ *rmargin = (it->first == MAX_TIME) ? MAX_TIME : it->first - time;
+ }
+
+ df::coord posAtTime(float time, float *lmargin = NULL, float *rmargin = NULL)
+ {
+ CHECK_INVALID_ARGUMENT(time < MAX_TIME);
+
+ auto it = path.upper_bound(time);
+ if (lmargin)
+ get_margin(it, time, lmargin, rmargin);
+ return it->second;
+ }
+
+ bool findHits(EngineInfo *engine, std::vector *hit_points, float bias)
+ {
+ df::coord origin = engine->center;
+
+ Hit info;
+ info.path = this;
+
+ for (auto it = path.begin(); it != path.end(); ++it)
+ {
+ info.pos = it->second;
+ info.dist = point_distance(origin - info.pos);
+ info.time = float(info.dist)*(engine->proj_speed+1) + engine->hit_delay + bias;
+ get_margin(it, info.time, &info.lmargin, &info.rmargin);
+
+ if (info.lmargin > 0 && info.rmargin > 0)
+ {
+ if (engine->onTarget(info.pos) && engine->isInRange(info.dist))
+ hit_points->push_back(info);
+ }
+ }
+
+ return !hit_points->empty();
+ }
+};
+
+std::map UnitPath::cache;
+
+static void push_margin(lua_State *L, float margin)
+{
+ if (margin == MAX_TIME)
+ lua_pushnil(L);
+ else
+ lua_pushnumber(L, margin);
+}
+
+static int traceUnitPath(lua_State *L)
+{
+ auto unit = Lua::CheckDFObject(L, 1);
+
+ CHECK_NULL_POINTER(unit);
+
+ size_t idx = 1;
+ auto info = UnitPath::get(unit);
+ lua_createtable(L, info->path.size(), 0);
+
+ float last_time = 0.0f;
+ for (auto it = info->path.begin(); it != info->path.end(); ++it)
+ {
+ Lua::Push(L, it->second);
+ if (idx > 1)
+ {
+ lua_pushnumber(L, last_time);
+ lua_setfield(L, -2, "from");
+ }
+ if (idx < info->path.size())
+ {
+ lua_pushnumber(L, it->first);
+ lua_setfield(L, -2, "to");
+ }
+ lua_rawseti(L, -2, idx++);
+ last_time = it->first;
+ }
+
+ return 1;
+}
+
+static int unitPosAtTime(lua_State *L)
+{
+ auto unit = Lua::CheckDFObject(L, 1);
+ float time = luaL_checknumber(L, 2);
+
+ CHECK_NULL_POINTER(unit);
+
+ float lmargin, rmargin;
+ auto info = UnitPath::get(unit);
+
+ Lua::Push(L, info->posAtTime(time, &lmargin, &rmargin));
+ push_margin(L, lmargin);
+ push_margin(L, rmargin);
+ return 3;
+}
+
+static bool canTargetUnit(df::unit *unit)
+{
+ CHECK_NULL_POINTER(unit);
+
+ if (unit->flags1.bits.dead ||
+ unit->flags3.bits.ghostly ||
+ unit->flags1.bits.caged ||
+ unit->flags1.bits.hidden_in_ambush)
+ return false;
+
+ return true;
+}
+
+static void proposeUnitHits(EngineInfo *engine, std::vector *hits, float bias)
+{
+ auto &active = world->units.active;
+
+ for (size_t i = 0; i < active.size(); i++)
+ {
+ auto unit = active[i];
+
+ if (!canTargetUnit(unit))
+ continue;
+
+ UnitPath::get(unit)->findHits(engine, hits, bias);
+ }
+}
+
+static int proposeUnitHits(lua_State *L)
+{
+ auto engine = find_engine(L, 1);
+ float bias = luaL_optnumber(L, 2, 0);
+
+ if (!engine->hasTarget())
+ luaL_error(L, "target not set");
+
+ std::vector hits;
+ proposeUnitHits(engine, &hits, bias);
+
+ lua_createtable(L, hits.size(), 0);
+
+ for (size_t i = 0; i < hits.size(); i++)
+ {
+ auto &hit = hits[i];
+ lua_createtable(L, 0, 6);
+ Lua::SetField(L, hit.path->unit, -1, "unit");
+ Lua::SetField(L, hit.pos, -1, "pos");
+ Lua::SetField(L, hit.dist, -1, "dist");
+ Lua::SetField(L, hit.time, -1, "time");
+ push_margin(L, hit.lmargin); lua_setfield(L, -2, "lmargin");
+ push_margin(L, hit.rmargin); lua_setfield(L, -2, "rmargin");
+ lua_rawseti(L, -2, i+1);
+ }
+
+ 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
+ */
+
+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;
+
+ void aimAtPoint(EngineInfo *engine, const ProjectilePath &path)
+ {
+ 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
+ for (int i = 0; i < raytrace.collision_step; i++)
+ Maps::ensureTileBlock(path[i]);
+
+ // Find valid hit point for catapult stones
+ if (flags.bits.high_flying)
+ {
+ if (raytrace.hits())
+ fall_threshold = raytrace.goal_step;
+ else
+ fall_threshold = (raytrace.collision_z_step+raytrace.collision_step-1)/2;
+
+ while (fall_threshold < raytrace.collision_step-1)
+ {
+ if (isTargetableTile(path[fall_threshold]))
+ break;
+
+ fall_threshold++;
+ }
+ }
+
+ fall_threshold = std::max(fall_threshold, engine->fire_range.first);
+ fall_threshold = std::min(fall_threshold, engine->fire_range.second);
+ }
+
+ 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;
+ df::coord tsize = engine->getTargetSize();
+ bool success = false;
+
+ for (int i = 0; i < 50; i++)
+ {
+ target = tbase + df::coord(
+ random_int(tsize.x), random_int(tsize.y), random_int(tsize.z)
+ );
+
+ if (adjustToTarget(engine, &target))
+ last_passable = target;
+ else
+ continue;
+
+ ProjectilePath path(engine->center, target, engine->is_catapult ? 0.5f : 0.0f);
+ PathMetrics raytrace(path);
+
+ if (raytrace.hits() && engine->isInRange(raytrace.goal_step))
+ {
+ aimAtPoint(engine, skill, path);
+ return;
+ }
+ }
+
+ if (!last_passable.isValid())
+ last_passable = target;
+
+ aimAtPoint(engine, skill, ProjectilePath(engine->center, last_passable));
+ }
+
+ static int safeAimProjectile(lua_State *L)
+ {
+ color_ostream &out = *Lua::GetOutput(L);
+ auto proj = (projectile_hook*)lua_touserdata(L, 1);
+ auto engine = (EngineInfo*)lua_touserdata(L, 2);
+ int skill = lua_tointeger(L, 3);
+
+ if (!Lua::PushModulePublic(out, L, "plugins.siege-engine", "doAimProjectile"))
+ luaL_error(L, "Projectile aiming AI not available");
+
+ Lua::PushDFObject(L, engine->bld);
+ Lua::Push(L, proj->item);
+ Lua::Push(L, engine->target.first);
+ Lua::Push(L, engine->target.second);
+ Lua::Push(L, skill);
+
+ lua_call(L, 5, 1);
+
+ if (lua_isnil(L, -1))
+ proj->aimAtArea(engine, skill);
+ else
+ proj->aimAtPoint(engine, skill, decode_path(L, -1, engine->center));
+
+ return 0;
+ }
+
+ void doCheckMovement()
+ {
+ if (flags.bits.parabolic || distance_flown != 0 ||
+ fall_counter != fall_delay || item == NULL)
+ return;
+
+ auto engine = find_engine(origin_pos);
+ if (!engine || !engine->hasTarget())
+ return;
+
+ auto L = Lua::Core::State;
+ CoreSuspendClaimer suspend;
+ color_ostream_proxy out(Core::getInstance().getConsole());
+
+ int skill = getOperatorSkill(engine->bld, true);
+
+ // 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, skill);
+ }
+
+ switch (item->getType())
+ {
+ case item_type::CAGE:
+ flags.bits.bouncing = false;
+ break;
+ case item_type::BIN:
+ case item_type::BARREL:
+ flags.bits.bouncing = false;
+ break;
+ default:
+ break;
+ }
+ }
+
+ void doLaunchContents()
+ {
+ // Translate cartoon flight speed to parabolic
+ 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((float)dist.z);
+ normalize(vx, vy, vz);
+
+ int start_z = 0;
+
+ // Start at tile top, if hit a wall
+ ProjectilePath path(origin_pos, target_pos);
+ auto next_pos = path[distance_flown+1];
+ if (next_pos.z == cur_pos.z && !isPassableTile(next_pos))
+ start_z = 49000;
+
+ MapExtras::MapCache mc;
+ std::vector contents;
+ Items::getContainedItems(item, &contents);
+
+ 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;
+
+ 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;
+ proj->flags.bits.unk9 = true;
+ proj->flags.bits.no_collide = true;
+
+ proj->pos_z = start_z;
+
+ float sx, sy, sz;
+ random_direction(sx, sy, sz);
+ sx += vx*0.7; sy += vy*0.7; sz += vz*0.7;
+ if (sz < 0) sz = -sz;
+ normalize(sx, sy, sz);
+
+ proj->speed_x = int(speed * sx);
+ proj->speed_y = int(speed * sy);
+ proj->speed_z = std::max(min_zspeed, int(speed * sz));
+ }
+ }
+
+ DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ())
+ {
+ if (flags.bits.high_flying || flags.bits.piercing)
+ doCheckMovement();
+
+ return INTERPOSE_NEXT(checkMovement)();
+ }
+
+ DEFINE_VMETHOD_INTERPOSE(bool, checkImpact, (bool no_damage_floor))
+ {
+ if (!flags.bits.has_hit_ground && !flags.bits.parabolic &&
+ flags.bits.high_flying && !flags.bits.bouncing &&
+ !flags.bits.no_impact_destroy && target_pos != origin_pos &&
+ item && item->flags.bits.container)
+ {
+ doLaunchContents();
+ }
+
+ return INTERPOSE_NEXT(checkImpact)(no_damage_floor);
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement);
+IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkImpact);
+
+/*
+ * Building hook
+ */
+
+struct building_hook : df::building_siegeenginest {
+ typedef df::building_siegeenginest interpose_base;
+
+ DEFINE_VMETHOD_INTERPOSE(df::workshop_profile*, getWorkshopProfile, ())
+ {
+ if (auto engine = find_engine(this))
+ return &engine->profile;
+
+ return INTERPOSE_NEXT(getWorkshopProfile)();
+ }
+
+ DEFINE_VMETHOD_INTERPOSE(df::stockpile_links*, getStockpileLinks, ())
+ {
+ if (auto engine = find_engine(this))
+ {
+ update_stockpile_links(engine);
+ return &engine->links;
+ }
+
+ return INTERPOSE_NEXT(getStockpileLinks)();
+ }
+
+ DEFINE_VMETHOD_INTERPOSE(void, updateAction, ())
+ {
+ INTERPOSE_NEXT(updateAction)();
+
+ if (jobs.empty())
+ return;
+
+ if (auto engine = find_engine(this))
+ {
+ auto job = jobs[0];
+ bool save_op = false;
+
+ switch (job->job_type)
+ {
+ case job_type::LoadCatapult:
+ if (!job->job_items.empty())
+ {
+ auto item = job->job_items[0];
+ item->item_type = engine->ammo_item_type;
+ item->vector_id = engine->ammo_vector_id;
+
+ switch (item->item_type)
+ {
+ case item_type::NONE:
+ case item_type::BOULDER:
+ case item_type::BLOCKS:
+ item->mat_type = 0;
+ break;
+
+ case item_type::BIN:
+ case item_type::BARREL:
+ item->mat_type = -1;
+ // A hack to make it take objects assigned to stockpiles.
+ // Since reaction_id is not set, the actual value is not used.
+ item->contains.resize(1);
+ break;
+
+ default:
+ item->mat_type = -1;
+ break;
+ }
+ }
+ // fallthrough
+
+ case job_type::LoadBallista:
+ case job_type::FireCatapult:
+ case job_type::FireBallista:
+ if (auto worker = Job::getWorker(job))
+ {
+ engine->operator_id = worker->id;
+ engine->operator_frame = world->frame_counter;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+};
+
+IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getWorkshopProfile);
+IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getStockpileLinks);
+IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction);
+
+/*
+ * Initialization
+ */
+
+DFHACK_PLUGIN_LUA_FUNCTIONS {
+ DFHACK_LUA_FUNCTION(clearTargetArea),
+ DFHACK_LUA_FUNCTION(setTargetArea),
+ DFHACK_LUA_FUNCTION(isLinkedToPile),
+ DFHACK_LUA_FUNCTION(addStockpileLink),
+ DFHACK_LUA_FUNCTION(removeStockpileLink),
+ DFHACK_LUA_FUNCTION(saveWorkshopProfile),
+ DFHACK_LUA_FUNCTION(getTileStatus),
+ DFHACK_LUA_FUNCTION(paintAimScreen),
+ DFHACK_LUA_FUNCTION(canTargetUnit),
+ DFHACK_LUA_FUNCTION(isPassableTile),
+ DFHACK_LUA_FUNCTION(isTreeTile),
+ DFHACK_LUA_FUNCTION(isTargetableTile),
+ DFHACK_LUA_END
+};
+
+DFHACK_PLUGIN_LUA_COMMANDS {
+ DFHACK_LUA_COMMAND(getTargetArea),
+ DFHACK_LUA_COMMAND(getAmmoItem),
+ DFHACK_LUA_COMMAND(setAmmoItem),
+ DFHACK_LUA_COMMAND(getStockpileLinks),
+ DFHACK_LUA_COMMAND(projPosAtStep),
+ DFHACK_LUA_COMMAND(projPathMetrics),
+ DFHACK_LUA_COMMAND(adjustToTarget),
+ DFHACK_LUA_COMMAND(traceUnitPath),
+ DFHACK_LUA_COMMAND(unitPosAtTime),
+ DFHACK_LUA_COMMAND(proposeUnitHits),
+ DFHACK_LUA_COMMAND(computeNearbyWeight),
+ DFHACK_LUA_END
+};
+
+static bool is_enabled = false;
+
+static void enable_hooks(bool enable)
+{
+ is_enabled = enable;
+
+ INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable);
+ INTERPOSE_HOOK(projectile_hook, checkImpact).apply(enable);
+
+ INTERPOSE_HOOK(building_hook, getWorkshopProfile).apply(enable);
+ INTERPOSE_HOOK(building_hook, getStockpileLinks).apply(enable);
+ INTERPOSE_HOOK(building_hook, updateAction).apply(enable);
+
+ if (enable)
+ load_engines();
+ else
+ clear_engines();
+}
+
+static bool enable_plugin()
+{
+ if (is_enabled)
+ return true;
+
+ auto pworld = Core::getInstance().getWorld();
+ auto entry = pworld->GetPersistentData("siege-engine/enabled", NULL);
+ if (!entry.isValid())
+ return false;
+
+ enable_hooks(true);
+ return true;
+}
+
+static void clear_caches(color_ostream &out)
+{
+ if (!UnitPath::cache.empty())
+ {
+ for (auto it = UnitPath::cache.begin(); it != UnitPath::cache.end(); ++it)
+ delete it->second;
+
+ UnitPath::cache.clear();
+ }
+}
+
+DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
+{
+ switch (event) {
+ case SC_MAP_LOADED:
+ {
+ auto pworld = Core::getInstance().getWorld();
+ bool enable = pworld->GetPersistentData("siege-engine/enabled").isValid();
+
+ if (enable)
+ {
+ out.print("Enabling the siege engine plugin.\n");
+ enable_hooks(true);
+ }
+ else
+ enable_hooks(false);
+ }
+ break;
+ case SC_MAP_UNLOADED:
+ enable_hooks(false);
+ 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;
+}
+
+DFhackCExport command_result plugin_onupdate ( color_ostream &out )
+{
+ clear_caches(out);
+ return CR_OK;
+}
diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua
index 47043cbb..7a76d767 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 },
}
--
cgit v1.2.1
From 613063cef4d87b3b4307144b85da60dc40daceb3 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 17:19:24 +0400
Subject: 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 a9b69b82..b8b53cad 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 bebc346c..4fef285f 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;
--
cgit v1.2.1
From 36e44c682cc2cecb552eca8dfc75ad1a436086cc Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Mon, 17 Sep 2012 21:15:51 +0400
Subject: 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 4294bedb..fdc69ac5 100644
--- a/NEWS
+++ b/NEWS
@@ -60,4 +60,7 @@ DFHack v0.34.11-r2 (UNRELEASED)
and restricting operator skill range like with ordinary workshops.
Disclaimer: not in any way to undermine the future siege update from Toady, but the aiming
logic of existing engines hasn't been updated since 2D, and is almost useless as/is.
-
+ New Add Spatter plugin:
+ Detects reactions with certain names in the raws, and changes them from adding
+ improvements to adding item contaminants. This allows directly covering items
+ with poisons. The added spatters are immune both to water and 'clean items'.
diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h
index 76c89de3..fb5a6353 100644
--- a/library/include/modules/Materials.h
+++ b/library/include/modules/Materials.h
@@ -131,6 +131,11 @@ namespace DFHack
bool findPlant(const std::string &token, const std::string &subtoken);
bool findCreature(const std::string &token, const std::string &subtoken);
+ bool findProduct(df::material *material, const std::string &name);
+ bool findProduct(const MaterialInfo &info, const std::string &name) {
+ return findProduct(info.material, name);
+ }
+
std::string getToken();
std::string toString(uint16_t temp = 10015, bool named = true);
diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp
index 50cf21a9..db9c9c7d 100644
--- a/library/modules/Materials.cpp
+++ b/library/modules/Materials.cpp
@@ -283,6 +283,19 @@ bool MaterialInfo::findCreature(const std::string &token, const std::string &sub
return decode(-1);
}
+bool MaterialInfo::findProduct(df::material *material, const std::string &name)
+{
+ if (!material || name.empty())
+ return decode(-1);
+
+ auto &pids = material->reaction_product.id;
+ for (size_t i = 0; i < pids.size(); i++)
+ if ((*pids[i]) == name)
+ return decode(material->reaction_product.material, i);
+
+ return decode(-1);
+}
+
std::string MaterialInfo::getToken()
{
if (isNone())
diff --git a/library/xml b/library/xml
index a6b95f1c..260ff4a1 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 8511d86c..0b0ad046 100644
--- a/plugins/CMakeLists.txt
+++ b/plugins/CMakeLists.txt
@@ -47,6 +47,9 @@ install(DIRECTORY lua/
install(DIRECTORY raw/
DESTINATION ${DFHACK_DATA_DESTINATION}/raw
FILES_MATCHING PATTERN "*.txt")
+install(DIRECTORY raw/
+ DESTINATION ${DFHACK_DATA_DESTINATION}/raw
+ FILES_MATCHING PATTERN "*.diff")
# Protobuf
FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)
@@ -120,6 +123,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(steam-engine steam-engine.cpp)
DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua)
+ DFHACK_PLUGIN(add-spatter add-spatter.cpp)
# not yet. busy with other crud again...
#DFHACK_PLUGIN(versionosd versionosd.cpp)
endif()
diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp
new file mode 100644
index 00000000..ed5f47f7
--- /dev/null
+++ b/plugins/add-spatter.cpp
@@ -0,0 +1,409 @@
+#include "Core.h"
+#include
+#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 c0301de7..319b83c1 100644
--- a/plugins/cleaners.cpp
+++ b/plugins/cleaners.cpp
@@ -81,11 +81,18 @@ command_result cleanitems (color_ostream &out)
df::item_actual *item = (df::item_actual *)world->items.all[i];
if (item->contaminants && item->contaminants->size())
{
+ std::vector saved;
for (size_t j = 0; j < item->contaminants->size(); j++)
- delete item->contaminants->at(j);
+ {
+ auto obj = (*item->contaminants)[j];
+ if (obj->flags.whole & 0x8000) // DFHack-generated contaminant
+ saved.push_back(obj);
+ else
+ delete obj;
+ }
cleaned_items++;
- cleaned_total += item->contaminants->size();
- item->contaminants->clear();
+ cleaned_total += item->contaminants->size() - saved.size();
+ item->contaminants->swap(saved);
}
}
if (cleaned_total)
diff --git a/plugins/raw/entity_default.diff b/plugins/raw/entity_default.diff
new file mode 100644
index 00000000..a99f8ebb
--- /dev/null
+++ b/plugins/raw/entity_default.diff
@@ -0,0 +1,29 @@
+--- ../objects.old/entity_default.txt 2012-09-17 17:59:28.853898702 +0400
++++ entity_default.txt 2012-09-17 17:59:28.684899429 +0400
+@@ -49,6 +49,7 @@
+ [TRAPCOMP:ITEM_TRAPCOMP_SPIKEDBALL]
+ [TRAPCOMP:ITEM_TRAPCOMP_LARGESERRATEDDISC]
+ [TRAPCOMP:ITEM_TRAPCOMP_MENACINGSPIKE]
++ [TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON]
+ [TOY:ITEM_TOY_PUZZLEBOX]
+ [TOY:ITEM_TOY_BOAT]
+ [TOY:ITEM_TOY_HAMMER]
+@@ -204,6 +205,8 @@
+ [PERMITTED_JOB:WAX_WORKER]
+ [PERMITTED_BUILDING:SOAP_MAKER]
+ [PERMITTED_BUILDING:SCREW_PRESS]
++ [PERMITTED_BUILDING:STEAM_ENGINE]
++ [PERMITTED_BUILDING:MAGMA_STEAM_ENGINE]
+ [PERMITTED_REACTION:TAN_A_HIDE]
+ [PERMITTED_REACTION:RENDER_FAT]
+ [PERMITTED_REACTION:MAKE_SOAP_FROM_TALLOW]
+@@ -248,6 +251,9 @@
+ [PERMITTED_REACTION:ROSE_GOLD_MAKING]
+ [PERMITTED_REACTION:BISMUTH_BRONZE_MAKING]
+ [PERMITTED_REACTION:ADAMANTINE_WAFERS]
++ [PERMITTED_REACTION:STOKE_BOILER]
++ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_WEAPON]
++ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_AMMO]
+ [WORLD_CONSTRUCTION:TUNNEL]
+ [WORLD_CONSTRUCTION:BRIDGE]
+ [WORLD_CONSTRUCTION:ROAD]
diff --git a/plugins/raw/material_template_default.diff b/plugins/raw/material_template_default.diff
new file mode 100644
index 00000000..8b6ef327
--- /dev/null
+++ b/plugins/raw/material_template_default.diff
@@ -0,0 +1,10 @@
+--- ../objects.old/material_template_default.txt 2012-09-17 17:59:28.907898469 +0400
++++ material_template_default.txt 2012-09-17 17:59:28.695899382 +0400
+@@ -2374,6 +2374,7 @@
+ [MAX_EDGE:500]
+ [ABSORPTION:100]
+ [LIQUID_MISC_CREATURE]
++ [REACTION_CLASS:CREATURE_EXTRACT]
+ [ROTS]
+
+ This is for creatures that are "made of fire". Right now there isn't a good format for that.
diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt
new file mode 100644
index 00000000..b31d82fa
--- /dev/null
+++ b/plugins/raw/reaction_spatter.txt
@@ -0,0 +1,41 @@
+reaction_spatter
+
+[OBJECT:REACTION]
+
+Reaction name must start with 'SPATTER_ADD_':
+
+[REACTION:SPATTER_ADD_EXTRACT_WEAPON]
+ [NAME:cover weapon with extract]
+ [BUILDING:CRAFTSMAN:CUSTOM_ALT_V]
+ [SKILL:DYER]
+ [ADVENTURE_MODE_ENABLED]
+ [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE]
+ [MIN_DIMENSION:10]
+ [REACTION_CLASS:CREATURE_EXTRACT]
+ [REAGENT:extract container:1:NONE:NONE:NONE:NONE]
+ [CONTAINS:extract]
+ [PRESERVE_REAGENT]
+ [DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
+ The object to improve must be the last reagent:
+ [REAGENT:object:1:WEAPON:NONE:NONE:NONE]
+ [PRESERVE_REAGENT]
+ The probability is used as spatter size instead:
+ [IMPROVEMENT:100:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
+
+[REACTION:SPATTER_ADD_EXTRACT_AMMO]
+ [NAME:cover ammo with extract]
+ [BUILDING:CRAFTSMAN:CUSTOM_ALT_M]
+ [SKILL:DYER]
+ [ADVENTURE_MODE_ENABLED]
+ [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE]
+ [MIN_DIMENSION:10]
+ [REACTION_CLASS:CREATURE_EXTRACT]
+ [REAGENT:extract container:1:NONE:NONE:NONE:NONE]
+ [CONTAINS:extract]
+ [PRESERVE_REAGENT]
+ [DOES_NOT_DETERMINE_PRODUCT_AMOUNT]
+ The object to improve must be the last reagent:
+ [REAGENT:object:1:AMMO:NONE:NONE:NONE]
+ [PRESERVE_REAGENT]
+ The probability is used as spatter size instead:
+ [IMPROVEMENT:100:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE]
--
cgit v1.2.1
From 2c0a8a9544f81834b0a6e7ac9bf78db8aa5008d1 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 00:24:59 +0400
Subject: Tweak new plugin descriptions in the NEWS document.
---
NEWS | 21 ++++++++++++++-------
1 file changed, 14 insertions(+), 7 deletions(-)
diff --git a/NEWS b/NEWS
index fdc69ac5..68551d38 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.
+
--
cgit v1.2.1
From be928a9dc537290522577cde41211637f3b6f165 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 10:40:14 +0400
Subject: 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 583ef518..04642565 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);
}
}
--
cgit v1.2.1
From d70a79deb99a3c4ae6458317ee1111928f3db401 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 13:11:11 +0400
Subject: 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 b0cfda67..1662f446 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 260ff4a1..8a78bfa2 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 ed5f47f7..dda4ca2b 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]);
}
}
--
cgit v1.2.1
From f2e7ee4756813e1f60043d47942d18d4b7b814b5 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 13:15:25 +0400
Subject: 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 dda4ca2b..35ea11ef 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 b31d82fa..229e531c 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]
--
cgit v1.2.1
From a7998f71a2ee95d2d21f34468761118fd6b8585f Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 17:39:37 +0400
Subject: 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 68551d38..22f64d7d 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 b8b53cad..83c3641b 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 229e531c..085be7fd 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 4fef285f..fb286e0d 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;
--
cgit v1.2.1
From 57b72831ca700ab556566a85f2245e014ca96c30 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 20:30:25 +0400
Subject: 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 00000000..7b142e49
--- /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 baf0d42e..ce3be5a8 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 f9b6ab6d..6eaa9860 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 eb883465..b1a96a55 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 661e1559..ba3cfbe6 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 80986bbf..c8cd3bd0 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 89f08b7c..cddb9f01 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 c14bfcbe..d1e8ec80 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 8baf43e7..6c2f699a 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 a4507466..0de82db5 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 7a76d767..c98cb167 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()
--
cgit v1.2.1
From a4799a384b2f33be5b3fcf43b462175de6ce7f65 Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Tue, 18 Sep 2012 20:45:59 +0400
Subject: 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 f8497569..d73d14cf 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 }
};
--
cgit v1.2.1
From 69e8fcce915630b1e861544eca9701cc7a2029c5 Mon Sep 17 00:00:00 2001
From: Quietust
Date: Tue, 18 Sep 2012 13:57:06 -0500
Subject: 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 f4096965..1ad46ab2 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;
--
cgit v1.2.1
From 65a382a2d7b4c910fe498112687c9c584d5408cb Mon Sep 17 00:00:00 2001
From: Alexander Gavrilov
Date: Wed, 19 Sep 2012 15:55:08 +0400
Subject: 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 6c463911..8b6974ab 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 50ceae99..001e89b1 100644
--- a/Readme.html
+++ b/Readme.html
@@ -3,7 +3,7 @@
-
+