From 1e0ff32ad4a4251d7e0a7dc02a4829479df5dfe3 Mon Sep 17 00:00:00 2001 From: Timothy G Collett Date: Sun, 3 Jun 2012 11:01:13 -0400 Subject: [PATCH 01/48] fix clsocket? --- depends/clsocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/clsocket b/depends/clsocket index 27216d9a4..c85e9fb35 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit 27216d9a4be418729cb4671371b7309f0af558f1 +Subproject commit c85e9fb35d3510c5dcc367056cda3237d77a7add From 8f67139a7550f3168b2bb3c74d8ba9dc6096b342 Mon Sep 17 00:00:00 2001 From: Timothy G Collett Date: Sun, 3 Jun 2012 14:33:44 -0400 Subject: [PATCH 02/48] fix clsocket? --- depends/clsocket | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/depends/clsocket b/depends/clsocket index c85e9fb35..651c72229 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit c85e9fb35d3510c5dcc367056cda3237d77a7add +Subproject commit 651c722295da233ca8d918e298ed226cc0e6c9b9 From b40682f61c3d86e1e6f279454fca2247cdd81954 Mon Sep 17 00:00:00 2001 From: Timothy G Collett Date: Sun, 24 Jun 2012 18:32:53 -0400 Subject: [PATCH 03/48] Add running scripts --- package/darwin/dfhack | 15 +++++++++++++++ package/darwin/dfhack-run | 8 ++++++++ 2 files changed, 23 insertions(+) create mode 100755 package/darwin/dfhack create mode 100755 package/darwin/dfhack-run diff --git a/package/darwin/dfhack b/package/darwin/dfhack new file mode 100755 index 000000000..6f54d15d8 --- /dev/null +++ b/package/darwin/dfhack @@ -0,0 +1,15 @@ +#!/bin/sh +PWD=`dirname "${0}"` +#thanks to Iriel for figuring this out +OSREV=`uname -r | cut -d. -f1` +if [ "$OSREV" -ge 11 ] ; then + export DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib + export DYLD_LIBRARY_PATH=${PWD}/hack:${PWD}/libs + export DYLD_FRAMEWORK_PATH=${PWD}/hack${PWD}/libs +else + export DYLD_INSERT_LIBRARIES=./hack/libdfhack.dylib + export DYLD_FALLBACK_LIBRARY_PATH=${PWD}/hack:${PWD}/libs + export DYLD_FALLBACK_FRAMEWORK_PATH=${PWD}/hack:${PWD}/libs +fi +export DYLD_FORCE_FLAT_NAMESPACE=1 +cd "${PWD}"; ./dwarfort.exe diff --git a/package/darwin/dfhack-run b/package/darwin/dfhack-run new file mode 100755 index 000000000..cc69db964 --- /dev/null +++ b/package/darwin/dfhack-run @@ -0,0 +1,8 @@ +#!/bin/sh + +DF_DIR=$(dirname "$0") +cd "${DF_DIR}" + +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack" + +exec hack/dfhack-run "$@" From 7cdbae3f049126af7631cd23428ec3349fc9524f Mon Sep 17 00:00:00 2001 From: Timothy G Collett Date: Sun, 24 Jun 2012 18:35:16 -0400 Subject: [PATCH 04/48] Update df-structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 234d0f57a..ad38c5e96 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 234d0f57a927f306f2052fc2f45d38b3201ddee6 +Subproject commit ad38c5e96b05fedf16114fd16bd463e933f13582 From 604cf808321382286619042b1edbf81558086cb5 Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Thu, 30 Aug 2012 09:23:11 -0500 Subject: [PATCH 05/48] Repurpose the nestboxes plugin as a watcher that automatically forbids fertile eggs. --- plugins/devel/nestboxes.cpp | 111 ++++++++++++++++++------------------ 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/plugins/devel/nestboxes.cpp b/plugins/devel/nestboxes.cpp index b3d24cd92..42c3c0660 100644 --- a/plugins/devel/nestboxes.cpp +++ b/plugins/devel/nestboxes.cpp @@ -31,6 +31,40 @@ static command_result nestboxes(color_ostream &out, vector & parameters DFHACK_PLUGIN("nestboxes"); +static bool enabled = false; + +static void eggscan(color_ostream &out) +{ + CoreSuspender suspend; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (df::enums::building_type::NestBox == type) + { + bool fertile = false; + df::building_nest_boxst *nb = virtual_cast(build); + if (nb->claimed_by != -1) + { + df::unit* u = df::unit::find(nb->claimed_by); + if (u && u->relations.pregnancy_timer > 0) + fertile = true; + } + for (int j = 1; j < nb->contained_items.size(); j++) + { + df::item* item = nb->contained_items[j]->item; + if (item->flags.bits.forbid != fertile) + { + item->flags.bits.forbid = fertile; + out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl; + } + } + } + } +} + + DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (world && ui) { @@ -49,6 +83,19 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } +DFhackCExport command_result plugin_onupdate(color_ostream &out) +{ + if (!enabled) + return CR_OK; + + static unsigned cnt = 0; + if ((++cnt % 5) != 0) + return CR_OK; + + eggscan(out); + + return CR_OK; +} static command_result nestboxes(color_ostream &out, vector & parameters) { @@ -57,60 +104,16 @@ static command_result nestboxes(color_ostream &out, vector & parameters int dump_count = 0; int good_egg = 0; - if (parameters.size() == 1 && parameters[0] == "clean") - { - clean = true; - } - for (int i = 0; i < world->buildings.all.size(); ++i) - { - df::building *build = world->buildings.all[i]; - auto type = build->getType(); - if (df::enums::building_type::NestBox == type) - { - bool needs_clean = false; - df::building_nest_boxst *nb = virtual_cast(build); - out << "Nestbox at (" << nb->x1 << "," << nb->y1 << ","<< nb->z << "): claimed-by " << nb->claimed_by << ", contained item count " << nb->contained_items.size() << " (" << nb->anon_1 << ")" << endl; - if (nb->contained_items.size() > 1) - needs_clean = true; - if (nb->claimed_by != -1) - { - df::unit* u = df::unit::find(nb->claimed_by); - if (u) - { - out << " Claimed by "; - if (u->name.has_name) - out << u->name.first_name << ", "; - df::creature_raw *raw = df::global::world->raws.creatures.all[u->race]; - out << raw->creature_id - << ", pregnancy timer " << u->relations.pregnancy_timer << endl; - if (u->relations.pregnancy_timer > 0) - needs_clean = false; - } - } - for (int j = 1; j < nb->contained_items.size(); j++) - { - df::item* item = nb->contained_items[j]->item; - if (needs_clean) { - if (clean && !item->flags.bits.dump) - { - item->flags.bits.dump = 1; - dump_count += item->getStackSize(); - - } - } else { - good_egg += item->getStackSize(); - } - } - } - } - - if (clean) - { - out << dump_count << " eggs dumped." << endl; - } - out << good_egg << " fertile eggs found." << endl; - - + if (parameters.size() == 1) { + if (parameters[0] == "enable") + enabled = true; + else if (parameters[0] == "disable") + enabled = false; + else + return CR_WRONG_USAGE; + } else { + out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl; + } return CR_OK; } From 325e294af2bbd2ba0381a476756eddbbfceb1b36 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 7 Sep 2012 19:54:32 +0400 Subject: [PATCH 06/48] Start the siege engine plugin with code to highlight obstacles on screen. --- library/lua/dfhack.lua | 17 ++ library/lua/gui/dwarfmode.lua | 53 +++- plugins/devel/CMakeLists.txt | 1 + plugins/devel/siege-engine.cpp | 432 +++++++++++++++++++++++++++++++++ plugins/lua/siege-engine.lua | 13 + scripts/gui/siege-engine.lua | 72 ++++++ 6 files changed, 582 insertions(+), 6 deletions(-) create mode 100644 plugins/devel/siege-engine.cpp create mode 100644 plugins/lua/siege-engine.lua create mode 100644 scripts/gui/siege-engine.lua diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index e96bb0f4b..baf0d42e0 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -169,6 +169,23 @@ function xyz2pos(x,y,z) end end +function pos2xy(pos) + if pos then + local x = pos.x + if x and x ~= -30000 then + return x, pos.y + end + end +end + +function xy2pos(x,y) + if x then + return {x=x,y=y} + else + return {x=-30000,y=-30000} + end +end + function safe_index(obj,idx,...) if obj == nil or idx == nil then return nil diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 1f7ae1b03..21a942174 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -136,6 +136,14 @@ function Viewport:set() return vp end +function Viewport:getPos() + return xyz2pos(self.x1, self.y1, self.z) +end + +function Viewport:getSize() + return xy2pos(self.width, self.height) +end + function Viewport:clip(x,y,z) return self:make( math.max(0, math.min(x or self.x1, world_map.x_count-self.width)), @@ -207,16 +215,24 @@ MOVEMENT_KEYS = { CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, } -function Viewport:scrollByKey(key) +local function get_movement_delta(key, delta, big_step) local info = MOVEMENT_KEYS[key] if info then - local delta = 10 - if info[4] then delta = 20 end + if info[4] then + delta = big_step + end + return delta*info[1], delta*info[2], info[3] + end +end + +function Viewport:scrollByKey(key) + local dx, dy, dz = get_movement_delta(key, 10, 20) + if dx then return self:clip( - self.x1 + delta*info[1], - self.y1 + delta*info[2], - self.z + info[3] + self.x1 + dx, + self.y1 + dy, + self.z + dz ) else return self @@ -282,6 +298,31 @@ function DwarfOverlay:simulateViewScroll(keys, anchor, no_clip_cursor) end end +function DwarfOverlay:simulateCursorMovement(keys, anchor) + local layout = self.df_layout + local cursor = getCursorPos() + local cx, cy, cz = pos2xyz(cursor) + + if anchor and keys.A_MOVE_SAME_SQUARE then + setCursorPos(anchor) + self:getViewport():centerOn(anchor):set() + return 'A_MOVE_SAME_SQUARE' + end + + for code,_ in pairs(MOVEMENT_KEYS) do + if keys[code] then + local dx, dy, dz = get_movement_delta(code, 1, 10) + local ncur = xyz2pos(cx+dx, cy+dy, cz+dz) + + if dfhack.maps.isValidTilePos(ncur) then + setCursorPos(ncur) + self:getViewport():reveal(ncur,4,10,6,true):set() + return code + end + end + end +end + function DwarfOverlay:onAboutToShow(below) local screen = dfhack.gui.getCurViewscreen() if below then screen = below.parent end diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 134d5cb67..39e8f7b60 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,6 +18,7 @@ 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 new file mode 100644 index 000000000..189c43ad5 --- /dev/null +++ b/plugins/devel/siege-engine.cpp @@ -0,0 +1,432 @@ +#include "Core.h" +#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 "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; +using df::global::ui_build_selector; + +using Screen::Pen; + +DFHACK_PLUGIN("siege-engine"); + +/* + * Configuration management + */ + +static bool enable_plugin(); + +struct EngineInfo { + int id; + df::coord target_min, target_max; + + bool hasTarget() { + return target_min.isValid() && target_max.isValid(); + } + bool onTarget(df::coord pos) { + return hasTarget() && + target_min.x <= pos.x && pos.x <= target_max.x && + target_min.y <= pos.y && pos.y <= target_max.y && + target_min.z <= pos.z && pos.z <= target_max.z; + } +}; + +static std::map engines; + +static EngineInfo *find_engine(df::building *bld, bool create = false) +{ + if (!bld) + return NULL; + + auto it = engines.find(bld); + if (it != engines.end()) + return &it->second; + if (!create) + return NULL; + + auto *obj = &engines[bld]; + obj->id = bld->id; + return obj; +} + +static void load_engines() +{ + engines.clear(); + + 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_min = df::coord(it->ival(1), it->ival(2), it->ival(3)); + engine->target_max = df::coord(it->ival(4), it->ival(5), it->ival(6)); + } +} + +static int getTargetArea(lua_State *L) +{ + auto bld = Lua::CheckDFObject(L, 1); + if (!bld) luaL_argerror(L, 1, "null building"); + auto engine = find_engine(bld); + + if (engine && engine->target_min.isValid()) + { + Lua::Push(L, engine->target_min); + Lua::Push(L, engine->target_max); + } + 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_min = engine->target_max = df::coord(); + + 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); + + entry.ival(0) = bld->id; + entry.ival(1) = engine->target_min.x = std::min(target_min.x, target_max.x); + entry.ival(2) = engine->target_min.y = std::min(target_min.y, target_max.y); + entry.ival(3) = engine->target_min.z = std::min(target_min.z, target_max.z); + entry.ival(4) = engine->target_max.x = std::max(target_min.x, target_max.x); + entry.ival(5) = engine->target_max.y = std::max(target_min.y, target_max.y); + entry.ival(6) = engine->target_max.z = std::max(target_min.z, target_max.z); + + return true; +} + +/* + * Trajectory + */ + +struct ProjectilePath { + df::coord origin, target; + int divisor; + df::coord speed, direction; + + ProjectilePath(df::coord origin, df::coord target) : + origin(origin), target(target) + { + speed = target - origin; + divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); + 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 bool isPassableTile(df::coord pos) +{ + auto ptile = Maps::getTileType(pos); + return !ptile || FlowPassable(*ptile); +} + +struct PathMetrics { + enum CollisionType { + Impassable, + Floor, + Ceiling, + MapEdge + } hit_type; + + int collision_step; + int goal_step, goal_z_step; + std::vector coords; + + bool hits() { return goal_step != -1 && collision_step > goal_step; } + + PathMetrics(const ProjectilePath &path, df::coord goal, bool list_coords = false) + { + coords.clear(); + collision_step = goal_step = goal_z_step = -1; + + int step = 0; + df::coord prev_pos = path.origin; + if (list_coords) + coords.push_back(prev_pos); + + for (;;) { + df::coord cur_pos = path[++step]; + if (cur_pos == prev_pos) + break; + if (list_coords) + coords.push_back(cur_pos); + + if (cur_pos.z == goal.z) + { + if (goal_z_step == -1) + goal_z_step = step; + if (cur_pos == goal) + goal_step = step; + } + + if (!Maps::isValidTilePos(cur_pos)) + { + hit_type = PathMetrics::MapEdge; + break; + } + + if (!isPassableTile(cur_pos)) + { + 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; + } + } + + prev_pos = cur_pos; + } + + collision_step = step; + } +}; + +void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +{ + CHECK_NULL_POINTER(bld); + + df::coord origin = df::coord(bld->centerx, bld->centery, bld->z); + + auto engine = find_engine(bld); + int min_distance, max_distance; + + if (bld->type == siegeengine_type::Ballista) + { + min_distance = 0; + max_distance = 200; + } + else + { + min_distance = 30; + max_distance = 100; + } + + df::coord cursor = Gui::getCursorPos(); + + 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 (tile_pos == cursor) + continue; + if (tile_pos.z == bld->z && + tile_pos.x >= bld->x1 && tile_pos.x <= bld->x2 && + tile_pos.y >= bld->y1 && tile_pos.y <= bld->y2) + continue; + + Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); + if (!cur_tile.valid()) + continue; + + ProjectilePath path(origin, tile_pos); + + if (path.speed.z != 0 && abs(path.speed.z) != path.divisor) { + path.divisor *= 20; + path.speed.x *= 20; + path.speed.y *= 20; + path.speed.z *= 20; + path.speed.z += 9; + } + + PathMetrics raytrace(path, tile_pos); + + int color; + if (raytrace.hits()) + { + if (raytrace.goal_step >= min_distance && + raytrace.goal_step <= max_distance) + color = COLOR_GREEN; + else + color = COLOR_CYAN; + } + else + color = COLOR_RED; + + 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 && engine->onTarget(tile_pos)); + + if (cur_tile.tile) + cur_tile.tile_mode = Pen::CharColor; + + Screen::paintTile(cur_tile, ltop.x+x, ltop.y+y); + } + } +} + +/* + * Initialization + */ + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(clearTargetArea), + DFHACK_LUA_FUNCTION(setTargetArea), + DFHACK_LUA_FUNCTION(paintAimScreen), + DFHACK_LUA_END +}; + +DFHACK_PLUGIN_LUA_COMMANDS { + DFHACK_LUA_COMMAND(getTargetArea), + DFHACK_LUA_END +}; + +static bool is_enabled = false; + +static void enable_hooks(bool enable) +{ + is_enabled = enable; + + if (enable) + load_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; +} + +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; +} diff --git a/plugins/lua/siege-engine.lua b/plugins/lua/siege-engine.lua new file mode 100644 index 000000000..01b5d1447 --- /dev/null +++ b/plugins/lua/siege-engine.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.siege-engine') + +--[[ + + Native functions: + + * getTargetArea(building) -> point1, point2 + * clearTargetArea(building) + * setTargetArea(building, point1, point2) -> true/false + +--]] + +return _ENV \ No newline at end of file diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua new file mode 100644 index 000000000..466657e56 --- /dev/null +++ b/scripts/gui/siege-engine.lua @@ -0,0 +1,72 @@ +-- Front-end for the siege engine plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' + +local plugin = require 'plugins.siege-engine' + +SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) + +SiegeEngine.focus_path = 'siege-engine' + +function SiegeEngine:init(building) + self:init_fields{ + building = building, + center = utils.getBuildingCenter(building), + links = {}, selected = 1 + } + guidm.MenuOverlay.init(self) + return self +end + +function SiegeEngine:onShow() + guidm.MenuOverlay.onShow(self) + + self.old_cursor = guidm.getCursorPos() + self.old_viewport = self:getViewport() +end + +function SiegeEngine:onDestroy() + guidm.setCursorPos(self.old_cursor) + self:getViewport(self.old_viewport):set() +end + +function SiegeEngine:onRenderBody(dc) + dc:clear() + dc:seek(1,1):string(utils.getBuildingName(self.building), COLOR_WHITE):newline() + + local view = self:getViewport() + local map = self.df_layout.map + + plugin.paintAimScreen( + self.building, view:getPos(), + xy2pos(map.x1, map.y1), view:getSize() + ) + + dc:newline():newline(1):pen(COLOR_WHITE) + dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") + dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") +end + +function SiegeEngine:onInput(keys) + if keys.LEAVESCREEN then + self:dismiss() + elseif self:simulateCursorMovement(keys, self.center) then + return + end +end + +if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then + qerror("This script requires the main dwarfmode view in 'q' mode") +end + +local building = df.global.world.selected_building + +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) +list:show() From bfa6ed3e0868c7a2fa9851c3feb8f9055b0330bb Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Sep 2012 13:46:02 +0400 Subject: [PATCH 07/48] Support setting the target area for the siege engine. --- library/lua/gui/dwarfmode.lua | 29 +++- library/modules/Gui.cpp | 7 +- plugins/devel/siege-engine.cpp | 160 +++++++++++++------- scripts/devel/pop-screen.lua | 3 + scripts/gui/mechanisms.lua | 2 +- scripts/gui/siege-engine.lua | 264 +++++++++++++++++++++++++++++++-- 6 files changed, 390 insertions(+), 75 deletions(-) create mode 100644 scripts/devel/pop-screen.lua diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 21a942174..661e15591 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -46,7 +46,7 @@ function getPanelLayout() end function getCursorPos() - if g_cursor ~= -30000 then + if g_cursor.x ~= -30000 then return copyall(g_cursor) end end @@ -167,6 +167,18 @@ function Viewport:isVisible(target,gap) return self:isVisibleXY(target,gap) and target.z == self.z end +function Viewport:tileToScreen(coord) + return xyz2pos(coord.x - self.x1, coord.y - self.y1, coord.z - self.z) +end + +function Viewport:getCenter() + return xyz2pos( + math.floor((self.x2+self.x1)/2), + math.floor((self.y2+self.y1)/2), + self.z + ) +end + function Viewport:centerOn(target) return self:clip( target.x - math.floor(self.width/2), @@ -253,16 +265,23 @@ function DwarfOverlay:getViewport(old_vp) end end -function DwarfOverlay:moveCursorTo(cursor,viewport) +function DwarfOverlay:moveCursorTo(cursor,viewport,gap) setCursorPos(cursor) - self:getViewport(viewport):reveal(cursor, 5, 0, 10):set() + self:zoomViewportTo(cursor,viewport,gap) +end + +function DwarfOverlay:zoomViewportTo(target, viewport, gap) + if gap and self:getViewport():isVisible(target, gap) then + return + end + self:getViewport(viewport):reveal(target, 5, 0, 10):set() end -function DwarfOverlay:selectBuilding(building,cursor,viewport) +function DwarfOverlay:selectBuilding(building,cursor,viewport,gap) cursor = cursor or utils.getBuildingCenter(building) df.global.world.selected_building = building - self:moveCursorTo(cursor, viewport) + self:moveCursorTo(cursor, viewport, gap) end function DwarfOverlay:propagateMoveKeys(keys) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 8de908734..91df14eaf 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -173,10 +173,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) else if (id == &df::building_trapst::_identity) { auto trap = (df::building_trapst*)selected; - if (trap->trap_type == trap_type::Lever) { - focus += "/Lever"; + focus += "/" + enum_item_key(trap->trap_type); + if (trap->trap_type == trap_type::Lever) jobs = true; - } } else if (ui_building_in_assign && *ui_building_in_assign && ui_building_assign_type && ui_building_assign_units && @@ -189,6 +188,8 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) focus += unit ? "/Unit" : "/None"; } } + else + focus += "/" + enum_item_key(selected->getType()); if (jobs) { diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 189c43ad5..6906f540c 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -47,6 +47,65 @@ 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(0, 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; +} + /* * Configuration management */ @@ -55,17 +114,10 @@ static bool enable_plugin(); struct EngineInfo { int id; - df::coord target_min, target_max; + coord_range target; - bool hasTarget() { - return target_min.isValid() && target_max.isValid(); - } - bool onTarget(df::coord pos) { - return hasTarget() && - target_min.x <= pos.x && pos.x <= target_max.x && - target_min.y <= pos.y && pos.y <= target_max.y && - target_min.z <= pos.z && pos.z <= target_max.z; - } + bool hasTarget() { return is_range_valid(target); } + bool onTarget(df::coord pos) { return is_in_range(target, pos); } }; static std::map engines; @@ -98,8 +150,8 @@ static void load_engines() { auto engine = find_engine(df::building::find(it->ival(0)), true); if (!engine) continue; - engine->target_min = df::coord(it->ival(1), it->ival(2), it->ival(3)); - engine->target_max = df::coord(it->ival(4), it->ival(5), it->ival(6)); + 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)); } } @@ -109,10 +161,10 @@ static int getTargetArea(lua_State *L) if (!bld) luaL_argerror(L, 1, "null building"); auto engine = find_engine(bld); - if (engine && engine->target_min.isValid()) + if (engine && engine->hasTarget()) { - Lua::Push(L, engine->target_min); - Lua::Push(L, engine->target_max); + Lua::Push(L, engine->target.first); + Lua::Push(L, engine->target.second); } else { @@ -128,7 +180,7 @@ static void clearTargetArea(df::building_siegeenginest *bld) CHECK_NULL_POINTER(bld); if (auto engine = find_engine(bld)) - engine->target_min = engine->target_max = df::coord(); + engine->target = coord_range(); auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/target/%d", bld->id); @@ -151,13 +203,18 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, auto engine = find_engine(bld, true); + set_range(&engine->target, target_min, target_max); + entry.ival(0) = bld->id; - entry.ival(1) = engine->target_min.x = std::min(target_min.x, target_max.x); - entry.ival(2) = engine->target_min.y = std::min(target_min.y, target_max.y); - entry.ival(3) = engine->target_min.z = std::min(target_min.z, target_max.z); - entry.ival(4) = engine->target_max.x = std::max(target_min.x, target_max.x); - entry.ival(5) = engine->target_max.y = std::max(target_min.y, target_max.y); - entry.ival(6) = engine->target_max.z = std::max(target_min.z, target_max.z); + 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; } @@ -267,38 +324,45 @@ struct PathMetrics { } }; -void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos) { - CHECK_NULL_POINTER(bld); + df::coord origin(bld->centerx, bld->centery, bld->z); + auto fire_range = get_engine_range(bld); - df::coord origin = df::coord(bld->centerx, bld->centery, bld->z); + ProjectilePath path(origin, tile_pos); + PathMetrics raytrace(path, tile_pos); - auto engine = find_engine(bld); - int min_distance, max_distance; - - if (bld->type == siegeengine_type::Ballista) + if (raytrace.hits()) { - min_distance = 0; - max_distance = 200; + if (raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second) + return "ok"; + else + return "out_of_range"; } else - { - min_distance = 30; - max_distance = 100; - } + return "blocked"; +} + +static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d ltop, df::coord2d size) +{ + CHECK_NULL_POINTER(bld); - df::coord cursor = Gui::getCursorPos(); + df::coord origin(bld->centerx, bld->centery, bld->z); + coord_range building_rect( + df::coord(bld->x1, bld->y1, bld->z), + df::coord(bld->x2, bld->y2, bld->z) + ); + + auto engine = find_engine(bld); + auto fire_range = get_engine_range(bld); 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 (tile_pos == cursor) - continue; - if (tile_pos.z == bld->z && - tile_pos.x >= bld->x1 && tile_pos.x <= bld->x2 && - tile_pos.y >= bld->y1 && tile_pos.y <= bld->y2) + if (is_in_range(building_rect, tile_pos)) continue; Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); @@ -306,22 +370,13 @@ void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d continue; ProjectilePath path(origin, tile_pos); - - if (path.speed.z != 0 && abs(path.speed.z) != path.divisor) { - path.divisor *= 20; - path.speed.x *= 20; - path.speed.y *= 20; - path.speed.z *= 20; - path.speed.z += 9; - } - PathMetrics raytrace(path, tile_pos); int color; if (raytrace.hits()) { - if (raytrace.goal_step >= min_distance && - raytrace.goal_step <= max_distance) + if (raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second) color = COLOR_GREEN; else color = COLOR_CYAN; @@ -357,6 +412,7 @@ void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df::coord2d DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(clearTargetArea), DFHACK_LUA_FUNCTION(setTargetArea), + DFHACK_LUA_FUNCTION(getTileStatus), DFHACK_LUA_FUNCTION(paintAimScreen), DFHACK_LUA_END }; diff --git a/scripts/devel/pop-screen.lua b/scripts/devel/pop-screen.lua new file mode 100644 index 000000000..f1ee072c5 --- /dev/null +++ b/scripts/devel/pop-screen.lua @@ -0,0 +1,3 @@ +-- For killing bugged out gui script screens. + +dfhack.screen.dismiss(dfhack.gui.getCurViewscreen()) diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index 4468e1dcb..c14bfcbe9 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -122,7 +122,7 @@ function MechanismList:onInput(keys) end end -if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then 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 466657e56..bba6bce89 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -6,6 +6,11 @@ local guidm = require 'gui.dwarfmode' local dlg = require 'gui.dialogs' local plugin = require 'plugins.siege-engine' +local wmap = df.global.world.map + +-- Globals kept between script calls +last_target_min = last_target_min or nil +last_target_max = last_target_max or nil SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) @@ -15,9 +20,17 @@ function SiegeEngine:init(building) self:init_fields{ building = building, center = utils.getBuildingCenter(building), - links = {}, selected = 1 + links = {}, selected = 1, } 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', + } return self end @@ -26,40 +39,263 @@ function SiegeEngine:onShow() self.old_cursor = guidm.getCursorPos() self.old_viewport = self:getViewport() + + self.mode = self.mode_main + self:showCursor(false) end function SiegeEngine:onDestroy() - guidm.setCursorPos(self.old_cursor) - self:getViewport(self.old_viewport):set() + self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) end -function SiegeEngine:onRenderBody(dc) - dc:clear() - dc:seek(1,1):string(utils.getBuildingName(self.building), COLOR_WHITE):newline() +function SiegeEngine:showCursor(enable) + local cursor = guidm.getCursorPos() + if cursor and not enable then + self.cursor = cursor + self.target_select_first = nil + guidm.clearCursorPos() + elseif not cursor and enable then + local view = self:getViewport() + cursor = self.cursor + if not cursor or not view:isVisible(cursor) then + cursor = view:getCenter() + end + self.cursor = nil + guidm.setCursorPos(cursor) + end +end +function SiegeEngine:centerViewOn(pos) + local cursor = guidm.getCursorPos() + if cursor then + guidm.setCursorPos(pos) + else + self.cursor = pos + end + self:getViewport():centerOn(pos):set() +end + +function SiegeEngine:zoomToTarget() + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + local cx = math.floor((target_min.x + target_max.x)/2) + local cy = math.floor((target_min.y + target_max.y)/2) + local cz = math.floor((target_min.z + target_max.z)/2) + for z = cz,target_max.z do + if plugin.getTileStatus(self.building, xyz2pos(cx,cy,z)) ~= 'blocked' then + cz = z + break + end + end + self:centerViewOn(xyz2pos(cx,cy,cz)) + end +end + +function paint_target_grid(dc, view, origin, p1, p2) + local r1, sz, r2 = guidm.getSelectionRange(p1, p2) + + if view.z < r1.z or view.z > r2.z then + return + end + + local p1 = view:tileToScreen(r1) + local p2 = view:tileToScreen(r2) + local org = view:tileToScreen(origin) + dc:pen{ fg = COLOR_CYAN, bg = COLOR_CYAN, ch = '+', bold = true } + + -- Frame + dc:fill(p1.x,p1.y,p1.x,p2.y) + dc:fill(p1.x,p1.y,p2.x,p1.y) + dc:fill(p2.x,p1.y,p2.x,p2.y) + dc:fill(p1.x,p2.y,p2.x,p2.y) + + -- Grid + local gxmin = org.x+10*math.ceil((p1.x-org.x)/10) + local gxmax = org.x+10*math.floor((p2.x-org.x)/10) + local gymin = org.y+10*math.ceil((p1.y-org.y)/10) + local gymax = org.y+10*math.floor((p2.y-org.y)/10) + for x = gxmin,gxmax,10 do + for y = gymin,gymax,10 do + dc:fill(p1.x,y,p2.x,y) + dc:fill(x,p1.y,x,p2.y) + end + end +end + +function SiegeEngine:renderTargetView(target_min, target_max) local view = self:getViewport() local map = self.df_layout.map + local map_dc = gui.Painter.new(map) plugin.paintAimScreen( self.building, view:getPos(), xy2pos(map.x1, map.y1), view:getSize() ) - dc:newline():newline(1):pen(COLOR_WHITE) - dc:string("Esc", COLOR_LIGHTGREEN):string(": Back, ") - dc:string("Enter", COLOR_LIGHTGREEN):string(": Switch") + if target_min and math.floor(dfhack.getTickCount()/500) % 2 == 0 then + paint_target_grid(map_dc, view, self.center, target_min, target_max) + end + + local cursor = guidm.getCursorPos() + if cursor then + local cx, cy, cz = pos2xyz(view:tileToScreen(cursor)) + if cz == 0 then + map_dc:seek(cx,cy):char('X', COLOR_YELLOW) + end + end +end + +function SiegeEngine:onRenderBody_main(dc) + dc:newline(1):pen(COLOR_WHITE):string("Target: ") + + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + dc:string( + (target_max.x-target_min.x+1).."x".. + (target_max.y-target_min.y+1).."x".. + (target_max.z-target_min.z+1).." Rect" + ) + else + dc:string("None (default)") + end + + dc:newline(3):string("r",COLOR_LIGHTGREEN):string(": Rectangle") + if last_target_min then + dc:string(", "):string("p",COLOR_LIGHTGREEN):string(": Paste") + end + dc:newline(3) + if target_min then + dc:string("x",COLOR_LIGHTGREEN):string(": Clear, ") + dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") + end + + if self.target_select_first then + self:renderTargetView(self.target_select_first, guidm.getCursorPos()) + else + self:renderTargetView(target_min, target_max) + end +end + +function SiegeEngine:setTargetArea(p1, p2) + self.target_select_first = nil + + if not plugin.setTargetArea(self.building, p1, p2) then + dlg.showMessage( + 'Set Target Area', + 'Could not set the target area', COLOR_LIGHTRED + ) + else + last_target_min = p1 + last_target_max = p2 + end +end + +function SiegeEngine:onInput_main(keys) + if keys.CUSTOM_R then + self:showCursor(true) + self.target_select_first = nil + self.mode = self.mode_aim + elseif keys.CUSTOM_P and last_target_min then + self:setTargetArea(last_target_min, last_target_max) + elseif keys.CUSTOM_Z then + self:zoomToTarget() + elseif keys.CUSTOM_X then + plugin.clearTargetArea(self.building) + elseif self:simulateViewScroll(keys) then + self.cursor = nil + else + return false + end + return true +end + +local status_table = { + ok = { pen = COLOR_GREEN, msg = "Target accessible" }, + out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" }, + blocked = { pen = COLOR_RED, msg = "Target obstructed" }, +} + +function SiegeEngine:onRenderBody_aim(dc) + local cursor = guidm.getCursorPos() + local first = self.target_select_first + + dc:newline(1):string('Select target rectangle'):newline() + + local info = status_table[plugin.getTileStatus(self.building, cursor)] + if info then + dc:newline(2):string(info.msg, info.pen) + else + dc:newline(2):string('ERROR', COLOR_RED) + end + + dc:newline():newline(1):string("Enter",COLOR_LIGHTGREEN) + if first then + dc:string(": Finish rectangle") + else + dc:string(": Start rectangle") + end + dc:newline() + + local target_min, target_max = plugin.getTargetArea(self.building) + if target_min then + dc:newline(1):string("z",COLOR_LIGHTGREEN):string(": Zoom to current target") + end + + if first then + self:renderTargetView(first, cursor) + else + local target_min, target_max = plugin.getTargetArea(self.building) + self:renderTargetView(target_min, target_max) + end +end + +function SiegeEngine:onInput_aim(keys) + if keys.SELECT then + local cursor = guidm.getCursorPos() + if self.target_select_first then + self:setTargetArea(self.target_select_first, cursor) + + self.mode = self.mode_main + self:showCursor(false) + else + self.target_select_first = cursor + end + elseif keys.CUSTOM_Z then + self:zoomToTarget() + elseif keys.LEAVESCREEN then + self.mode = self.mode_main + self:showCursor(false) + elseif self:simulateCursorMovement(keys) then + self.cursor = nil + else + return false + end + return true +end + +function SiegeEngine:onRenderBody(dc) + dc:clear() + dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline() + + self.mode.render(dc) + + dc:seek(1, math.max(dc:localY(), 21)):pen(COLOR_WHITE) + dc:string("ESC", COLOR_LIGHTGREEN):string(": Back, ") + dc:string("c", COLOR_LIGHTGREEN):string(": Recenter") end function SiegeEngine:onInput(keys) - if keys.LEAVESCREEN then + if self.mode.input(keys) then + -- + elseif keys.CUSTOM_C then + self:centerViewOn(self.center) + elseif keys.LEAVESCREEN then self:dismiss() - elseif self:simulateCursorMovement(keys, self.center) then - return end end -if not string.find(dfhack.gui.getCurFocus(), 'dwarfmode/QueryBuilding/Some') then - qerror("This script requires the main dwarfmode view in 'q' mode") +if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/SiegeEngine') then + qerror("This script requires a siege engine selected in 'q' mode") end local building = df.global.world.selected_building From 003c3391d1a1712c79ef3c151441bfc798fee3d0 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Sep 2012 15:49:46 +0400 Subject: [PATCH 08/48] Implement aiming projectiles at random points in the designated area. --- plugins/devel/siege-engine.cpp | 252 ++++++++++++++++++++++++++++----- 1 file changed, 219 insertions(+), 33 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 6906f540c..86fe3ba5e 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -29,6 +29,7 @@ #include "df/ui_build_selector.h" #include "df/flow_info.h" #include "df/report.h" +#include "df/proj_itemst.h" #include "MiscUtils.h" @@ -106,6 +107,11 @@ static void orient_engine(df::building_siegeenginest *bld, df::coord target) df::building_siegeenginest::Up; } +static int random_int(int val) +{ + return int(int64_t(rand())*val/RAND_MAX); +} + /* * Configuration management */ @@ -115,12 +121,15 @@ static bool enable_plugin(); struct EngineInfo { int id; coord_range target; + df::coord center; 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; } }; static std::map engines; +static std::map coord_engines; static EngineInfo *find_engine(df::building *bld, bool create = false) { @@ -135,12 +144,26 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) auto *obj = &engines[bld]; obj->id = bld->id; + obj->center = df::coord(bld->centerx, bld->centery, bld->z); + + coord_engines[obj->center] = bld; return obj; } -static void load_engines() +static EngineInfo *find_engine(df::coord pos) +{ + return find_engine(coord_engines[pos]); +} + +static void clear_engines() { engines.clear(); + coord_engines.clear(); +} + +static void load_engines() +{ + clear_engines(); auto pworld = Core::getInstance().getWorld(); std::vector vec; @@ -224,12 +247,30 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, */ struct ProjectilePath { - df::coord origin, target; - int divisor; + df::coord origin, goal, target, fudge_delta; + int divisor, fudge_factor; df::coord speed, direction; - ProjectilePath(df::coord origin, df::coord target) : - origin(origin), target(target) + ProjectilePath(df::coord origin, df::coord goal) : + origin(origin), goal(goal), target(goal), fudge_factor(1) + { + fudge_delta = df::coord(0,0,0); + calc_line(); + } + + void fudge(int factor, df::coord delta) + { + fudge_factor = factor; + fudge_delta = delta; + auto diff = goal - origin; + diff.x *= fudge_factor; + diff.y *= fudge_factor; + diff.z *= fudge_factor; + target = origin + diff + fudge_delta; + calc_line(); + } + + void calc_line() { speed = target - origin; divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); @@ -237,7 +278,8 @@ struct ProjectilePath { direction = df::coord(speed.x>=0?1:-1,speed.y>=0?1:-1,speed.z>=0?1:-1); } - df::coord operator[] (int i) const { + df::coord operator[] (int i) const + { int div2 = divisor * 2; int bias = divisor-1; return origin + df::coord( @@ -266,12 +308,12 @@ struct PathMetrics { int goal_step, goal_z_step; std::vector coords; - bool hits() { return goal_step != -1 && collision_step > goal_step; } + bool hits() { return collision_step > goal_step; } - PathMetrics(const ProjectilePath &path, df::coord goal, bool list_coords = false) + PathMetrics(const ProjectilePath &path, bool list_coords = false) { coords.clear(); - collision_step = goal_step = goal_z_step = -1; + collision_step = goal_step = goal_z_step = 1000000; int step = 0; df::coord prev_pos = path.origin; @@ -285,11 +327,10 @@ struct PathMetrics { if (list_coords) coords.push_back(cur_pos); - if (cur_pos.z == goal.z) + if (cur_pos.z == path.goal.z) { - if (goal_z_step == -1) - goal_z_step = step; - if (cur_pos == goal) + goal_z_step = std::min(step, goal_z_step); + if (cur_pos == path.goal) goal_step = step; } @@ -324,18 +365,68 @@ struct PathMetrics { } }; +struct AimContext { + df::building_siegeenginest *bld; + df::coord origin; + coord_range building_rect; + EngineInfo *engine; + std::pair fire_range; + + AimContext(df::building_siegeenginest *bld, EngineInfo *engine) + : bld(bld), engine(engine) + { + origin = df::coord(bld->centerx, bld->centery, bld->z); + building_rect = coord_range( + df::coord(bld->x1, bld->y1, bld->z), + df::coord(bld->x2, bld->y2, bld->z) + ); + fire_range = get_engine_range(bld); + } + + bool isInRange(const PathMetrics &raytrace) + { + return raytrace.goal_step >= fire_range.first && + raytrace.goal_step <= fire_range.second; + } + + bool adjustToPassable(df::coord *pos) + { + if (isPassableTile(*pos)) + return true; + + for (df::coord fudge = *pos; + fudge.z < engine->target.second.z; fudge.z++) + { + if (!isPassableTile(fudge)) + continue; + *pos = fudge; + return true; + } + + for (df::coord fudge = *pos; + fudge.z > engine->target.first.z; fudge.z--) + { + if (!isPassableTile(fudge)) + continue; + *pos = fudge; + return true; + } + + return false; + } + +}; + static std::string getTileStatus(df::building_siegeenginest *bld, df::coord tile_pos) { - df::coord origin(bld->centerx, bld->centery, bld->z); - auto fire_range = get_engine_range(bld); + AimContext context(bld, NULL); - ProjectilePath path(origin, tile_pos); - PathMetrics raytrace(path, tile_pos); + ProjectilePath path(context.origin, tile_pos); + PathMetrics raytrace(path); if (raytrace.hits()) { - if (raytrace.goal_step >= fire_range.first && - raytrace.goal_step <= fire_range.second) + if (context.isInRange(raytrace)) return "ok"; else return "out_of_range"; @@ -348,35 +439,27 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: { CHECK_NULL_POINTER(bld); - df::coord origin(bld->centerx, bld->centery, bld->z); - coord_range building_rect( - df::coord(bld->x1, bld->y1, bld->z), - df::coord(bld->x2, bld->y2, bld->z) - ); - - auto engine = find_engine(bld); - auto fire_range = get_engine_range(bld); + AimContext context(bld, find_engine(bld)); 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(building_rect, tile_pos)) + if (is_in_range(context.building_rect, tile_pos)) continue; Pen cur_tile = Screen::readTile(ltop.x+x, ltop.y+y); if (!cur_tile.valid()) continue; - ProjectilePath path(origin, tile_pos); - PathMetrics raytrace(path, tile_pos); + ProjectilePath path(context.origin, tile_pos); + PathMetrics raytrace(path); int color; if (raytrace.hits()) { - if (raytrace.goal_step >= fire_range.first && - raytrace.goal_step <= fire_range.second) + if (context.isInRange(raytrace)) color = COLOR_GREEN; else color = COLOR_CYAN; @@ -395,7 +478,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: cur_tile.bg = color; } - cur_tile.bold = (engine && engine->onTarget(tile_pos)); + cur_tile.bold = (context.engine && context.engine->onTarget(tile_pos)); if (cur_tile.tile) cur_tile.tile_mode = Pen::CharColor; @@ -405,6 +488,105 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: } } +/* + * Projectile hook + */ + +struct projectile_hook : df::proj_itemst { + typedef df::proj_itemst interpose_base; + + void aimAtPoint(AimContext &context, ProjectilePath &path, bool bad_shot = false) + { + target_pos = path.target; + + 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]); + + if (flags.bits.piercing) + { + if (bad_shot) + fall_threshold = std::min(raytrace.goal_z_step, raytrace.collision_step); + } + else + { + if (bad_shot) + fall_threshold = context.fire_range.second; + else + fall_threshold = raytrace.goal_step; + } + + fall_threshold = std::max(fall_threshold, context.fire_range.first); + fall_threshold = std::min(fall_threshold, context.fire_range.second); + } + + void aimAtArea(AimContext &context) + { + df::coord target, last_passable; + df::coord tbase = context.engine->target.first; + df::coord tsize = context.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 (context.adjustToPassable(&target)) + last_passable = target; + else + continue; + + ProjectilePath path(context.origin, target); + PathMetrics raytrace(path); + + if (raytrace.hits() && context.isInRange(raytrace)) + { + aimAtPoint(context, path); + return; + } + } + + if (!last_passable.isValid()) + last_passable = target; + + ProjectilePath path(context.origin, last_passable); + aimAtPoint(context, path, true); + } + + void doCheckMovement() + { + if (distance_flown != 0 || fall_counter != fall_delay) + return; + + auto engine = find_engine(origin_pos); + if (!engine || !engine->hasTarget()) + return; + + auto bld0 = df::building::find(engine->id); + auto bld = strict_virtual_cast(bld0); + if (!bld) + return; + + AimContext context(bld, engine); + + aimAtArea(context); + } + + DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ()) + { + if (flags.bits.high_flying || flags.bits.piercing) + doCheckMovement(); + + return INTERPOSE_NEXT(checkMovement)(); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement); + /* * Initialization */ @@ -428,8 +610,12 @@ static void enable_hooks(bool enable) { is_enabled = enable; + INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); + if (enable) load_engines(); + else + clear_engines(); } static bool enable_plugin() From fb88aad51d9a49f489ed99fb1c33f9212b38c068 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sat, 8 Sep 2012 21:07:18 +0400 Subject: [PATCH 09/48] Reverse-engineer unit speed computation from DF code. --- library/xml | 2 +- plugins/devel/siege-engine.cpp | 293 +++++++++++++++++++++++++++++++++ 2 files changed, 294 insertions(+), 1 deletion(-) diff --git a/library/xml b/library/xml index a914f3b75..18e76d8bd 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a914f3b7558335d53c0ac93f6e7267906a33cd29 +Subproject commit 18e76d8bdd3d7e604c8bb40e62cd1fd7c4647e36 diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 86fe3ba5e..60035b276 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,14 @@ #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 "MiscUtils.h" @@ -488,6 +497,290 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: } } +/* + * Unit tracking + */ + +static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) +{ + auto creature = df::creature_raw::find(race); + if (!creature) + return false; + + auto craw = vector_get(creature->caste, caste); + if (!craw) + return false; + + return craw->flags.is_set(flag); +} + +static bool hasExtravision(df::unit *unit) +{ + if (unit->curse.rem_tags1.bits.EXTRAVISION) + return false; + if (unit->curse.add_tags1.bits.EXTRAVISION) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); +} + +int getEffectiveSkill(df::unit *unit, df::job_skill skill_id) +{ + CHECK_NULL_POINTER(unit); + + if (!unit->status.current_soul) + return 0; + + int rating = 0; + + df::enum_field key(skill_id); + auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); + if (skill) + rating = std::max(0, int(skill->rating) - skill->rusty); + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) rating >>= 1; + if (unit->counters.winded > 0) rating >>= 1; + if (unit->counters.stunned > 0) rating >>= 1; + if (unit->counters.dizziness > 0) rating >>= 1; + if (unit->counters2.fever > 0) rating >>= 1; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (unit->counters.pain >= 100 && unit->mood == -1) rating >>= 1; + + if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle && + !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged && + !hasExtravision(unit)) + { + rating >>= 2; + } + if (unit->counters2.exhaustion >= 2000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 4000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 6000) + rating = rating*3/4; + } + } + } + + // TODO: bloodsucker; advmode + + if (unit->counters2.thirst_timer >= 50000) rating >>= 1; + if (unit->counters2.hunger_timer >= 75000) rating >>= 1; + if (unit->counters2.sleepiness_timer >= 150000) rating >>= 1; + + return rating; +} + +static int getAttrValue(const df::unit_attribute &attr) +{ + return std::max(0, attr.value - attr.soft_demotion); +} + +static int getPhysAttrValue(df::unit *unit, df::physical_attribute_type attr, bool hide_curse) +{ + int value = getAttrValue(unit->body.physical_attrs[attr]); + + if (auto mod = unit->curse.attr_change) + { + int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; + + if (hide_curse) + value = std::min(value, mvalue); + else + value = mvalue; + } + + return value; +} + +int getSpeedRating(df::unit *unit) +{ + using namespace df::enums::physical_attribute_type; + + // Base speed + auto creature = df::creature_raw::find(unit->race); + if (!creature) + return 0; + + auto craw = vector_get(creature->caste, unit->caste); + if (!craw) + return 0; + + int speed = craw->misc.speed; + + if (unit->flags3.bits.ghostly) + return speed; + + // Curse multiplier + if (unit->curse.speed_mul_percent != 100) + { + speed *= 100; + if (unit->curse.speed_mul_percent != 0) + speed /= unit->curse.speed_mul_percent; + } + + speed += unit->curse.speed_add; + + // Swimming + if (unit->flags2.bits.swimming) + { + speed = craw->misc.swim_speed; + if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma) + speed *= 2; + if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) + { + int skill = getEffectiveSkill(unit, job_skill::SWIMMING); + if (skill > 1) + skill = skill * std::max(6, 21-skill) / 20; + } + } + else + { + if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Water) + speed += 150*unit->status2.liquid_depth; + else if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma) + speed += 300*unit->status2.liquid_depth; + } + + // General counters + if (unit->profession == profession::BABY) + speed += 3000; + + if (unit->counters2.exhaustion >= 2000) + { + speed += 200; + if (unit->counters2.exhaustion >= 4000) + { + speed += 200; + if (unit->counters2.exhaustion >= 6000) + speed += 200; + } + } + + if (unit->flags2.bits.gutted) speed += 2000; + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) speed += 1000; + if (unit->counters.winded > 0) speed += 1000; + if (unit->counters.stunned > 0) speed += 1000; + if (unit->counters.dizziness > 0) speed += 1000; + if (unit->counters2.fever > 0) speed += 1000; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (unit->counters.pain >= 100 && unit->mood < 0) + speed += 1000; + } + + // TODO: bloodsucker + + if (unit->counters2.thirst_timer >= 50000) speed += 100; + if (unit->counters2.hunger_timer >= 75000) speed += 100; + if (unit->counters2.sleepiness_timer >= 150000) speed += 200; + else if (unit->counters2.sleepiness_timer >= 57600) speed += 100; + + if (unit->relations.draggee_id != -1) speed += 1000; + + if (unit->flags1.bits.on_ground) + speed += 2000; + else if (unit->flags3.bits.on_crutch) + { + int skill = getEffectiveSkill(unit, job_skill::CRUTCH_WALK); + speed += 2000 - 100*std::min(20, skill); + } + + // TODO: hidden_in_ambush + + if (unsigned(unit->counters2.paralysis-1) <= 98) + speed += unit->counters2.paralysis*10; + if (unsigned(unit->counters.webbed-1) <= 8) + speed += unit->counters.webbed*100; + + // Muscle weight vs agility + auto &attr_unk3 = unit->body.physical_attr_unk3; + int strength = attr_unk3[STRENGTH]; + int agility = attr_unk3[AGILITY]; + speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*strength/agility))); + + // Attributes + bool hide_curse = false; + if (!unit->job.hunt_target) + { + auto identity = Units::getIdentity(unit); + if (identity && identity->unk_4c == 0) + hide_curse = true; + } + + int strength_attr = getPhysAttrValue(unit, STRENGTH, hide_curse); + int agility_attr = getPhysAttrValue(unit, AGILITY, hide_curse); + int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr)); + + speed = ((total_attr-200)*(speed*3/2) + (3800-total_attr)*(speed/2))/4800; // ?? + + if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) + { + // WTF + int as = unit->status2.able_stand; + int x = (as-1) - (as>>1); + int y = as - unit->status2.able_stand_impair; + if (unit->flags3.bits.on_crutch) y--; + y = y * 500 / x; + if (y > 0) speed += y; + } + + if (unit->mood == mood_type::Melancholy) speed += 8000; + + // Inventory encumberance + int armor_skill = getEffectiveSkill(unit, job_skill::ARMOR); + armor_skill = std::min(15, armor_skill); + + int inv_weight = 0, inv_weight_fraction = 0; + + for (size_t i = 0; i < unit->inventory.size(); i++) + { + auto item = unit->inventory[i]->item; + if (!item->flags.bits.weight_computed) + continue; + + int wval = item->weight; + int wfval = item->weight_fraction; + + auto mode = unit->inventory[i]->mode; + if ((mode == df::unit_inventory_item::Worn || + mode == df::unit_inventory_item::WrappedAround) && + item->isArmor() && armor_skill > 1) + { + wval = wval * (15 - armor_skill) / 16; + wfval = wfval * (15 - armor_skill) / 16; + } + + inv_weight += wval; + inv_weight_fraction += wfval; + } + + int total_weight = inv_weight*100 + inv_weight_fraction/10000; + int free_weight = std::max(1, attr_unk3[STRENGTH]/10 + strength_attr*3); + + if (free_weight < total_weight) + { + int delta = (total_weight - free_weight)/10; + delta = std::min(5000, delta); // dwarfmode only + speed += delta; + } + + // skipped: unknown loop on inventory items that amounts to 0 change + + return std::min(10000, std::max(0, speed)); +} + /* * Projectile hook */ From 8e0f3e3bce25484ecd37280e764302737acb8443 Mon Sep 17 00:00:00 2001 From: warmist Date: Sun, 9 Sep 2012 02:28:07 +0300 Subject: [PATCH 10/48] Added ListBox to gui.dialogs A listbox class. Can be either filled with table of strings, or string+callback tables. Needs some code revision :) --- library/lua/gui/dialogs.lua | 95 +++++++++++++++++++++++++++++++++++++ 1 file changed, 95 insertions(+) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index c4f15c9ac..dc4358119 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -172,5 +172,100 @@ function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_wi }:show() end +ListBox = defclass(ListBox, MessageBox) + +ListBox.focus_path = 'ListBox' + +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 +end + +function ListBox:getWantedFrameSize() + local mw, mh = MessageBox.getWantedFrameSize(self) + return mw, mh+#self.choices +end + +function ListBox:onRenderBody(dc) + MessageBox.onRenderBody(self, dc) + + dc:newline(1) + + if self.selection>dc.height-3 then + self.page_top=self.selection-(dc.height-3) + elseif self.selection0 then + self.page_top=self.selection-1 + end + for i,entry in ipairs(self.choices) do + if type(entry)=="table" then + entry=entry[1] + end + if i>self.page_top then + if i == self.selection then + dc:pen(self.select_pen or COLOR_LIGHTCYAN) + else + dc:pen(self.text_pen or COLOR_GREY) + end + dc:string(entry) + dc:newline(1) + end + end +end +function ListBox:moveCursor(delta) + local newsel=self.selection+delta + if #self.choices ~=0 then + if newsel<1 or newsel>#self.choices then + newsel=newsel % #self.choices + end + end + self.selection=newsel +end +function ListBox:onInput(keys) + if keys.SELECT then + self:dismiss() + local choice=self.choices[self.selection] + if self.on_input then + self.on_input(self.selection,choice) + end + + if choice and choice[2] then + choice[2](choice,self.selection) -- maybe reverse the arguments? + end + elseif keys.LEAVESCREEN then + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + elseif keys.CURSOR_UP then + self:moveCursor(-1) + elseif keys.CURSOR_DOWN then + self:moveCursor(1) + elseif keys.CURSOR_UP_FAST then + self:moveCursor(-10) + elseif keys.CURSOR_DOWN_FAST then + self:moveCursor(10) + end +end + +function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width) + mkinstance(ListBox):init{ + title = title, + text = text, + text_pen = tcolor, + choices = choices, + on_input = on_input, + on_cancel = on_cancel, + frame_width = min_width, + }:show() +end return _ENV From 94b729579e924bb511523a1aa157cec7fdb66bdf Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 10:53:08 +0400 Subject: [PATCH 11/48] Reindent to remove tabs. --- library/lua/gui/dialogs.lua | 86 ++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index dc4358119..eb883465f 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -180,10 +180,10 @@ function ListBox:init(info) info = info or {} self:init_fields{ selection = info.selection or 0, - choices = info.choices or {}, + choices = info.choices or {}, select_pen = info.select_pen, on_input = info.on_input, - page_top = 0 + page_top = 0 } MessageBox.init(self, info) self.on_accept = nil @@ -197,62 +197,62 @@ end function ListBox:onRenderBody(dc) MessageBox.onRenderBody(self, dc) - + dc:newline(1) - - if self.selection>dc.height-3 then - self.page_top=self.selection-(dc.height-3) - elseif self.selection0 then - self.page_top=self.selection-1 - end - for i,entry in ipairs(self.choices) do - if type(entry)=="table" then - entry=entry[1] - end - if i>self.page_top then - if i == self.selection then - dc:pen(self.select_pen or COLOR_LIGHTCYAN) - else - dc:pen(self.text_pen or COLOR_GREY) - end - dc:string(entry) - dc:newline(1) - end - end + + if self.selection>dc.height-3 then + self.page_top=self.selection-(dc.height-3) + elseif self.selection0 then + self.page_top=self.selection-1 + end + for i,entry in ipairs(self.choices) do + if type(entry)=="table" then + entry=entry[1] + end + if i>self.page_top then + if i == self.selection then + dc:pen(self.select_pen or COLOR_LIGHTCYAN) + else + dc:pen(self.text_pen or COLOR_GREY) + end + dc:string(entry) + dc:newline(1) + end + end end function ListBox:moveCursor(delta) - local newsel=self.selection+delta - if #self.choices ~=0 then - if newsel<1 or newsel>#self.choices then - newsel=newsel % #self.choices - end - end - self.selection=newsel + local newsel=self.selection+delta + if #self.choices ~=0 then + if newsel<1 or newsel>#self.choices then + newsel=newsel % #self.choices + end + end + self.selection=newsel end function ListBox:onInput(keys) if keys.SELECT then self:dismiss() - local choice=self.choices[self.selection] + local choice=self.choices[self.selection] if self.on_input then self.on_input(self.selection,choice) end - - if choice and choice[2] then - choice[2](choice,self.selection) -- maybe reverse the arguments? - end + + if choice and choice[2] then + choice[2](choice,self.selection) -- maybe reverse the arguments? + end elseif keys.LEAVESCREEN then self:dismiss() if self.on_cancel then self.on_cancel() end - elseif keys.CURSOR_UP then - self:moveCursor(-1) - elseif keys.CURSOR_DOWN then - self:moveCursor(1) - elseif keys.CURSOR_UP_FAST then - self:moveCursor(-10) - elseif keys.CURSOR_DOWN_FAST then - self:moveCursor(10) + elseif keys.CURSOR_UP then + self:moveCursor(-1) + elseif keys.CURSOR_DOWN then + self:moveCursor(1) + elseif keys.CURSOR_UP_FAST then + self:moveCursor(-10) + elseif keys.CURSOR_DOWN_FAST then + self:moveCursor(10) end end From a36fe25e7249c60094a6347726f961779cf3b98a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 12:27:40 +0400 Subject: [PATCH 12/48] Finish the effective skill computation function, and move to core. --- LUA_API.rst | 15 +++ Lua API.html | 15 +++ library/LuaApi.cpp | 7 ++ library/include/modules/Units.h | 12 +++ library/modules/Units.cpp | 159 +++++++++++++++++++++++++++++++- library/xml | 2 +- plugins/devel/siege-engine.cpp | 82 +--------------- 7 files changed, 209 insertions(+), 83 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 4d9170d6e..48d2c19e6 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -868,6 +868,17 @@ Units module Returns the nemesis record of the unit if it has one, or *nil*. +* ``dfhack.units.isCrazed(unit)`` +* ``dfhack.units.isOpposedToLife(unit)`` +* ``dfhack.units.hasExtravision(unit)`` +* ``dfhack.units.isBloodsucker(unit)`` + + Simple checks of caste attributes that can be modified by curses. + +* ``dfhack.units.getMiscTrait(unit, type[, create])`` + + Finds (or creates if requested) a misc trait object with the given id. + * ``dfhack.units.isDead(unit)`` The unit is completely dead and passive, or a ghost. @@ -894,6 +905,10 @@ Units module Returns the age of the unit in years as a floating-point value. If ``true_age`` is true, ignores false identities. +* ``dfhack.units.getEffectiveSkill(unit, skill)`` + + Computes the effective rating for the given skill, taking into account exhaustion, pain etc. + * ``dfhack.units.getNoblePositions(unit)`` Returns a list of tables describing noble position assignments, or *nil*. diff --git a/Lua API.html b/Lua API.html index dc9c8d73e..c302c29f7 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1106,6 +1106,18 @@ a lua list containing them.

  • dfhack.units.getNemesis(unit)

    Returns the nemesis record of the unit if it has one, or nil.

  • +
  • dfhack.units.isCrazed(unit)

    +
  • +
  • dfhack.units.isOpposedToLife(unit)

    +
  • +
  • dfhack.units.hasExtravision(unit)

    +
  • +
  • dfhack.units.isBloodsucker(unit)

    +

    Simple checks of caste attributes that can be modified by curses.

    +
  • +
  • dfhack.units.getMiscTrait(unit, type[, create])

    +

    Finds (or creates if requested) a misc trait object with the given id.

    +
  • dfhack.units.isDead(unit)

    The unit is completely dead and passive, or a ghost.

  • @@ -1126,6 +1138,9 @@ same checks the game uses to decide game-over by extinction.

    Returns the age of the unit in years as a floating-point value. If true_age is true, ignores false identities.

    +
  • dfhack.units.getEffectiveSkill(unit, skill)

    +

    Computes the effective rating for the given skill, taking into account exhaustion, pain etc.

    +
  • dfhack.units.getNoblePositions(unit)

    Returns a list of tables describing noble position assignments, or nil. Every table has fields entity, assignment and position.

    diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 807cbf539..6caf45575 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -79,6 +79,7 @@ distribution. #include "df/building_civzonest.h" #include "df/region_map_entry.h" #include "df/flow_info.h" +#include "df/unit_misc_trait.h" #include #include @@ -813,12 +814,18 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getVisibleName), WRAPM(Units, getIdentity), WRAPM(Units, getNemesis), + WRAPM(Units, isCrazed), + WRAPM(Units, isOpposedToLife), + WRAPM(Units, hasExtravision), + WRAPM(Units, isBloodsucker), + WRAPM(Units, getMiscTrait), WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), WRAPM(Units, isDwarf), WRAPM(Units, isCitizen), WRAPM(Units, getAge), + WRAPM(Units, getEffectiveSkill), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), WRAPM(Units, getProfessionColor), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 9003dc3af..ece151127 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -32,6 +32,8 @@ distribution. #include "modules/Items.h" #include "DataDefs.h" #include "df/unit.h" +#include "df/misc_trait_type.h" +#include "df/job_skill.h" namespace df { @@ -41,6 +43,7 @@ namespace df struct historical_entity; struct entity_position_assignment; struct entity_position; + struct unit_misc_trait; } /** @@ -208,6 +211,13 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); +DFHACK_EXPORT bool isCrazed(df::unit *unit); +DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); +DFHACK_EXPORT bool hasExtravision(df::unit *unit); +DFHACK_EXPORT bool isBloodsucker(df::unit *unit); + +DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false); + DFHACK_EXPORT bool isDead(df::unit *unit); DFHACK_EXPORT bool isAlive(df::unit *unit); DFHACK_EXPORT bool isSane(df::unit *unit); @@ -216,6 +226,8 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); +DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); + struct NoblePosition { df::historical_entity *entity; df::entity_position_assignment *assignment; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 874dabc3d..6a672b585 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -63,11 +63,15 @@ using namespace std; #include "df/burrow.h" #include "df/creature_raw.h" #include "df/caste_raw.h" +#include "df/game_mode.h" +#include "df/unit_misc_trait.h" +#include "df/unit_skill.h" using namespace DFHack; using namespace df::enums; using df::global::world; using df::global::ui; +using df::global::gamemode; bool Units::isValid() { @@ -626,8 +630,9 @@ static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) return craw->flags.is_set(flag); } -static bool isCrazed(df::unit *unit) +bool Units::isCrazed(df::unit *unit) { + CHECK_NULL_POINTER(unit); if (unit->flags3.bits.scuttle) return false; if (unit->curse.rem_tags1.bits.CRAZED) @@ -637,13 +642,54 @@ static bool isCrazed(df::unit *unit) return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CRAZED); } -static bool isOpposedToLife(df::unit *unit) +bool Units::isOpposedToLife(df::unit *unit) { + CHECK_NULL_POINTER(unit); if (unit->curse.rem_tags1.bits.OPPOSED_TO_LIFE) return false; if (unit->curse.add_tags1.bits.OPPOSED_TO_LIFE) return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::CANNOT_UNDEAD); + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::OPPOSED_TO_LIFE); +} + +bool Units::hasExtravision(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->curse.rem_tags1.bits.EXTRAVISION) + return false; + if (unit->curse.add_tags1.bits.EXTRAVISION) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); +} + +bool Units::isBloodsucker(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->curse.rem_tags1.bits.BLOODSUCKER) + return false; + if (unit->curse.add_tags1.bits.BLOODSUCKER) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER); +} + +df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create) +{ + CHECK_NULL_POINTER(unit); + + auto &vec = unit->status.misc_traits; + for (size_t i = 0; i < vec.size(); i++) + if (vec[i]->id == type) + return vec[i]; + + if (create) + { + auto obj = new df::unit_misc_trait(); + obj->id = type; + vec.push_back(obj); + return obj; + } + + return NULL; } bool DFHack::Units::isDead(df::unit *unit) @@ -753,6 +799,113 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age) return cur_time - birth_time; } +inline int adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2) +{ + if (is_adventure) + { + if (value >= adv1_2) rating >>= 1; + else if (value >= adv3_4) rating = rating*3/4; + else if (value >= adv9_10) rating = rating*9/10; + } + else + { + if (value >= dwarf1_2) rating >>= 1; + else if (value >= dwarf3_4) return rating*3/4; + } +} + +int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) +{ + CHECK_NULL_POINTER(unit); + + /* + * This is 100% reverse-engineered from DF code. + */ + + if (!unit->status.current_soul) + return 0; + + // Retrieve skill from unit soul: + + df::enum_field key(skill_id); + auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); + + int rating = 0; + if (skill) + rating = std::max(0, int(skill->rating) - skill->rusty); + + // Apply special states + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) rating >>= 1; + if (unit->counters.winded > 0) rating >>= 1; + if (unit->counters.stunned > 0) rating >>= 1; + if (unit->counters.dizziness > 0) rating >>= 1; + if (unit->counters2.fever > 0) rating >>= 1; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle && + !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged && + !hasExtravision(unit)) + { + rating >>= 2; + } + if (unit->counters.pain >= 100 && unit->mood == -1) + { + rating >>= 1; + } + if (unit->counters2.exhaustion >= 2000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 4000) + { + rating = rating*3/4; + if (unit->counters2.exhaustion >= 6000) + rating = rating*3/4; + } + } + } + + // Hunger etc timers + + bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE); + + if (!unit->flags3.bits.scuttle && isBloodsucker(unit)) + { + using namespace df::enums::misc_trait_type; + + if (auto trait = getMiscTrait(unit, TimeSinceSuckedBlood)) + { + adjust_skill_rating( + rating, is_adventure, trait->value, + 302400, 403200, // dwf 3/4; 1/2 + 1209600, 1209600, 2419200 // adv 9/10; 3/4; 1/2 + ); + } + } + + adjust_skill_rating( + rating, is_adventure, unit->counters2.thirst_timer, + 50000, 50000, 115200, 172800, 345600 + ); + adjust_skill_rating( + rating, is_adventure, unit->counters2.hunger_timer, + 75000, 75000, 172800, 1209600, 2592000 + ); + if (is_adventure && unit->counters2.sleepiness_timer >= 846000) + rating >>= 2; + else + adjust_skill_rating( + rating, is_adventure, unit->counters2.sleepiness_timer, + 150000, 150000, 172800, 259200, 345600 + ); + + return rating; +} + static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) { if (a.position->precedence < b.position->precedence) diff --git a/library/xml b/library/xml index 18e76d8bd..db765a65b 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 18e76d8bdd3d7e604c8bb40e62cd1fd7c4647e36 +Subproject commit db765a65b17099dbec115812b40a19b46ad59431 diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 60035b276..8b5010194 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -501,82 +501,6 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * Unit tracking */ -static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) -{ - auto creature = df::creature_raw::find(race); - if (!creature) - return false; - - auto craw = vector_get(creature->caste, caste); - if (!craw) - return false; - - return craw->flags.is_set(flag); -} - -static bool hasExtravision(df::unit *unit) -{ - if (unit->curse.rem_tags1.bits.EXTRAVISION) - return false; - if (unit->curse.add_tags1.bits.EXTRAVISION) - return true; - return casteFlagSet(unit->race, unit->caste, caste_raw_flags::EXTRAVISION); -} - -int getEffectiveSkill(df::unit *unit, df::job_skill skill_id) -{ - CHECK_NULL_POINTER(unit); - - if (!unit->status.current_soul) - return 0; - - int rating = 0; - - df::enum_field key(skill_id); - auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, key); - if (skill) - rating = std::max(0, int(skill->rating) - skill->rusty); - - if (unit->counters.soldier_mood == df::unit::T_counters::None) - { - if (unit->counters.nausea > 0) rating >>= 1; - if (unit->counters.winded > 0) rating >>= 1; - if (unit->counters.stunned > 0) rating >>= 1; - if (unit->counters.dizziness > 0) rating >>= 1; - if (unit->counters2.fever > 0) rating >>= 1; - } - - if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) - { - if (unit->counters.pain >= 100 && unit->mood == -1) rating >>= 1; - - if (!unit->flags3.bits.ghostly && !unit->flags3.bits.scuttle && - !unit->flags2.bits.vision_good && !unit->flags2.bits.vision_damaged && - !hasExtravision(unit)) - { - rating >>= 2; - } - if (unit->counters2.exhaustion >= 2000) - { - rating = rating*3/4; - if (unit->counters2.exhaustion >= 4000) - { - rating = rating*3/4; - if (unit->counters2.exhaustion >= 6000) - rating = rating*3/4; - } - } - } - - // TODO: bloodsucker; advmode - - if (unit->counters2.thirst_timer >= 50000) rating >>= 1; - if (unit->counters2.hunger_timer >= 75000) rating >>= 1; - if (unit->counters2.sleepiness_timer >= 150000) rating >>= 1; - - return rating; -} - static int getAttrValue(const df::unit_attribute &attr) { return std::max(0, attr.value - attr.soft_demotion); @@ -635,7 +559,7 @@ int getSpeedRating(df::unit *unit) speed *= 2; if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) { - int skill = getEffectiveSkill(unit, job_skill::SWIMMING); + int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING); if (skill > 1) skill = skill * std::max(6, 21-skill) / 20; } @@ -693,7 +617,7 @@ int getSpeedRating(df::unit *unit) speed += 2000; else if (unit->flags3.bits.on_crutch) { - int skill = getEffectiveSkill(unit, job_skill::CRUTCH_WALK); + int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK); speed += 2000 - 100*std::min(20, skill); } @@ -739,7 +663,7 @@ int getSpeedRating(df::unit *unit) if (unit->mood == mood_type::Melancholy) speed += 8000; // Inventory encumberance - int armor_skill = getEffectiveSkill(unit, job_skill::ARMOR); + int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR); armor_skill = std::min(15, armor_skill); int inv_weight = 0, inv_weight_fraction = 0; From ec3d489bda19f8ab2a45fbb19d7259ea3f4ad75b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 12:51:08 +0400 Subject: [PATCH 13/48] Move curse-affected attribute value getters to the core. --- LUA_API.rst | 9 ++++++ Lua API.html | 8 +++++ library/include/modules/Units.h | 6 ++++ library/modules/Units.cpp | 52 +++++++++++++++++++++++++++++++++ plugins/devel/siege-engine.cpp | 34 ++------------------- 5 files changed, 77 insertions(+), 32 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 48d2c19e6..686b90038 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -868,6 +868,15 @@ Units module Returns the nemesis record of the unit if it has one, or *nil*. +* ``dfhack.units.isHidingCurse(unit)`` + + Checks if the unit hides improved attributes from its curse. + +* ``dfhack.units.getPhysicalAttrValue(unit, attr_type)`` +* ``dfhack.units.getMentalAttrValue(unit, attr_type)`` + + Computes the effective attribute value, including curse effect. + * ``dfhack.units.isCrazed(unit)`` * ``dfhack.units.isOpposedToLife(unit)`` * ``dfhack.units.hasExtravision(unit)`` diff --git a/Lua API.html b/Lua API.html index c302c29f7..12bcf5fa8 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1106,6 +1106,14 @@ a lua list containing them.

  • dfhack.units.getNemesis(unit)

    Returns the nemesis record of the unit if it has one, or nil.

  • +
  • dfhack.units.isHidingCurse(unit)

    +

    Checks if the unit hides improved attributes from its curse.

    +
  • +
  • dfhack.units.getPhysicalAttrValue(unit, attr_type)

    +
  • +
  • dfhack.units.getMentalAttrValue(unit, attr_type)

    +

    Computes the effective attribute value, including curse effect.

    +
  • dfhack.units.isCrazed(unit)

  • dfhack.units.isOpposedToLife(unit)

    diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index ece151127..3fba5c218 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -33,6 +33,8 @@ distribution. #include "DataDefs.h" #include "df/unit.h" #include "df/misc_trait_type.h" +#include "df/physical_attribute_type.h" +#include "df/mental_attribute_type.h" #include "df/job_skill.h" namespace df @@ -211,6 +213,10 @@ DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); +DFHACK_EXPORT bool isHidingCurse(df::unit *unit); +DFHACK_EXPORT int getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr); +DFHACK_EXPORT int getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr); + DFHACK_EXPORT bool isCrazed(df::unit *unit); DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); DFHACK_EXPORT bool hasExtravision(df::unit *unit); diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 6a672b585..1565dbbd3 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -617,6 +617,58 @@ df::nemesis_record *Units::getNemesis(df::unit *unit) return NULL; } + +bool Units::isHidingCurse(df::unit *unit) +{ + if (!unit->job.hunt_target) + { + auto identity = Units::getIdentity(unit); + if (identity && identity->unk_4c == 0) + return true; + } + + return false; +} + +int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr) +{ + auto &aobj = unit->body.physical_attrs[attr]; + int value = std::max(0, aobj.value - aobj.soft_demotion); + + if (auto mod = unit->curse.attr_change) + { + int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; + + if (isHidingCurse(unit)) + value = std::min(value, mvalue); + else + value = mvalue; + } + + return value; +} + +int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) +{ + auto soul = unit->status.current_soul; + if (!soul) return 0; + + auto &aobj = soul->mental_attrs[attr]; + int value = std::max(0, aobj.value - aobj.soft_demotion); + + if (auto mod = unit->curse.attr_change) + { + int mvalue = (value * mod->ment_att_perc[attr]) + mod->ment_att_add[attr]; + + if (isHidingCurse(unit)) + value = std::min(value, mvalue); + else + value = mvalue; + } + + return value; +} + static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) { auto creature = df::creature_raw::find(race); diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 8b5010194..648e4c3b2 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -501,28 +501,6 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * Unit tracking */ -static int getAttrValue(const df::unit_attribute &attr) -{ - return std::max(0, attr.value - attr.soft_demotion); -} - -static int getPhysAttrValue(df::unit *unit, df::physical_attribute_type attr, bool hide_curse) -{ - int value = getAttrValue(unit->body.physical_attrs[attr]); - - if (auto mod = unit->curse.attr_change) - { - int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; - - if (hide_curse) - value = std::min(value, mvalue); - else - value = mvalue; - } - - return value; -} - int getSpeedRating(df::unit *unit) { using namespace df::enums::physical_attribute_type; @@ -635,18 +613,10 @@ int getSpeedRating(df::unit *unit) speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*strength/agility))); // Attributes - bool hide_curse = false; - if (!unit->job.hunt_target) - { - auto identity = Units::getIdentity(unit); - if (identity && identity->unk_4c == 0) - hide_curse = true; - } + int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH); + int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY); - int strength_attr = getPhysAttrValue(unit, STRENGTH, hide_curse); - int agility_attr = getPhysAttrValue(unit, AGILITY, hide_curse); int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr)); - speed = ((total_attr-200)*(speed*3/2) + (3800-total_attr)*(speed/2))/4800; // ?? if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) From 9679b7729c79888d8bdac99484dfad7a5e07d13e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 17:04:58 +0400 Subject: [PATCH 14/48] Clean up the movement speed calculation function and move into the core. --- LUA_API.rst | 4 + Lua API.html | 3 + library/LuaApi.cpp | 2 + library/include/modules/Units.h | 2 + library/modules/Units.cpp | 280 +++++++++++++++++++++++++++++++- library/xml | 2 +- plugins/devel/siege-engine.cpp | 177 +------------------- 7 files changed, 289 insertions(+), 181 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 686b90038..a5be09a0e 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -918,6 +918,10 @@ Units module Computes the effective rating for the given skill, taking into account exhaustion, pain etc. +* ``dfhack.units.computeMovementSpeed(unit)`` + + Computes number of frames * 100 it takes the unit to move in its current state of mind and body. + * ``dfhack.units.getNoblePositions(unit)`` Returns a list of tables describing noble position assignments, or *nil*. diff --git a/Lua API.html b/Lua API.html index 12bcf5fa8..7886fbc25 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1149,6 +1149,9 @@ If true_age is true, ignores false identities.
  • dfhack.units.getEffectiveSkill(unit, skill)

    Computes the effective rating for the given skill, taking into account exhaustion, pain etc.

  • +
  • dfhack.units.computeMovementSpeed(unit)

    +

    Computes number of frames * 100 it takes the unit to move in its current state of mind and body.

    +
  • dfhack.units.getNoblePositions(unit)

    Returns a list of tables describing noble position assignments, or nil. Every table has fields entity, assignment and position.

    diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 6caf45575..d39a945dd 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -818,6 +818,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isOpposedToLife), WRAPM(Units, hasExtravision), WRAPM(Units, isBloodsucker), + WRAPM(Units, isMischievous), WRAPM(Units, getMiscTrait), WRAPM(Units, isDead), WRAPM(Units, isAlive), @@ -826,6 +827,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, isCitizen), WRAPM(Units, getAge), WRAPM(Units, getEffectiveSkill), + WRAPM(Units, computeMovementSpeed), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), WRAPM(Units, getProfessionColor), diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 3fba5c218..65f0b58a0 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -221,6 +221,7 @@ DFHACK_EXPORT bool isCrazed(df::unit *unit); DFHACK_EXPORT bool isOpposedToLife(df::unit *unit); DFHACK_EXPORT bool hasExtravision(df::unit *unit); DFHACK_EXPORT bool isBloodsucker(df::unit *unit); +DFHACK_EXPORT bool isMischievous(df::unit *unit); DFHACK_EXPORT df::unit_misc_trait *getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create = false); @@ -233,6 +234,7 @@ DFHACK_EXPORT bool isDwarf(df::unit *unit); DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); +DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); struct NoblePosition { df::historical_entity *entity; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 1565dbbd3..28c34b029 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -637,7 +637,7 @@ int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr if (auto mod = unit->curse.attr_change) { - int mvalue = (value * mod->phys_att_perc[attr]) + mod->phys_att_add[attr]; + int mvalue = (value * mod->phys_att_perc[attr] / 100) + mod->phys_att_add[attr]; if (isHidingCurse(unit)) value = std::min(value, mvalue); @@ -645,7 +645,7 @@ int Units::getPhysicalAttrValue(df::unit *unit, df::physical_attribute_type attr value = mvalue; } - return value; + return std::max(0, value); } int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) @@ -658,7 +658,7 @@ int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) if (auto mod = unit->curse.attr_change) { - int mvalue = (value * mod->ment_att_perc[attr]) + mod->ment_att_add[attr]; + int mvalue = (value * mod->ment_att_perc[attr] / 100) + mod->ment_att_add[attr]; if (isHidingCurse(unit)) value = std::min(value, mvalue); @@ -666,7 +666,7 @@ int Units::getMentalAttrValue(df::unit *unit, df::mental_attribute_type attr) value = mvalue; } - return value; + return std::max(0, value); } static bool casteFlagSet(int race, int caste, df::caste_raw_flags flag) @@ -724,6 +724,16 @@ bool Units::isBloodsucker(df::unit *unit) return casteFlagSet(unit->race, unit->caste, caste_raw_flags::BLOODSUCKER); } +bool Units::isMischievous(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + if (unit->curse.rem_tags1.bits.MISCHIEVOUS) + return false; + if (unit->curse.add_tags1.bits.MISCHIEVOUS) + return true; + return casteFlagSet(unit->race, unit->caste, caste_raw_flags::MISCHIEVOUS); +} + df::unit_misc_trait *Units::getMiscTrait(df::unit *unit, df::misc_trait_type type, bool create) { CHECK_NULL_POINTER(unit); @@ -851,7 +861,7 @@ double DFHack::Units::getAge(df::unit *unit, bool true_age) return cur_time - birth_time; } -inline int adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2) +inline void adjust_skill_rating(int &rating, bool is_adventure, int value, int dwarf3_4, int dwarf1_2, int adv9_10, int adv3_4, int adv1_2) { if (is_adventure) { @@ -862,7 +872,7 @@ inline int adjust_skill_rating(int &rating, bool is_adventure, int value, int dw else { if (value >= dwarf1_2) rating >>= 1; - else if (value >= dwarf3_4) return rating*3/4; + else if (value >= dwarf3_4) rating = rating*3/4; } } @@ -958,6 +968,264 @@ int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) return rating; } +inline void adjust_speed_rating(int &rating, bool is_adventure, int value, int dwarf100, int dwarf200, int adv50, int adv75, int adv100, int adv200) +{ + if (is_adventure) + { + if (value >= adv200) rating += 200; + else if (value >= adv100) rating += 100; + else if (value >= adv75) rating += 75; + else if (value >= adv50) rating += 50; + } + else + { + if (value >= dwarf200) rating += 200; + else if (value >= dwarf100) rating += 100; + } +} + +static int calcInventoryWeight(df::unit *unit) +{ + int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR); + int armor_mul = 15 - std::min(15, armor_skill); + + int inv_weight = 0, inv_weight_fraction = 0; + + for (size_t i = 0; i < unit->inventory.size(); i++) + { + auto item = unit->inventory[i]->item; + if (!item->flags.bits.weight_computed) + continue; + + int wval = item->weight; + int wfval = item->weight_fraction; + auto mode = unit->inventory[i]->mode; + + if ((mode == df::unit_inventory_item::Worn || + mode == df::unit_inventory_item::WrappedAround) && + item->isArmor() && armor_skill > 1) + { + wval = wval * armor_mul / 16; + wfval = wfval * armor_mul / 16; + } + + inv_weight += wval; + inv_weight_fraction += wfval; + } + + return inv_weight*100 + inv_weight_fraction/10000; +} + +int Units::computeMovementSpeed(df::unit *unit) +{ + using namespace df::enums::physical_attribute_type; + + /* + * Pure reverse-engineered computation of unit _slowness_, + * i.e. number of ticks to move * 100. + */ + + // Base speed + + auto creature = df::creature_raw::find(unit->race); + if (!creature) + return 0; + + auto craw = vector_get(creature->caste, unit->caste); + if (!craw) + return 0; + + int speed = craw->misc.speed; + + if (unit->flags3.bits.ghostly) + return speed; + + // Curse multiplier + + if (unit->curse.speed_mul_percent != 100) + { + speed *= 100; + if (unit->curse.speed_mul_percent != 0) + speed /= unit->curse.speed_mul_percent; + } + + speed += unit->curse.speed_add; + + // Swimming + + auto cur_liquid = unit->status2.liquid_type.bits.liquid_type; + bool in_magma = (cur_liquid == tile_liquid::Magma); + + if (unit->flags2.bits.swimming) + { + speed = craw->misc.swim_speed; + if (in_magma) + speed *= 2; + + if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) + { + int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING); + + // Originally a switch: + if (skill > 1) + speed = speed * std::max(6, 21-skill) / 20; + } + } + else + { + int delta = 150*unit->status2.liquid_depth; + if (in_magma) + delta *= 2; + speed += delta; + } + + // General counters and flags + + if (unit->profession == profession::BABY) + speed += 3000; + + if (unit->flags3.bits.unk15) + speed /= 20; + + if (unit->counters2.exhaustion >= 2000) + { + speed += 200; + if (unit->counters2.exhaustion >= 4000) + { + speed += 200; + if (unit->counters2.exhaustion >= 6000) + speed += 200; + } + } + + if (unit->flags2.bits.gutted) speed += 2000; + + if (unit->counters.soldier_mood == df::unit::T_counters::None) + { + if (unit->counters.nausea > 0) speed += 1000; + if (unit->counters.winded > 0) speed += 1000; + if (unit->counters.stunned > 0) speed += 1000; + if (unit->counters.dizziness > 0) speed += 1000; + if (unit->counters2.fever > 0) speed += 1000; + } + + if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) + { + if (unit->counters.pain >= 100 && unit->mood == -1) + speed += 1000; + } + + // Hunger etc timers + + bool is_adventure = (gamemode && *gamemode == game_mode::ADVENTURE); + + if (!unit->flags3.bits.scuttle && Units::isBloodsucker(unit)) + { + using namespace df::enums::misc_trait_type; + + if (auto trait = Units::getMiscTrait(unit, TimeSinceSuckedBlood)) + { + adjust_speed_rating( + speed, is_adventure, trait->value, + 302400, 403200, // dwf 100; 200 + 1209600, 1209600, 1209600, 2419200 // adv 50; 75; 100; 200 + ); + } + } + + adjust_speed_rating( + speed, is_adventure, unit->counters2.thirst_timer, + 50000, 0x7fffffff, 172800, 172800, 172800, 345600 + ); + adjust_speed_rating( + speed, is_adventure, unit->counters2.hunger_timer, + 75000, 0x7fffffff, 1209600, 1209600, 1209600, 2592000 + ); + adjust_speed_rating( + speed, is_adventure, unit->counters2.sleepiness_timer, + 57600, 150000, 172800, 259200, 345600, 864000 + ); + + // Activity state + + if (unit->relations.draggee_id != -1) speed += 1000; + + if (unit->flags1.bits.on_ground) + speed += 2000; + else if (unit->flags3.bits.on_crutch) + { + int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK); + speed += 2000 - 100*std::min(20, skill); + } + + if (unit->flags1.bits.hidden_in_ambush && !Units::isMischievous(unit)) + { + int skill = Units::getEffectiveSkill(unit, job_skill::SNEAK); + speed += 2000 - 100*std::min(20, skill); + } + + if (unsigned(unit->counters2.paralysis-1) <= 98) + speed += unit->counters2.paralysis*10; + if (unsigned(unit->counters.webbed-1) <= 8) + speed += unit->counters.webbed*100; + + // Muscle weight vs vascular tissue (?) + + auto &attr_tissue = unit->body.physical_attr_tissues; + int muscle = attr_tissue[STRENGTH]; + int blood = attr_tissue[AGILITY]; + speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*muscle/blood))); + + // Attributes + + int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH); + int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY); + + int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr)); + speed = ((total_attr-200)*(speed/2) + (3800-total_attr)*(speed*3/2))/3600; + + // Stance + + if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) + { + // WTF + int as = unit->status2.able_stand; + int x = (as-1) - (as>>1); + int y = as - unit->status2.able_stand_impair; + if (unit->flags3.bits.on_crutch) y--; + y = y * 500 / x; + if (y > 0) speed += y; + } + + // Mood + + if (unit->mood == mood_type::Melancholy) speed += 8000; + + // Inventory encumberance + + int total_weight = calcInventoryWeight(unit); + int free_weight = std::max(1, muscle/10 + strength_attr*3); + + if (free_weight < total_weight) + { + int delta = (total_weight - free_weight)/10 + 1; + if (!is_adventure) + delta = std::min(5000, delta); + speed += delta; + } + + // skipped: unknown loop on inventory items that amounts to 0 change + + if (is_adventure) + { + auto player = vector_get(world->units.active, 0); + if (player && player->id == unit->relations.group_leader_id) + speed = std::min(speed, computeMovementSpeed(player)); + } + + return std::min(10000, std::max(0, speed)); +} + static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) { if (a.position->precedence < b.position->precedence) diff --git a/library/xml b/library/xml index db765a65b..d55f1cf43 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit db765a65b17099dbec115812b40a19b46ad59431 +Subproject commit d55f1cf43dd71d3abee724bfa88a0a401b4ccaa3 diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 648e4c3b2..4c6cebaac 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -39,6 +39,8 @@ #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 "MiscUtils.h" @@ -48,6 +50,7 @@ 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; @@ -501,180 +504,6 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * Unit tracking */ -int getSpeedRating(df::unit *unit) -{ - using namespace df::enums::physical_attribute_type; - - // Base speed - auto creature = df::creature_raw::find(unit->race); - if (!creature) - return 0; - - auto craw = vector_get(creature->caste, unit->caste); - if (!craw) - return 0; - - int speed = craw->misc.speed; - - if (unit->flags3.bits.ghostly) - return speed; - - // Curse multiplier - if (unit->curse.speed_mul_percent != 100) - { - speed *= 100; - if (unit->curse.speed_mul_percent != 0) - speed /= unit->curse.speed_mul_percent; - } - - speed += unit->curse.speed_add; - - // Swimming - if (unit->flags2.bits.swimming) - { - speed = craw->misc.swim_speed; - if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma) - speed *= 2; - if (craw->flags.is_set(caste_raw_flags::SWIMS_LEARNED)) - { - int skill = Units::getEffectiveSkill(unit, job_skill::SWIMMING); - if (skill > 1) - skill = skill * std::max(6, 21-skill) / 20; - } - } - else - { - if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Water) - speed += 150*unit->status2.liquid_depth; - else if (unit->status2.liquid_type.bits.liquid_type == tile_liquid::Magma) - speed += 300*unit->status2.liquid_depth; - } - - // General counters - if (unit->profession == profession::BABY) - speed += 3000; - - if (unit->counters2.exhaustion >= 2000) - { - speed += 200; - if (unit->counters2.exhaustion >= 4000) - { - speed += 200; - if (unit->counters2.exhaustion >= 6000) - speed += 200; - } - } - - if (unit->flags2.bits.gutted) speed += 2000; - - if (unit->counters.soldier_mood == df::unit::T_counters::None) - { - if (unit->counters.nausea > 0) speed += 1000; - if (unit->counters.winded > 0) speed += 1000; - if (unit->counters.stunned > 0) speed += 1000; - if (unit->counters.dizziness > 0) speed += 1000; - if (unit->counters2.fever > 0) speed += 1000; - } - - if (unit->counters.soldier_mood != df::unit::T_counters::MartialTrance) - { - if (unit->counters.pain >= 100 && unit->mood < 0) - speed += 1000; - } - - // TODO: bloodsucker - - if (unit->counters2.thirst_timer >= 50000) speed += 100; - if (unit->counters2.hunger_timer >= 75000) speed += 100; - if (unit->counters2.sleepiness_timer >= 150000) speed += 200; - else if (unit->counters2.sleepiness_timer >= 57600) speed += 100; - - if (unit->relations.draggee_id != -1) speed += 1000; - - if (unit->flags1.bits.on_ground) - speed += 2000; - else if (unit->flags3.bits.on_crutch) - { - int skill = Units::getEffectiveSkill(unit, job_skill::CRUTCH_WALK); - speed += 2000 - 100*std::min(20, skill); - } - - // TODO: hidden_in_ambush - - if (unsigned(unit->counters2.paralysis-1) <= 98) - speed += unit->counters2.paralysis*10; - if (unsigned(unit->counters.webbed-1) <= 8) - speed += unit->counters.webbed*100; - - // Muscle weight vs agility - auto &attr_unk3 = unit->body.physical_attr_unk3; - int strength = attr_unk3[STRENGTH]; - int agility = attr_unk3[AGILITY]; - speed = std::max(speed*3/4, std::min(speed*3/2, int(int64_t(speed)*strength/agility))); - - // Attributes - int strength_attr = Units::getPhysicalAttrValue(unit, STRENGTH); - int agility_attr = Units::getPhysicalAttrValue(unit, AGILITY); - - int total_attr = std::max(200, std::min(3800, strength_attr + agility_attr)); - speed = ((total_attr-200)*(speed*3/2) + (3800-total_attr)*(speed/2))/4800; // ?? - - if (!unit->flags1.bits.on_ground && unit->status2.able_stand > 2) - { - // WTF - int as = unit->status2.able_stand; - int x = (as-1) - (as>>1); - int y = as - unit->status2.able_stand_impair; - if (unit->flags3.bits.on_crutch) y--; - y = y * 500 / x; - if (y > 0) speed += y; - } - - if (unit->mood == mood_type::Melancholy) speed += 8000; - - // Inventory encumberance - int armor_skill = Units::getEffectiveSkill(unit, job_skill::ARMOR); - armor_skill = std::min(15, armor_skill); - - int inv_weight = 0, inv_weight_fraction = 0; - - for (size_t i = 0; i < unit->inventory.size(); i++) - { - auto item = unit->inventory[i]->item; - if (!item->flags.bits.weight_computed) - continue; - - int wval = item->weight; - int wfval = item->weight_fraction; - - auto mode = unit->inventory[i]->mode; - if ((mode == df::unit_inventory_item::Worn || - mode == df::unit_inventory_item::WrappedAround) && - item->isArmor() && armor_skill > 1) - { - wval = wval * (15 - armor_skill) / 16; - wfval = wfval * (15 - armor_skill) / 16; - } - - inv_weight += wval; - inv_weight_fraction += wfval; - } - - int total_weight = inv_weight*100 + inv_weight_fraction/10000; - int free_weight = std::max(1, attr_unk3[STRENGTH]/10 + strength_attr*3); - - if (free_weight < total_weight) - { - int delta = (total_weight - free_weight)/10; - delta = std::min(5000, delta); // dwarfmode only - speed += delta; - } - - // skipped: unknown loop on inventory items that amounts to 0 change - - return std::min(10000, std::max(0, speed)); -} - /* * Projectile hook */ From 8ab615f6d0dce167a1952c4684922a7e48263c23 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Sun, 9 Sep 2012 20:54:12 +0400 Subject: [PATCH 15/48] Implement unit path prediction in siege engine. --- plugins/devel/siege-engine.cpp | 317 ++++++++++++++++++++++++++++++++- 1 file changed, 316 insertions(+), 1 deletion(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 4c6cebaac..c36e9fb32 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -124,6 +124,11 @@ 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))); +} + /* * Configuration management */ @@ -134,6 +139,7 @@ struct EngineInfo { int id; coord_range target; df::coord center; + int proj_speed, hit_delay; bool hasTarget() { return is_range_valid(target); } bool onTarget(df::coord pos) { return is_in_range(target, pos); } @@ -155,8 +161,11 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) return NULL; auto *obj = &engines[bld]; + obj->id = bld->id; obj->center = df::coord(bld->centerx, bld->centery, bld->z); + obj->proj_speed = 2; + obj->hit_delay = 3; coord_engines[obj->center] = bld; return obj; @@ -285,7 +294,7 @@ struct ProjectilePath { void calc_line() { speed = target - origin; - divisor = std::max(abs(speed.x), std::max(abs(speed.y), abs(speed.z))); + 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); } @@ -504,6 +513,292 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: * 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) + { + 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; + + 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)) + 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 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 (unit->flags1.bits.dead || + unit->flags3.bits.ghostly || + unit->flags1.bits.caged || + unit->flags1.bits.chained || + unit->flags1.bits.rider || + unit->flags1.bits.hidden_in_ambush) + continue; + + UnitPath::get(unit)->findHits(engine, hits, bias); + } +} + +static int proposeUnitHits(lua_State *L) +{ + auto bld = Lua::CheckDFObject(L, 1); + float bias = luaL_optnumber(L, 2, 0); + + auto engine = find_engine(bld); + if (!engine) + luaL_error(L, "no such engine"); + 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::PushDFObject(L, hit.path->unit); lua_setfield(L, -2, "unit"); + Lua::Push(L, hit.pos); lua_setfield(L, -2, "pos"); + lua_pushnumber(L, hit.dist); lua_setfield(L, -2, "dist"); + lua_pushnumber(L, hit.time); lua_setfield(L, -2, "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; +} + +#if 0 +struct UnitContext { + AimContext &ctx; + + struct UnitInfo { + df::unit *unit; + + UnitPath path; + float score; + + UnitInfo(df::unit *unit) : unit(unit), path(unit) {} + }; + + std::map units; + + UnitContext(AimContext &ctx) : ctx(ctx) {} + + ~UnitContext() + { + for (auto it = units.begin(); it != units.end(); ++it) + delete it->second; + } + + float unit_score(df::unit *unit) + { + float score = 1.0f; + + if (unit->flags1.bits.tame && unit->civ_id == ui->civ_id) + score = -1.0f; + if (unit->flags1.bits.diplomat || unit->flags1.bits.merchant) + score = -2.0f; + else if (Units::isCitizen(unit)) + score = -10.0f; + else + { + if (unit->flags1.bits.marauder) + score += 0.5f; + if (unit->flags1.bits.active_invader) + score += 1.0f; + if (unit->flags1.bits.invader_origin) + score += 1.0f; + if (unit->flags1.bits.invades) + score += 1.0f; + if (unit->flags1.bits.hidden_ambusher) + score += 1.0f; + } + + if (unit->flags1.bits.ridden) + { + for (size_t i = 0; i < unit->refs.size(); i++) + { + if (!unit->refs[i]->getType() == general_ref_type::UNIT_RIDER) + continue; + if (auto rider = unit->refs[i]->getUnit()) + score += unit_score(rider); + } + } + } + + void select_units() + { + auto &active = world->units.active; + + for (size_t i = 0; i < active.size(); i++) + { + auto unit = active[i]; + if (unit->flags1.bits.dead || + unit->flags3.bits.ghostly || + unit->flags1.bits.caged || + unit->flags1.bits.chained || + unit->flags1.bits.rider || + unit->flags1.bits.hidden_in_ambush) + continue; + + auto info = units[unit] = new UnitInfo(unit); + + info->findHits(ctx, ctx.proj_hit_delay); + info->score = unit_score(unit); + } + } +}; +#endif + /* * Projectile hook */ @@ -617,6 +912,9 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getTargetArea), + DFHACK_LUA_COMMAND(traceUnitPath), + DFHACK_LUA_COMMAND(unitPosAtTime), + DFHACK_LUA_COMMAND(proposeUnitHits), DFHACK_LUA_END }; @@ -648,6 +946,17 @@ static bool enable_plugin() return true; } +static void clear_caches() +{ + 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) { @@ -688,3 +997,9 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) enable_hooks(false); return CR_OK; } + +DFhackCExport command_result plugin_onupdate ( color_ostream &out ) +{ + clear_caches(); + return CR_OK; +} From 274d6038adce5797b58cee78a330eb5d639bf59e Mon Sep 17 00:00:00 2001 From: Timothy Collett Date: Mon, 10 Sep 2012 09:19:21 -0400 Subject: [PATCH 16/48] Merge further changes (???) --- depends/protobuf/CMakeLists.txt | 6 +++--- library/RemoteClient.cpp | 2 +- library/RemoteServer.cpp | 2 +- library/RemoteTools.cpp | 2 +- library/include/DataDefs.h | 2 +- library/include/RemoteTools.h | 2 +- library/modules/Job.cpp | 2 +- library/modules/Units.cpp | 2 +- library/modules/kitchen.cpp | 2 +- plugins/autolabor.cpp | 6 +++--- plugins/cleaners.cpp | 4 ++-- plugins/cleanowned.cpp | 14 +++++++------- plugins/jobutils.cpp | 2 +- plugins/prospector.cpp | 2 +- plugins/tiletypes.cpp | 2 +- plugins/workflow.cpp | 2 +- plugins/zone.cpp | 4 ++-- 17 files changed, 29 insertions(+), 29 deletions(-) diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index 570c77b18..8cd5febcd 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -7,10 +7,10 @@ IF(CMAKE_COMPILER_IS_GNUCC) STRING(REGEX MATCHALL "[0-9]+" GCC_VERSION_COMPONENTS ${GCC_VERSION}) LIST(GET GCC_VERSION_COMPONENTS 0 GCC_MAJOR) LIST(GET GCC_VERSION_COMPONENTS 1 GCC_MINOR) - IF(GCC_MAJOR LESS 4 OR (GCC_MAJOR EQUAL 4 AND GCC_MINOR LESS 2)) + #IF(GCC_MAJOR LESS 4 OR (GCC_MAJOR EQUAL 4 AND GCC_MINOR LESS 2)) #GCC is too old - SET(STL_HASH_OLD_GCC 1) - ENDIF() + # SET(STL_HASH_OLD_GCC 1) + #ENDIF() #SET(CMAKE_CXX_FLAGS "-std=c++0x") SET(HAVE_HASH_MAP 0) diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index 4d30988c6..09861ad5f 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -394,7 +394,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, //out.print("Received %d:%d\n", header.id, header.size); - if (header.id == RPC_REPLY_FAIL) + if ((DFHack::DFHackReplyCode)header.id == RPC_REPLY_FAIL) return header.size == CR_OK ? CR_FAILURE : command_result(header.size); if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 53428f2bd..06a9f859c 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -250,7 +250,7 @@ void ServerConnection::threadFn() break; } - if (header.id == RPC_REQUEST_QUIT) + if ((DFHack::DFHackReplyCode)header.id == RPC_REQUEST_QUIT) break; if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 95c495e93..b371d60fa 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -287,7 +287,7 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, if (mask && mask->profession()) { - if (unit->profession >= 0) + if (unit->profession >= (df::profession)0) info->set_profession(unit->profession); if (!unit->custom_profession.empty()) info->set_custom_profession(unit->custom_profession); diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 1d485156f..7f4d94c80 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -506,7 +506,7 @@ namespace DFHack { template inline const char *enum_item_raw_key(T val) { typedef df::enum_traits traits; - return traits::is_valid(val) ? traits::key_table[val - traits::first_item_value] : NULL; + return traits::is_valid(val) ? traits::key_table[(short)val - traits::first_item_value] : NULL; } /** diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index 65884badc..e87e8026b 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -88,7 +88,7 @@ namespace DFHack { typedef df::enum_traits traits; int base = traits::first_item; - int size = traits::last_item - base + 1; + int size = (int)traits::last_item - base + 1; describeEnum(pf, base, size, traits::key_table); } diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 1207c97b3..a5e73bf19 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -181,7 +181,7 @@ void DFHack::Job::printItemDetails(color_ostream &out, df::job_item *item, int i out << " reaction class: " << item->reaction_class << endl; if (!item->has_material_reaction_product.empty()) out << " reaction product: " << item->has_material_reaction_product << endl; - if (item->has_tool_use >= 0) + if (item->has_tool_use >= (df::tool_uses)0) out << " tool use: " << ENUM_KEY_STR(tool_uses, item->has_tool_use) << endl; } diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index ee383cc07..308700fb7 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -838,7 +838,7 @@ std::string DFHack::Units::getCasteProfessionName(int race, int casteid, df::pro { std::string prof, race_prefix; - if (pid < 0 || !is_valid_enum_item(pid)) + if (pid < (df::profession)0 || !is_valid_enum_item(pid)) return ""; bool use_race_prefix = (race >= 0 && race != df::global::ui->race_id); diff --git a/library/modules/kitchen.cpp b/library/modules/kitchen.cpp index 4300d63df..aa235780d 100644 --- a/library/modules/kitchen.cpp +++ b/library/modules/kitchen.cpp @@ -114,7 +114,7 @@ void Kitchen::fillWatchMap(std::map& watchMap) watchMap.clear(); for(std::size_t i = 0; i < size(); ++i) { - if(ui->kitchen.item_subtypes[i] == limitType && ui->kitchen.item_subtypes[i] == limitSubtype && ui->kitchen.exc_types[i] == limitExclusion) + if(ui->kitchen.item_subtypes[i] == (short)limitType && ui->kitchen.item_subtypes[i] == (short)limitSubtype && ui->kitchen.exc_types[i] == limitExclusion) { watchMap[ui->kitchen.mat_indices[i]] = (unsigned int) ui->kitchen.mat_types[i]; } diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index de1a1aef6..5536335c4 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -732,9 +732,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) if (df::enums::building_type::Workshop == type) { auto subType = build->getSubtype(); - if (df::enums::workshop_type::Butchers == subType) + if ((short)df::enums::workshop_type::Butchers == subType) has_butchers = true; - if (df::enums::workshop_type::Fishery == subType) + if ((short)df::enums::workshop_type::Fishery == subType) has_fishery = true; } else if (df::enums::building_type::TradeDepot == type) @@ -863,7 +863,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { // 7 / 0x7 = Newly arrived migrant, will not work yet // 17 / 0x11 = On break - if ((*p)->id == 0x07 || (*p)->id == 0x11) + if ((*p)->id == (df::misc_trait_type)0x07 || (*p)->id == (df::misc_trait_type)0x11) is_on_break = true; } diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index 30befab2f..fb436a6db 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -50,12 +50,12 @@ command_result cleanmap (color_ostream &out, bool snow, bool mud) // filter snow if(!snow && spatter->mat_type == builtin_mats::WATER - && spatter->mat_state == matter_state::Powder) + && spatter->mat_state == (short)matter_state::Powder) continue; // filter mud if(!mud && spatter->mat_type == builtin_mats::MUD - && spatter->mat_state == matter_state::Solid) + && spatter->mat_state == (short)matter_state::Solid) continue; delete evt; diff --git a/plugins/cleanowned.cpp b/plugins/cleanowned.cpp index c1521b8de..a9d461d2f 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -117,13 +117,13 @@ command_result df_cleanowned (color_ostream &out, vector & parameters) else if (item->flags.bits.on_ground) { int32_t type = item->getType(); - if(type == item_type::MEAT || - type == item_type::FISH || - type == item_type::VERMIN || - type == item_type::PET || - type == item_type::PLANT || - type == item_type::CHEESE || - type == item_type::FOOD + if((df::enums::item_type::item_type)type == item_type::MEAT || + (df::enums::item_type::item_type)type == item_type::FISH || + (df::enums::item_type::item_type)type == item_type::VERMIN || + (df::enums::item_type::item_type)type == item_type::PET || + (df::enums::item_type::item_type)type == item_type::PLANT || + (df::enums::item_type::item_type)type == item_type::CHEESE || + (df::enums::item_type::item_type)type == item_type::FOOD ) { confiscate = true; diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp index 24ad4170e..dbfe26b90 100644 --- a/plugins/jobutils.cpp +++ b/plugins/jobutils.cpp @@ -372,7 +372,7 @@ static command_result job_cmd(color_ostream &out, vector & parameters) out << "Job item updated." << endl; - if (item->item_type < 0 && minfo.isValid()) + if (item->item_type < (df::item_type)0 && minfo.isValid()) out.printerr("WARNING: Due to a probable bug, creature & plant material subtype\n" " is ignored unless the item type is also specified.\n"); diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index e2f1e9534..91abd544e 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -293,7 +293,7 @@ static command_result embark_prospector(color_ostream &out, df::viewscreen_choos int level_cnt = layer->top_height - layer->bottom_height + 1; int layer_size = 48*48*cnt*level_cnt; - int sums[ENUM_LAST_ITEM(inclusion_type)+1] = { 0 }; + int sums[(int)ENUM_LAST_ITEM(inclusion_type)+1] = { 0 }; for (unsigned j = 0; j < layer->vein_mat.size(); j++) if (is_valid_enum_item(layer->vein_type[j])) diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 190bda7cd..6af94f2ee 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -767,7 +767,7 @@ command_result executePaintJob(color_ostream &out) } // Remove liquid from walls, etc - if (type != -1 && !DFHack::FlowPassable(type)) + if (type != (df::tiletype)-1 && !DFHack::FlowPassable(type)) { des.bits.flow_size = 0; //des.bits.liquid_type = DFHack::liquid_water; diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index dbf546070..9e0e45292 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -808,7 +808,7 @@ static void compute_custom_job(ProtectedJob *pj, df::job *job) using namespace df::enums::reaction_product_item_flags; VIRTUAL_CAST_VAR(prod, df::reaction_product_itemst, r->products[i]); - if (!prod || (prod->item_type < 0 && !prod->flags.is_set(CRAFTS))) + if (!prod || (prod->item_type < (df::item_type)0 && !prod->flags.is_set(CRAFTS))) continue; MaterialInfo mat(prod); diff --git a/plugins/zone.cpp b/plugins/zone.cpp index ce610128b..b5e45f5c5 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -792,7 +792,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) bool isActivityZone(df::building * building) { if( building->getType() == building_type::Civzone - && building->getSubtype() == civzone_type::ActivityZone) + && building->getSubtype() == (short)civzone_type::ActivityZone) return true; else return false; @@ -1603,7 +1603,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) if(building->getType()!= building_type::Civzone) return; - if(building->getSubtype() != civzone_type::ActivityZone) + if(building->getSubtype() != (short)civzone_type::ActivityZone) return; string name; From 3a075f4bc716550e0a621a2db40cfa578db1d0fa Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 11 Sep 2012 19:17:24 +0400 Subject: [PATCH 17/48] Trivial siege engine aiming at units, with logic in lua. --- library/include/LuaTools.h | 5 + plugins/devel/siege-engine.cpp | 591 +++++++++++++++++++++------------ plugins/lua/siege-engine.lua | 31 ++ scripts/gui/siege-engine.lua | 11 +- 4 files changed, 419 insertions(+), 219 deletions(-) diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index 6b1afb88b..3330e23e7 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -287,6 +287,11 @@ namespace DFHack {namespace Lua { PushDFObject(state, ptr); } + template inline void SetField(lua_State *L, T val, int idx, const char *name) { + if (idx < 0) idx = lua_absindex(L, idx); + Push(L, val); lua_setfield(L, idx, name); + } + template void PushVector(lua_State *state, const T &pvec, bool addn = false) { diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index c36e9fb32..ce835c6d4 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -137,13 +137,24 @@ static bool enable_plugin(); struct EngineInfo { int id; - coord_range target; + 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; 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; @@ -151,29 +162,64 @@ static std::map coord_engines; static EngineInfo *find_engine(df::building *bld, bool create = false) { - if (!bld) + auto ebld = strict_virtual_cast(bld); + if (!ebld) return NULL; auto it = engines.find(bld); if (it != engines.end()) + { + it->second.bld = ebld; return &it->second; + } + if (!create) return NULL; auto *obj = &engines[bld]; 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 = 3; + obj->fire_range = get_engine_range(ebld); coord_engines[obj->center] = bld; return obj; } +static EngineInfo *find_engine(lua_State *L, int idx, bool create = false) +{ + auto bld = Lua::CheckDFObject(L, idx); + + auto engine = find_engine(bld); + if (!engine) + luaL_error(L, "no such engine"); + + return engine; +} + static EngineInfo *find_engine(df::coord pos) { - return find_engine(coord_engines[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; } static void clear_engines() @@ -263,37 +309,61 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, return true; } +static int getShotSkill(df::building_siegeenginest *bld) +{ + CHECK_NULL_POINTER(bld); + + auto engine = find_engine(bld); + if (!engine) + return 0; + + 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 */ 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), target(goal), fudge_factor(1) + origin(origin), goal(goal), fudge_factor(1) { fudge_delta = df::coord(0,0,0); calc_line(); } - void fudge(int factor, df::coord delta) + 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_factor = factor; - fudge_delta = delta; - auto diff = goal - origin; - diff.x *= fudge_factor; - diff.y *= fudge_factor; - diff.z *= fudge_factor; - target = origin + diff + fudge_delta; + fudge_delta = df::coord(0,0,int(factor * zdelta)); calc_line(); } void calc_line() { - speed = target - origin; + 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); @@ -311,42 +381,139 @@ struct ProjectilePath { } }; +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 + MapEdge, + Tree } hit_type; - int collision_step; - int goal_step, goal_z_step; - std::vector coords; + int collision_step, collision_z_step; + int goal_step, goal_z_step, goal_distance; + + bool hits() const { return collision_step > goal_step; } - bool hits() { return collision_step > goal_step; } + PathMetrics(const ProjectilePath &path) + { + compute(path); + } - PathMetrics(const ProjectilePath &path, bool list_coords = false) + void compute(const ProjectilePath &path) { - coords.clear(); 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; - if (list_coords) - coords.push_back(prev_pos); for (;;) { df::coord cur_pos = path[++step]; if (cur_pos == prev_pos) break; - if (list_coords) - coords.push_back(cur_pos); if (cur_pos.z == path.goal.z) { @@ -363,8 +530,21 @@ struct PathMetrics { if (!isPassableTile(cur_pos)) { - hit_type = Impassable; - break; + 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) @@ -377,6 +557,8 @@ struct PathMetrics { hit_type = (cur_pos.z > prev_pos.z ? Ceiling : Floor); break; } + + collision_z_step = step; } prev_pos = cur_pos; @@ -386,107 +568,112 @@ struct PathMetrics { } }; -struct AimContext { - df::building_siegeenginest *bld; - df::coord origin; - coord_range building_rect; - EngineInfo *engine; - std::pair fire_range; +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" +}; - AimContext(df::building_siegeenginest *bld, EngineInfo *engine) - : bld(bld), engine(engine) +static TargetTileStatus calcTileStatus(EngineInfo *engine, const PathMetrics &raytrace) +{ + if (raytrace.hits()) { - origin = df::coord(bld->centerx, bld->centery, bld->z); - building_rect = coord_range( - df::coord(bld->x1, bld->y1, bld->z), - df::coord(bld->x2, bld->y2, bld->z) - ); - fire_range = get_engine_range(bld); + if (engine->isInRange(raytrace.goal_step)) + return TARGET_OK; + else + return TARGET_RANGE; } + else + return TARGET_BLOCKED; +} - bool isInRange(const PathMetrics &raytrace) - { - return raytrace.goal_step >= fire_range.first && - raytrace.goal_step <= fire_range.second; - } +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; +} - bool adjustToPassable(df::coord *pos) - { - if (isPassableTile(*pos)) - return true; +static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target, float zdelta) +{ + ProjectilePath path(engine->center, target, zdelta); + PathMetrics raytrace(path); + return calcTileStatus(engine, raytrace); +} - for (df::coord fudge = *pos; - fudge.z < engine->target.second.z; fudge.z++) - { - if (!isPassableTile(fudge)) - continue; - *pos = fudge; - return true; - } +static TargetTileStatus calcTileStatus(EngineInfo *engine, df::coord target) +{ + auto status = calcTileStatus(engine, target, 0.0f); - for (df::coord fudge = *pos; - fudge.z > engine->target.first.z; fudge.z--) - { - if (!isPassableTile(fudge)) - continue; - *pos = fudge; - return true; - } + if (status == TARGET_BLOCKED) + { + if (calcTileStatus(engine, target, 0.5f) < TARGET_BLOCKED) + return TARGET_SEMIBLOCKED; - return false; + 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) { - AimContext context(bld, NULL); - - ProjectilePath path(context.origin, tile_pos); - PathMetrics raytrace(path); + auto engine = find_engine(bld, true); + if (!engine) + return "invalid"; - if (raytrace.hits()) - { - if (context.isInRange(raytrace)) - return "ok"; - else - return "out_of_range"; - } - else - return "blocked"; + 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) { - CHECK_NULL_POINTER(bld); - - AimContext context(bld, find_engine(bld)); + 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(context.building_rect, tile_pos)) + 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; - ProjectilePath path(context.origin, tile_pos); - PathMetrics raytrace(path); - int color; - if (raytrace.hits()) + + switch (calcTileStatus(engine, tile_pos)) { - if (context.isInRange(raytrace)) + case TARGET_OK: color = COLOR_GREEN; - else + break; + case TARGET_RANGE: color = COLOR_CYAN; + break; + case TARGET_BLOCKED: + color = COLOR_RED; + break; + case TARGET_SEMIBLOCKED: + color = COLOR_BROWN; + break; } - else - color = COLOR_RED; if (cur_tile.fg && cur_tile.ch != ' ') { @@ -499,7 +686,7 @@ static void paintAimScreen(df::building_siegeenginest *bld, df::coord view, df:: cur_tile.bg = color; } - cur_tile.bold = (context.engine && context.engine->onTarget(tile_pos)); + cur_tile.bold = engine->onTarget(tile_pos); if (cur_tile.tile) cur_tile.tile_mode = Pen::CharColor; @@ -537,6 +724,17 @@ struct UnitPath { 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; @@ -598,7 +796,7 @@ struct UnitPath { if (info.lmargin > 0 && info.rmargin > 0) { - if (engine->onTarget(info.pos)) + if (engine->onTarget(info.pos) && engine->isInRange(info.dist)) hit_points->push_back(info); } } @@ -664,6 +862,19 @@ static int unitPosAtTime(lua_State *L) 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; @@ -672,12 +883,7 @@ static void proposeUnitHits(EngineInfo *engine, std::vector *hits { auto unit = active[i]; - if (unit->flags1.bits.dead || - unit->flags3.bits.ghostly || - unit->flags1.bits.caged || - unit->flags1.bits.chained || - unit->flags1.bits.rider || - unit->flags1.bits.hidden_in_ambush) + if (!canTargetUnit(unit)) continue; UnitPath::get(unit)->findHits(engine, hits, bias); @@ -686,12 +892,9 @@ static void proposeUnitHits(EngineInfo *engine, std::vector *hits static int proposeUnitHits(lua_State *L) { - auto bld = Lua::CheckDFObject(L, 1); + auto engine = find_engine(L, 1); float bias = luaL_optnumber(L, 2, 0); - auto engine = find_engine(bld); - if (!engine) - luaL_error(L, "no such engine"); if (!engine->hasTarget()) luaL_error(L, "target not set"); @@ -704,10 +907,10 @@ static int proposeUnitHits(lua_State *L) { auto &hit = hits[i]; lua_createtable(L, 0, 6); - Lua::PushDFObject(L, hit.path->unit); lua_setfield(L, -2, "unit"); - Lua::Push(L, hit.pos); lua_setfield(L, -2, "pos"); - lua_pushnumber(L, hit.dist); lua_setfield(L, -2, "dist"); - lua_pushnumber(L, hit.time); lua_setfield(L, -2, "time"); + 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); @@ -716,89 +919,6 @@ static int proposeUnitHits(lua_State *L) return 1; } -#if 0 -struct UnitContext { - AimContext &ctx; - - struct UnitInfo { - df::unit *unit; - - UnitPath path; - float score; - - UnitInfo(df::unit *unit) : unit(unit), path(unit) {} - }; - - std::map units; - - UnitContext(AimContext &ctx) : ctx(ctx) {} - - ~UnitContext() - { - for (auto it = units.begin(); it != units.end(); ++it) - delete it->second; - } - - float unit_score(df::unit *unit) - { - float score = 1.0f; - - if (unit->flags1.bits.tame && unit->civ_id == ui->civ_id) - score = -1.0f; - if (unit->flags1.bits.diplomat || unit->flags1.bits.merchant) - score = -2.0f; - else if (Units::isCitizen(unit)) - score = -10.0f; - else - { - if (unit->flags1.bits.marauder) - score += 0.5f; - if (unit->flags1.bits.active_invader) - score += 1.0f; - if (unit->flags1.bits.invader_origin) - score += 1.0f; - if (unit->flags1.bits.invades) - score += 1.0f; - if (unit->flags1.bits.hidden_ambusher) - score += 1.0f; - } - - if (unit->flags1.bits.ridden) - { - for (size_t i = 0; i < unit->refs.size(); i++) - { - if (!unit->refs[i]->getType() == general_ref_type::UNIT_RIDER) - continue; - if (auto rider = unit->refs[i]->getUnit()) - score += unit_score(rider); - } - } - } - - void select_units() - { - auto &active = world->units.active; - - for (size_t i = 0; i < active.size(); i++) - { - auto unit = active[i]; - if (unit->flags1.bits.dead || - unit->flags3.bits.ghostly || - unit->flags1.bits.caged || - unit->flags1.bits.chained || - unit->flags1.bits.rider || - unit->flags1.bits.hidden_in_ambush) - continue; - - auto info = units[unit] = new UnitInfo(unit); - - info->findHits(ctx, ctx.proj_hit_delay); - info->score = unit_score(unit); - } - } -}; -#endif - /* * Projectile hook */ @@ -806,7 +926,7 @@ struct UnitContext { struct projectile_hook : df::proj_itemst { typedef df::proj_itemst interpose_base; - void aimAtPoint(AimContext &context, ProjectilePath &path, bool bad_shot = false) + void aimAtPoint(EngineInfo *engine, const ProjectilePath &path) { target_pos = path.target; @@ -816,28 +936,32 @@ struct projectile_hook : df::proj_itemst { for (int i = 0; i < raytrace.collision_step; i++) Maps::ensureTileBlock(path[i]); - if (flags.bits.piercing) - { - if (bad_shot) - fall_threshold = std::min(raytrace.goal_z_step, raytrace.collision_step); - } - else + // Find valid hit point for catapult stones + if (flags.bits.high_flying) { - if (bad_shot) - fall_threshold = context.fire_range.second; - else + 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, context.fire_range.first); - fall_threshold = std::min(fall_threshold, context.fire_range.second); + fall_threshold = std::max(fall_threshold, engine->fire_range.first); + fall_threshold = std::min(fall_threshold, engine->fire_range.second); } - void aimAtArea(AimContext &context) + void aimAtArea(EngineInfo *engine) { df::coord target, last_passable; - df::coord tbase = context.engine->target.first; - df::coord tsize = context.engine->getTargetSize(); + df::coord tbase = engine->target.first; + df::coord tsize = engine->getTargetSize(); bool success = false; for (int i = 0; i < 50; i++) @@ -846,17 +970,17 @@ struct projectile_hook : df::proj_itemst { random_int(tsize.x), random_int(tsize.y), random_int(tsize.z) ); - if (context.adjustToPassable(&target)) + if (adjustToTarget(engine, &target)) last_passable = target; else continue; - ProjectilePath path(context.origin, target); + ProjectilePath path(engine->center, target, engine->is_catapult ? 0.5f : 0.0f); PathMetrics raytrace(path); - if (raytrace.hits() && context.isInRange(raytrace)) + if (raytrace.hits() && engine->isInRange(raytrace.goal_step)) { - aimAtPoint(context, path); + aimAtPoint(engine, path); return; } } @@ -864,8 +988,30 @@ struct projectile_hook : df::proj_itemst { if (!last_passable.isValid()) last_passable = target; - ProjectilePath path(context.origin, last_passable); - aimAtPoint(context, path, true); + aimAtPoint(engine, 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); + + 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, engine->target.first); + Lua::Push(L, engine->target.second); + + lua_call(L, 3, 1); + + if (lua_isnil(L, -1)) + proj->aimAtArea(engine); + else + proj->aimAtPoint(engine, decode_path(L, -1, engine->center)); + + return 0; } void doCheckMovement() @@ -877,14 +1023,16 @@ struct projectile_hook : df::proj_itemst { if (!engine || !engine->hasTarget()) return; - auto bld0 = df::building::find(engine->id); - auto bld = strict_virtual_cast(bld0); - if (!bld) - return; + auto L = Lua::Core::State; + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); - AimContext context(bld, engine); + lua_pushcfunction(L, safeAimProjectile); + lua_pushlightuserdata(L, this); + lua_pushlightuserdata(L, engine); - aimAtArea(context); + if (!Lua::Core::SafeCall(out, 2, 0)) + aimAtArea(engine); } DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ()) @@ -907,11 +1055,18 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(setTargetArea), 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(projPosAtStep), + DFHACK_LUA_COMMAND(projPathMetrics), + DFHACK_LUA_COMMAND(adjustToTarget), DFHACK_LUA_COMMAND(traceUnitPath), DFHACK_LUA_COMMAND(unitPosAtTime), DFHACK_LUA_COMMAND(proposeUnitHits), diff --git a/plugins/lua/siege-engine.lua b/plugins/lua/siege-engine.lua index 01b5d1447..d078ce1d7 100644 --- a/plugins/lua/siege-engine.lua +++ b/plugins/lua/siege-engine.lua @@ -10,4 +10,35 @@ local _ENV = mkmodule('plugins.siege-engine') --]] +Z_STEP_COUNT = 15 +Z_STEP = 1/31 + +function findShotHeight(engine, target) + local path = { target = target, delta = 0.0 } + + if projPathMetrics(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 + end + + path.delta = -i*Z_STEP + if projPathMetrics(engine, path).goal_step then + return path + end + end +end + +function doAimProjectile(engine, target_min, target_max) + local targets = proposeUnitHits(engine) + if #targets > 0 then + local rnd = math.random(#targets) + return findShotHeight(engine, targets[rnd].pos) + end +end + return _ENV \ No newline at end of file diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index bba6bce89..7ad828c90 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -45,7 +45,9 @@ function SiegeEngine:onShow() end function SiegeEngine:onDestroy() - self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) + if self.building then + self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) + end end function SiegeEngine:showCursor(enable) @@ -213,6 +215,7 @@ local status_table = { ok = { pen = COLOR_GREEN, msg = "Target accessible" }, out_of_range = { pen = COLOR_CYAN, msg = "Target out of range" }, blocked = { pen = COLOR_RED, msg = "Target obstructed" }, + semi_blocked = { pen = COLOR_BROWN, msg = "Partially obstructed" }, } function SiegeEngine:onRenderBody_aim(dc) @@ -291,6 +294,12 @@ function SiegeEngine:onInput(keys) self:centerViewOn(self.center) elseif keys.LEAVESCREEN then self:dismiss() + elseif keys.LEAVESCREEN_ALL then + self:dismiss() + self.building = nil + guidm.clearCursorPos() + df.global.ui.main.mode = df.ui_sidebar_mode.Default + df.global.world.selected_building = nil end end From b0938d7e0d80720ef1a6fd264418ba20c375d9ba Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 11 Sep 2012 22:46:17 +0400 Subject: [PATCH 18/48] Allow specifying arbitrary items to use in catapults. --- plugins/devel/siege-engine.cpp | 151 ++++++++++++++++++++++++++++++++- scripts/gui/siege-engine.lua | 54 ++++++++++-- 2 files changed, 195 insertions(+), 10 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index ce835c6d4..2720c62f8 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -41,6 +42,10 @@ #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.h" +#include "df/items_other_id.h" #include "MiscUtils.h" @@ -148,6 +153,11 @@ struct EngineInfo { coord_range target; + df::job_item_vector_id ammo_vector_id; + df::item_type ammo_item_type; + + int operator_id, operator_frame; + 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; } @@ -190,6 +200,11 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) obj->hit_delay = 3; 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; } @@ -198,7 +213,7 @@ static EngineInfo *find_engine(lua_State *L, int idx, bool create = false) { auto bld = Lua::CheckDFObject(L, idx); - auto engine = find_engine(bld); + auto engine = find_engine(bld, create); if (!engine) luaL_error(L, "no such engine"); @@ -243,6 +258,15 @@ static void load_engines() 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); + } } static int getTargetArea(lua_State *L) @@ -309,6 +333,51 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, return true; } +static int getAmmoItem(lua_State *L) +{ + auto engine = find_engine(L, 1, true); + Lua::Push(L, engine->ammo_item_type); + return 1; +} + +static int setAmmoItem(lua_State *L) +{ + 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"); + + if (!enable_plugin()) + return 0; + + 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 int getShotSkill(df::building_siegeenginest *bld) { CHECK_NULL_POINTER(bld); @@ -446,7 +515,7 @@ static bool adjustToTarget(EngineInfo *engine, df::coord *pos) return true; for (df::coord fudge = *pos; - fudge.z < engine->target.second.z; fudge.z++) + fudge.z <= engine->target.second.z; fudge.z++) { if (!isTargetableTile(fudge)) continue; @@ -455,7 +524,7 @@ static bool adjustToTarget(EngineInfo *engine, df::coord *pos) } for (df::coord fudge = *pos; - fudge.z > engine->target.first.z; fudge.z--) + fudge.z >= engine->target.first.z; fudge.z--) { if (!isTargetableTile(fudge)) continue; @@ -1046,6 +1115,79 @@ struct projectile_hook : df::proj_itemst { IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement); +/* + * Building hook + */ + +struct building_hook : df::building_siegeenginest { + typedef df::building_siegeenginest interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + INTERPOSE_NEXT(updateAction)(); + + if (jobs.empty()) + return; + + if (auto engine = find_engine(this)) + { + auto job = jobs[0]; + + 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; + } + } + break; + + case job_type::FireCatapult: + case job_type::FireBallista: + if (auto worker = Job::getWorker(job)) + { + color_ostream_proxy out(Core::getInstance().getConsole()); + out.print("operator %d\n", worker->id); + + engine->operator_id = worker->id; + engine->operator_frame = world->frame_counter; + } + else + engine->operator_id = -1; + break; + + default: + break; + } + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction); + /* * Initialization */ @@ -1064,6 +1206,8 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(getTargetArea), + DFHACK_LUA_COMMAND(getAmmoItem), + DFHACK_LUA_COMMAND(setAmmoItem), DFHACK_LUA_COMMAND(projPosAtStep), DFHACK_LUA_COMMAND(projPathMetrics), DFHACK_LUA_COMMAND(adjustToTarget), @@ -1080,6 +1224,7 @@ static void enable_hooks(bool enable) is_enabled = enable; INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); + INTERPOSE_HOOK(building_hook, updateAction).apply(enable); if (enable) load_engines(); diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index 7ad828c90..d10a9df69 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -12,6 +12,21 @@ local wmap = df.global.world.map last_target_min = last_target_min or nil last_target_max = last_target_max or nil +local item_choices = { + { caption = 'boulders (default)', item_type = df.item_type.BOULDER }, + { caption = 'blocks', item_type = df.item_type.BLOCKS }, + { caption = 'weapons', item_type = df.item_type.WEAPON }, + { 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 = 'anything', item_type = -1 }, +} + +local item_choice_idx = {} +for i,v in ipairs(item_choices) do + item_choice_idx[v.item_type] = i +end + SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) SiegeEngine.focus_path = 'siege-engine' @@ -83,13 +98,8 @@ function SiegeEngine:zoomToTarget() local cx = math.floor((target_min.x + target_max.x)/2) local cy = math.floor((target_min.y + target_max.y)/2) local cz = math.floor((target_min.z + target_max.z)/2) - for z = cz,target_max.z do - if plugin.getTileStatus(self.building, xyz2pos(cx,cy,z)) ~= 'blocked' then - cz = z - break - end - end - self:centerViewOn(xyz2pos(cx,cy,cz)) + local pos = plugin.adjustToTarget(self.building, xyz2pos(cx,cy,cz)) + self:centerViewOn(pos) end end @@ -171,6 +181,19 @@ function SiegeEngine:onRenderBody_main(dc) dc:string("z",COLOR_LIGHTGREEN):string(": Zoom") end + dc:newline():newline(1) + if self.building.type == df.siegeengine_type.Ballista then + dc:string("Uses ballista arrows") + else + local item = plugin.getAmmoItem(self.building) + dc:string("u",COLOR_LIGHTGREEN):string(": Use ") + if item_choice_idx[item] then + dc:string(item_choices[item_choice_idx[item]].caption) + else + dc:string(df.item_type[item]) + end + end + if self.target_select_first then self:renderTargetView(self.target_select_first, guidm.getCursorPos()) else @@ -192,6 +215,19 @@ function SiegeEngine:setTargetArea(p1, p2) end end +function SiegeEngine:setAmmoItem(choice) + if self.building.type == df.siegeengine_type.Ballista then + return + end + + if not plugin.setAmmoItem(self.building, choice.item_type) then + dlg.showMessage( + 'Set Ammo Item', + 'Could not set the ammo item', COLOR_LIGHTRED + ) + end +end + function SiegeEngine:onInput_main(keys) if keys.CUSTOM_R then self:showCursor(true) @@ -199,6 +235,10 @@ function SiegeEngine:onInput_main(keys) self.mode = self.mode_aim elseif keys.CUSTOM_P and last_target_min then self:setTargetArea(last_target_min, last_target_max) + elseif keys.CUSTOM_U then + local item = plugin.getAmmoItem(self.building) + local idx = 1 + (item_choice_idx[item] or 0) % #item_choices + self:setAmmoItem(item_choices[idx]) elseif keys.CUSTOM_Z then self:zoomToTarget() elseif keys.CUSTOM_X then From 448d7e3633efd5171feaa3c3a0cdc4afbfafe105 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 12 Sep 2012 12:15:12 +0400 Subject: [PATCH 19/48] Support linking siege engines to stockpiles. Since they can't do that natively, the links object has to be maintained in dfhack memory, and with dfhack persistence. --- plugins/devel/siege-engine.cpp | 203 +++++++++++++++++++++++++++++++-- scripts/gui/siege-engine.lua | 105 ++++++++++++++++- 2 files changed, 298 insertions(+), 10 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 2720c62f8..6385111bc 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -46,6 +46,8 @@ #include "df/job_item.h" #include "df/item.h" #include "df/items_other_id.h" +#include "df/building_stockpilest.h" +#include "df/stockpile_links.h" #include "MiscUtils.h" @@ -158,6 +160,9 @@ struct EngineInfo { int operator_id, operator_frame; + std::set stockpiles; + df::stockpile_links links; + 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; } @@ -170,6 +175,8 @@ struct EngineInfo { static std::map engines; static std::map coord_engines; +static std::set recheck_piles; + static EngineInfo *find_engine(df::building *bld, bool create = false) { auto ebld = strict_virtual_cast(bld); @@ -209,12 +216,12 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) return obj; } -static EngineInfo *find_engine(lua_State *L, int idx, bool create = false) +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) + if (!engine && !silent) luaL_error(L, "no such engine"); return engine; @@ -241,6 +248,7 @@ static void clear_engines() { engines.clear(); coord_engines.clear(); + recheck_piles.clear(); } static void load_engines() @@ -267,13 +275,33 @@ static void load_engines() 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;; + } + auto plinks = pile->getStockpileLinks(); + if (!plinks) + continue; + + engine->stockpiles.insert(it->ival(1)); + + insert_into_vector(engine->links.take_from_pile, &df::building::id, pile); + insert_into_vector(plinks->give_to_workshop, &df::building::id, (df::building*)engine->bld); + } } static int getTargetArea(lua_State *L) { - auto bld = Lua::CheckDFObject(L, 1); - if (!bld) luaL_argerror(L, 1, "null building"); - auto engine = find_engine(bld); + auto engine = find_engine(L, 1, false, true); if (engine && engine->hasTarget()) { @@ -335,8 +363,11 @@ static bool setTargetArea(df::building_siegeenginest *bld, df::coord target_min, static int getAmmoItem(lua_State *L) { - auto engine = find_engine(L, 1, true); - Lua::Push(L, engine->ammo_item_type); + 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; } @@ -378,6 +409,123 @@ static int setAmmoItem(lua_State *L) return 1; } +static int getStockpileLinks(lua_State *L) +{ + auto engine = find_engine(L, 1, false, true); + if (!engine || engine->stockpiles.empty()) + return 0; + + int idx = 1; + lua_createtable(L, engine->stockpiles.size(), 0); + + for (auto it = engine->stockpiles.begin(); it != engine->stockpiles.end(); ++it) + { + auto pile = df::building::find(*it); + if (!pile) continue; + Lua::Push(L, pile); + lua_rawseti(L, -2, idx++); + } + + 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); + + auto plinks = pile->getStockpileLinks(); + CHECK_NULL_POINTER(plinks); + + 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); + + insert_into_vector(engine->links.take_from_pile, &df::building::id, (df::building*)pile); + insert_into_vector(plinks->give_to_workshop, &df::building::id, (df::building*)engine->bld); + return true; +} + +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 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); + + auto plinks = pile->getStockpileLinks(); + erase_from_vector(engine->links.take_from_pile, &df::building::id, pile->id); + erase_from_vector(plinks->give_to_workshop, &df::building::id, bld->id); + return true; + } + + return false; +} + +static void recheck_pile_links(color_ostream &out, EngineInfo *engine) +{ + auto removed = engine->stockpiles; + + out.print("rechecking piles in %d\n", engine->id); + + // Detect and save changes in take links + for (size_t i = 0; i < engine->links.take_from_pile.size(); i++) + { + auto pile = engine->links.take_from_pile[i]; + + removed.erase(pile->id); + + if (!engine->stockpiles.count(pile->id)) + addStockpileLink(engine->bld, (df::building_stockpilest*)pile); + } + + for (auto it = removed.begin(); it != removed.end(); it++) + forgetStockpileLink(engine, *it); + + // Remove give links + for (size_t i = 0; i < engine->links.give_to_pile.size(); i++) + { + auto pile = engine->links.give_to_pile[i]; + auto plinks = pile->getStockpileLinks(); + erase_from_vector(plinks->take_from_workshop, &df::building::id, engine->id); + } + + engine->links.give_to_pile.clear(); +} + static int getShotSkill(df::building_siegeenginest *bld) { CHECK_NULL_POINTER(bld); @@ -1122,6 +1270,25 @@ IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkMovement); struct building_hook : df::building_siegeenginest { typedef df::building_siegeenginest interpose_base; + DEFINE_VMETHOD_INTERPOSE(bool, canLinkToStockpile, ()) + { + if (find_engine(this, true)) + return true; + + return INTERPOSE_NEXT(canLinkToStockpile)(); + } + + DEFINE_VMETHOD_INTERPOSE(df::stockpile_links*, getStockpileLinks, ()) + { + if (auto engine = find_engine(this)) + { + recheck_piles.insert(this); + return &engine->links; + } + + return INTERPOSE_NEXT(getStockpileLinks)(); + } + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) { INTERPOSE_NEXT(updateAction)(); @@ -1186,6 +1353,8 @@ struct building_hook : df::building_siegeenginest { } }; +IMPLEMENT_VMETHOD_INTERPOSE(building_hook, canLinkToStockpile); +IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getStockpileLinks); IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction); /* @@ -1195,6 +1364,9 @@ IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction); 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(getTileStatus), DFHACK_LUA_FUNCTION(paintAimScreen), DFHACK_LUA_FUNCTION(canTargetUnit), @@ -1208,6 +1380,7 @@ 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), @@ -1224,6 +1397,9 @@ static void enable_hooks(bool enable) is_enabled = enable; INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); + + INTERPOSE_HOOK(building_hook, canLinkToStockpile).apply(enable); + INTERPOSE_HOOK(building_hook, getStockpileLinks).apply(enable); INTERPOSE_HOOK(building_hook, updateAction).apply(enable); if (enable) @@ -1246,7 +1422,7 @@ static bool enable_plugin() return true; } -static void clear_caches() +static void clear_caches(color_ostream &out) { if (!UnitPath::cache.empty()) { @@ -1255,6 +1431,15 @@ static void clear_caches() UnitPath::cache.clear(); } + + if (!recheck_piles.empty()) + { + for (auto it = recheck_piles.begin(); it != recheck_piles.end(); ++it) + if (auto engine = find_engine(*it)) + recheck_pile_links(out, engine); + + recheck_piles.clear(); + } } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) @@ -1300,6 +1485,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { - clear_caches(); + clear_caches(out); return CR_OK; } diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index d10a9df69..716dc89ba 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -35,7 +35,7 @@ function SiegeEngine:init(building) self:init_fields{ building = building, center = utils.getBuildingCenter(building), - links = {}, selected = 1, + selected_pile = 1, } guidm.MenuOverlay.init(self) self.mode_main = { @@ -46,6 +46,10 @@ function SiegeEngine:init(building) 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 @@ -157,6 +161,26 @@ function SiegeEngine:renderTargetView(target_min, target_max) end end +function SiegeEngine:scrollPiles(delta) + local links = plugin.getStockpileLinks(self.building) + if links then + self.selected_pile = 1+(self.selected_pile+delta-1) % #links + return links[self.selected_pile] + end +end + +function SiegeEngine:renderStockpiles(dc, links, nlines) + local idx = (self.selected_pile-1) % #links + local page = math.floor(idx/nlines) + for i = page*nlines,math.min(#links,(page+1)*nlines)-1 do + local color = COLOR_BROWN + if i == idx then + color = COLOR_YELLOW + end + dc:newline(2):string(utils.getBuildingName(links[i+1]), color) + end +end + function SiegeEngine:onRenderBody_main(dc) dc:newline(1):pen(COLOR_WHITE):string("Target: ") @@ -194,6 +218,15 @@ function SiegeEngine:onRenderBody_main(dc) end end + dc:newline():newline(1) + dc:string("t",COLOR_LIGHTGREEN):string(": Take from stockpile"):newline(3) + local links = plugin.getStockpileLinks(self.building) + if links then + dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ") + dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline() + self:renderStockpiles(dc, links, 19-dc:localY()) + end + if self.target_select_first then self:renderTargetView(self.target_select_first, guidm.getCursorPos()) else @@ -243,6 +276,25 @@ function SiegeEngine:onInput_main(keys) self:zoomToTarget() elseif keys.CUSTOM_X then plugin.clearTargetArea(self.building) + elseif keys.SECONDSCROLL_UP then + self:scrollPiles(-1) + elseif keys.SECONDSCROLL_DOWN then + self:scrollPiles(1) + elseif keys.CUSTOM_D then + local pile = self:scrollPiles(0) + if pile then + plugin.removeStockpileLink(self.building, pile) + end + elseif keys.CUSTOM_O then + local pile = self:scrollPiles(0) + if pile then + self:centerViewOn(utils.getBuildingCenter(pile)) + end + elseif keys.CUSTOM_T then + self:showCursor(true) + self.mode = self.mode_pile + self:sendInputToParent('CURSOR_DOWN_Z') + self:sendInputToParent('CURSOR_UP_Z') elseif self:simulateViewScroll(keys) then self.cursor = nil else @@ -316,6 +368,57 @@ function SiegeEngine:onInput_aim(keys) return true end +function SiegeEngine:onRenderBody_pile(dc) + dc:newline(1):string('Select pile to take from'):newline():newline(2) + + local sel = df.global.world.selected_building + + if df.building_stockpilest:is_instance(sel) then + dc:string(utils.getBuildingName(sel), COLOR_GREEN):newline():newline(1) + + if plugin.isLinkedToPile(self.building, sel) then + dc:string("Already taking from here"):newline():newline(2) + dc:string("d", COLOR_LIGHTGREEN):string(": Delete link") + else + dc:string("Enter",COLOR_LIGHTGREEN):string(": Take from this pile") + end + elseif sel then + dc:string(utils.getBuildingName(sel), COLOR_DARKGREY) + dc:newline():newline(1) + dc:string("Not a stockpile",COLOR_LIGHTRED) + else + dc:string("No building selected", COLOR_DARKGREY) + end +end + +function SiegeEngine:onInput_pile(keys) + if keys.SELECT then + local sel = df.global.world.selected_building + if df.building_stockpilest:is_instance(sel) + and not plugin.isLinkedToPile(self.building, sel) then + plugin.addStockpileLink(self.building, sel) + + df.global.world.selected_building = self.building + self.mode = self.mode_main + self:showCursor(false) + end + elseif keys.CUSTOM_D then + local sel = df.global.world.selected_building + if df.building_stockpilest:is_instance(sel) then + plugin.removeStockpileLink(self.building, sel) + end + elseif keys.LEAVESCREEN then + df.global.world.selected_building = self.building + self.mode = self.mode_main + self:showCursor(false) + elseif self:propagateMoveKeys(keys) then + -- + else + return false + end + return true +end + function SiegeEngine:onRenderBody(dc) dc:clear() dc:seek(1,1):pen(COLOR_WHITE):string(utils.getBuildingName(self.building)):newline() From 7c71aeab5f44c7fce106a0efc07c1ea2860e4638 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 12 Sep 2012 18:17:42 +0400 Subject: [PATCH 20/48] Add function for making item projectiles. --- LUA_API.rst | 4 +++ Lua API.html | 3 +++ library/LuaApi.cpp | 8 ++++++ library/include/modules/Items.h | 4 +++ library/modules/Items.cpp | 45 +++++++++++++++++++++++++++++++++ 5 files changed, 64 insertions(+) diff --git a/LUA_API.rst b/LUA_API.rst index a5be09a0e..1ffdada0a 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.makeProjectile(item)`` + + Turns the item into a projectile, and returns the new object, or *nil* if impossible. + Maps module ----------- diff --git a/Lua API.html b/Lua API.html index 7886fbc25..168f7dcc6 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.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 d39a945dd..f69fa7a1b 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -80,6 +80,7 @@ distribution. #include "df/region_map_entry.h" #include "df/flow_info.h" #include "df/unit_misc_trait.h" +#include "df/proj_itemst.h" #include #include @@ -885,6 +886,12 @@ static bool items_moveToInventory return Items::moveToInventory(mc, item, unit, mode, body_part); } +static df::proj_itemst *items_makeProjectile(df::item *item) +{ + MapExtras::MapCache mc; + return Items::makeProjectile(mc, item); +} + static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, getGeneralRef), WRAPM(Items, getSpecificRef), @@ -896,6 +903,7 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToBuilding, items_moveToBuilding), WRAPN(moveToInventory, items_moveToInventory), + WRAPN(makeProjectile, items_makeProjectile), { NULL, NULL } }; diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 4236f068a..7493d22fc 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -44,6 +44,7 @@ distribution. namespace df { struct itemdef; + struct proj_itemst; } namespace MapExtras { @@ -155,5 +156,8 @@ DFHACK_EXPORT bool moveToContainer(MapExtras::MapCache &mc, df::item *item, df:: DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::building_actual *building,int16_t use_mode); 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); + +/// 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 dc64143c9..751797f06 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -72,8 +72,11 @@ using namespace std; #include "df/general_ref_contains_itemst.h" #include "df/general_ref_contained_in_itemst.h" #include "df/general_ref_building_holderst.h" +#include "df/general_ref_projectile.h" #include "df/viewscreen_itemst.h" #include "df/vermin.h" +#include "df/proj_itemst.h" +#include "df/proj_list_link.h" #include "df/unit_inventory_item.h" #include "df/body_part_raw.h" @@ -88,6 +91,7 @@ using namespace df::enums; using df::global::world; using df::global::ui; using df::global::ui_selected_unit; +using df::global::proj_next_id; #define ITEMDEF_VECTORS \ ITEM(WEAPON, weapons, itemdef_weaponst) \ @@ -866,3 +870,44 @@ bool DFHack::Items::moveToInventory( return true; } + +df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item) +{ + CHECK_NULL_POINTER(item); + + if (!world || !proj_next_id) + return NULL; + + auto pos = getPosition(item); + if (!pos.isValid()) + return NULL; + + auto ref = df::allocate(); + if (!ref) + return NULL; + + if (!detachItem(mc, item)) + { + delete ref; + return NULL; + } + + item->pos = pos; + item->flags.bits.in_job = true; + + auto proj = new df::proj_itemst(); + proj->link = new df::proj_list_link(); + proj->link->item = proj; + proj->id = (*proj_next_id)++; + + proj->origin_pos = proj->target_pos = pos; + proj->cur_pos = proj->prev_pos = pos; + proj->item = item; + + ref->projectile_id = proj->id; + item->itemrefs.push_back(ref); + + linked_list_append(&world->proj_list, proj->link); + + return proj; +} From f06f9af6b80068fbafe340a59ff7d755399509d4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 12 Sep 2012 20:57:25 +0400 Subject: [PATCH 21/48] Throw items from bins around in siege engine, like minecarts do. --- library/modules/World.cpp | 6 +- library/xml | 2 +- plugins/devel/siege-engine.cpp | 170 ++++++++++++++++++++++++++++----- plugins/lua/siege-engine.lua | 3 +- 4 files changed, 155 insertions(+), 26 deletions(-) diff --git a/library/modules/World.cpp b/library/modules/World.cpp index e14aa02a0..67b8c1236 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -300,6 +300,8 @@ PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) void World::GetPersistentData(std::vector *vec, const std::string &key, bool prefix) { + vec->clear(); + if (!BuildPersistentCache()) return; @@ -343,8 +345,10 @@ bool World::DeletePersistentData(const PersistentDataItem &item) auto eqrange = d->persistent_index.equal_range(item.key_value); - for (auto it = eqrange.first; it != eqrange.second; ++it) + for (auto it2 = eqrange.first; it2 != eqrange.second; ) { + auto it = it2; ++it2; + if (it->second != -item.id) continue; diff --git a/library/xml b/library/xml index d55f1cf43..2bc8fbdf7 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit d55f1cf43dd71d3abee724bfa88a0a401b4ccaa3 +Subproject commit 2bc8fbdf71143398817d31e06e169a01cce37c50 diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 6385111bc..b41ac9314 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -136,6 +137,30 @@ 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; +} + /* * Configuration management */ @@ -172,7 +197,7 @@ struct EngineInfo { } }; -static std::map engines; +static std::map engines; static std::map coord_engines; static std::set recheck_piles; @@ -183,17 +208,18 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) if (!ebld) return NULL; - auto it = engines.find(bld); - if (it != engines.end()) + auto &obj = engines[bld]; + + if (obj) { - it->second.bld = ebld; - return &it->second; + obj->bld = ebld; + return obj; } if (!create) return NULL; - auto *obj = &engines[bld]; + obj = new EngineInfo(); obj->id = bld->id; obj->bld = ebld; @@ -246,6 +272,8 @@ static EngineInfo *find_engine(df::coord pos) static void clear_engines() { + for (auto it = engines.begin(); it != engines.end(); ++it) + delete it->second; engines.clear(); coord_engines.clear(); recheck_piles.clear(); @@ -373,14 +401,14 @@ static int getAmmoItem(lua_State *L) 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"); - if (!enable_plugin()) - return 0; - auto pworld = Core::getInstance().getWorld(); auto key = stl_sprintf("siege-engine/ammo/%d", engine->id); auto entry = pworld->GetPersistentData(key, NULL); @@ -526,7 +554,7 @@ static void recheck_pile_links(color_ostream &out, EngineInfo *engine) engine->links.give_to_pile.clear(); } -static int getShotSkill(df::building_siegeenginest *bld) +static int getOperatorSkill(df::building_siegeenginest *bld, bool force = false) { CHECK_NULL_POINTER(bld); @@ -534,10 +562,24 @@ static int getShotSkill(df::building_siegeenginest *bld) if (!engine) return 0; - 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); + 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; } @@ -1213,15 +1255,18 @@ struct projectile_hook : df::proj_itemst { 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, 3, 1); + lua_call(L, 5, 1); if (lua_isnil(L, -1)) proj->aimAtArea(engine); @@ -1233,7 +1278,8 @@ struct projectile_hook : df::proj_itemst { void doCheckMovement() { - if (distance_flown != 0 || fall_counter != fall_delay) + if (flags.bits.parabolic || distance_flown != 0 || + fall_counter != fall_delay || item == NULL) return; auto engine = find_engine(origin_pos); @@ -1244,12 +1290,78 @@ struct projectile_hook : df::proj_itemst { CoreSuspendClaimer suspend; color_ostream_proxy out(Core::getInstance().getConsole()); + int skill = getOperatorSkill(engine->bld, true); + lua_pushcfunction(L, safeAimProjectile); lua_pushlightuserdata(L, this); lua_pushlightuserdata(L, engine); + lua_pushinteger(L, skill); - if (!Lua::Core::SafeCall(out, 2, 0)) + if (!Lua::Core::SafeCall(out, 3, 0)) aimAtArea(engine); + + 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; + + // Flight direction vector + df::coord dist = target_pos - origin_pos; + float vx = dist.x, vy = dist.y, vz = fabs(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]; + auto proj = Items::makeProjectile(mc, child); + if (!proj) continue; + + proj->flags.bits.no_impact_destroy = true; + //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 = int(speed * sz); + } } DEFINE_VMETHOD_INTERPOSE(bool, checkMovement, ()) @@ -1259,9 +1371,23 @@ struct projectile_hook : df::proj_itemst { 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 @@ -1299,6 +1425,7 @@ struct building_hook : df::building_siegeenginest { if (auto engine = find_engine(this)) { auto job = jobs[0]; + bool save_op = false; switch (job->job_type) { @@ -1330,20 +1457,16 @@ struct building_hook : df::building_siegeenginest { break; } } - break; + // fallthrough + case job_type::LoadBallista: case job_type::FireCatapult: case job_type::FireBallista: if (auto worker = Job::getWorker(job)) { - color_ostream_proxy out(Core::getInstance().getConsole()); - out.print("operator %d\n", worker->id); - engine->operator_id = worker->id; engine->operator_frame = world->frame_counter; } - else - engine->operator_id = -1; break; default: @@ -1397,6 +1520,7 @@ 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, canLinkToStockpile).apply(enable); INTERPOSE_HOOK(building_hook, getStockpileLinks).apply(enable); diff --git a/plugins/lua/siege-engine.lua b/plugins/lua/siege-engine.lua index d078ce1d7..89c47659d 100644 --- a/plugins/lua/siege-engine.lua +++ b/plugins/lua/siege-engine.lua @@ -33,7 +33,8 @@ function findShotHeight(engine, target) end end -function doAimProjectile(engine, target_min, target_max) +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) From 46321a6a01ba4fff79992c7bde26c812b14fd382 Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 12 Sep 2012 13:41:59 -0500 Subject: [PATCH 22/48] Rename world_data.unk_204 to feature_map --- library/modules/Maps.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index d0401164a..f1f40f19c 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -307,7 +307,7 @@ df::feature_init *Maps::getLocalInitFeature(df::coord2d rgn_pos, int32_t index) df::coord2d bigregion = rgn_pos / 16; // bigregion is 16x16 regions. for each bigregion in X dimension: - auto fptr = data->unk_204[bigregion.x][bigregion.y].features; + auto fptr = data->feature_map[bigregion.x][bigregion.y].features; if (!fptr) return NULL; From c9d73cb6fb846a7e1b5befcb13d1712c0cce831a Mon Sep 17 00:00:00 2001 From: Quietust Date: Wed, 12 Sep 2012 13:42:16 -0500 Subject: [PATCH 23/48] Fix crash bug when using manipulator in Arena mode --- plugins/manipulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 1a90d2eea..06861bf0f 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -623,7 +623,7 @@ void viewscreen_unitlaborsst::render() Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 1); Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2); df::profession profession = columns[col_offset].profession; - if (profession != profession::NONE) + if ((profession != profession::NONE) && (ui->race_id != -1)) { auto graphics = world->raws.creatures.all[ui->race_id]->graphics; Screen::paintTile( From ae6e0f617dec8394b1572b400928f3a6676211ab Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 13 Sep 2012 08:27:28 -0500 Subject: [PATCH 24/48] Make it clear that this is from DFHack, and properly name it Dwarf Manipulator --- plugins/manipulator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 06861bf0f..429bcc173 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -603,7 +603,7 @@ void viewscreen_unitlaborsst::render() dfhack_viewscreen::render(); Screen::clear(); - Screen::drawBorder(" Manage Labors "); + Screen::drawBorder(" Dwarf Manipulator - Manage Labors "); for (int col = 0; col < labors_width; col++) { @@ -810,7 +810,7 @@ struct unitlist_hook : df::viewscreen_unitlistst { int x = 2; OutputString(12, x, gps->dimy - 2, "l"); // UNITVIEW_PRF_PROF key - OutputString(15, x, gps->dimy - 2, ": Manage labors"); + OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)"); } } }; From 5690a26439c275995f48f60a3a7ba18c86e2c083 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 13 Sep 2012 17:49:41 +0400 Subject: [PATCH 25/48] On second thought, remove stockpile->engine links; keep only reverse. Bi-directional links involve the risk of crashes if the plugin is unloaded, and the engine subsequently deconstructed. --- plugins/devel/siege-engine.cpp | 120 ++++++++++----------------------- 1 file changed, 34 insertions(+), 86 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index b41ac9314..3e5777e5b 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -200,8 +200,6 @@ struct EngineInfo { static std::map engines; static std::map coord_engines; -static std::set recheck_piles; - static EngineInfo *find_engine(df::building *bld, bool create = false) { auto ebld = strict_virtual_cast(bld); @@ -276,7 +274,6 @@ static void clear_engines() delete it->second; engines.clear(); coord_engines.clear(); - recheck_piles.clear(); } static void load_engines() @@ -316,14 +313,8 @@ static void load_engines() pworld->DeletePersistentData(*it); continue;; } - auto plinks = pile->getStockpileLinks(); - if (!plinks) - continue; engine->stockpiles.insert(it->ival(1)); - - insert_into_vector(engine->links.take_from_pile, &df::building::id, pile); - insert_into_vector(plinks->give_to_workshop, &df::building::id, (df::building*)engine->bld); } } @@ -437,21 +428,47 @@ static int setAmmoItem(lua_State *L) 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; - int idx = 1; - lua_createtable(L, engine->stockpiles.size(), 0); + update_stockpile_links(engine); + + auto &links = engine->links.take_from_pile; + lua_createtable(L, links.size(), 0); - for (auto it = engine->stockpiles.begin(); it != engine->stockpiles.end(); ++it) + for (size_t i = 0; i < links.size(); i++) { - auto pile = df::building::find(*it); - if (!pile) continue; - Lua::Push(L, pile); - lua_rawseti(L, -2, idx++); + Lua::Push(L, links[i]); + lua_rawseti(L, -2, i+1); } return 1; @@ -472,9 +489,6 @@ static bool addStockpileLink(df::building_siegeenginest *bld, df::building_stock CHECK_NULL_POINTER(bld); CHECK_NULL_POINTER(pile); - auto plinks = pile->getStockpileLinks(); - CHECK_NULL_POINTER(plinks); - if (!enable_plugin()) return false; @@ -490,21 +504,9 @@ static bool addStockpileLink(df::building_siegeenginest *bld, df::building_stock entry.ival(1) = pile->id; engine->stockpiles.insert(pile->id); - - insert_into_vector(engine->links.take_from_pile, &df::building::id, (df::building*)pile); - insert_into_vector(plinks->give_to_workshop, &df::building::id, (df::building*)engine->bld); return true; } -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 bool removeStockpileLink(df::building_siegeenginest *bld, df::building_stockpilest *pile) { CHECK_NULL_POINTER(bld); @@ -513,47 +515,12 @@ static bool removeStockpileLink(df::building_siegeenginest *bld, df::building_st if (auto engine = find_engine(bld)) { forgetStockpileLink(engine, pile->id); - - auto plinks = pile->getStockpileLinks(); - erase_from_vector(engine->links.take_from_pile, &df::building::id, pile->id); - erase_from_vector(plinks->give_to_workshop, &df::building::id, bld->id); return true; } return false; } -static void recheck_pile_links(color_ostream &out, EngineInfo *engine) -{ - auto removed = engine->stockpiles; - - out.print("rechecking piles in %d\n", engine->id); - - // Detect and save changes in take links - for (size_t i = 0; i < engine->links.take_from_pile.size(); i++) - { - auto pile = engine->links.take_from_pile[i]; - - removed.erase(pile->id); - - if (!engine->stockpiles.count(pile->id)) - addStockpileLink(engine->bld, (df::building_stockpilest*)pile); - } - - for (auto it = removed.begin(); it != removed.end(); it++) - forgetStockpileLink(engine, *it); - - // Remove give links - for (size_t i = 0; i < engine->links.give_to_pile.size(); i++) - { - auto pile = engine->links.give_to_pile[i]; - auto plinks = pile->getStockpileLinks(); - erase_from_vector(plinks->take_from_workshop, &df::building::id, engine->id); - } - - engine->links.give_to_pile.clear(); -} - static int getOperatorSkill(df::building_siegeenginest *bld, bool force = false) { CHECK_NULL_POINTER(bld); @@ -1396,19 +1363,11 @@ IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkImpact); struct building_hook : df::building_siegeenginest { typedef df::building_siegeenginest interpose_base; - DEFINE_VMETHOD_INTERPOSE(bool, canLinkToStockpile, ()) - { - if (find_engine(this, true)) - return true; - - return INTERPOSE_NEXT(canLinkToStockpile)(); - } - DEFINE_VMETHOD_INTERPOSE(df::stockpile_links*, getStockpileLinks, ()) { if (auto engine = find_engine(this)) { - recheck_piles.insert(this); + update_stockpile_links(engine); return &engine->links; } @@ -1476,7 +1435,6 @@ struct building_hook : df::building_siegeenginest { } }; -IMPLEMENT_VMETHOD_INTERPOSE(building_hook, canLinkToStockpile); IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getStockpileLinks); IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction); @@ -1522,7 +1480,6 @@ static void enable_hooks(bool enable) INTERPOSE_HOOK(projectile_hook, checkMovement).apply(enable); INTERPOSE_HOOK(projectile_hook, checkImpact).apply(enable); - INTERPOSE_HOOK(building_hook, canLinkToStockpile).apply(enable); INTERPOSE_HOOK(building_hook, getStockpileLinks).apply(enable); INTERPOSE_HOOK(building_hook, updateAction).apply(enable); @@ -1555,15 +1512,6 @@ static void clear_caches(color_ostream &out) UnitPath::cache.clear(); } - - if (!recheck_piles.empty()) - { - for (auto it = recheck_piles.begin(); it != recheck_piles.end(); ++it) - if (auto engine = find_engine(*it)) - recheck_pile_links(out, engine); - - recheck_piles.clear(); - } } DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) From 881fed41def071e43c324ce81579837d8949c3b7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 13 Sep 2012 20:20:56 +0400 Subject: [PATCH 26/48] Support setting workshop profile for siege engines. Since can't use built-in viewscreen, UI limited to skills only. --- plugins/devel/siege-engine.cpp | 91 +++++++++++++++++++++++++++++++++- scripts/gui/siege-engine.lua | 36 ++++++++++++-- 2 files changed, 122 insertions(+), 5 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 3e5777e5b..a41bfe5f7 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -49,6 +49,7 @@ #include "df/items_other_id.h" #include "df/building_stockpilest.h" #include "df/stockpile_links.h" +#include "df/workshop_profile.h" #include "MiscUtils.h" @@ -162,7 +163,7 @@ static void random_direction(float &x, float &y, float &z) } /* - * Configuration management + * Configuration object */ static bool enable_plugin(); @@ -187,6 +188,7 @@ struct EngineInfo { 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); } @@ -268,6 +270,10 @@ static EngineInfo *find_engine(df::coord pos) return engine; } +/* + * Configuration management + */ + static void clear_engines() { for (auto it = engines.begin(); it != engines.end(); ++it) @@ -316,6 +322,30 @@ static void load_engines() 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) @@ -521,6 +551,52 @@ static bool removeStockpileLink(df::building_siegeenginest *bld, df::building_st 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); @@ -552,7 +628,7 @@ static int getOperatorSkill(df::building_siegeenginest *bld, bool force = false) } /* - * Trajectory + * Trajectory raytracing */ struct ProjectilePath { @@ -1363,6 +1439,14 @@ IMPLEMENT_VMETHOD_INTERPOSE(projectile_hook, checkImpact); 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)) @@ -1435,6 +1519,7 @@ struct building_hook : df::building_siegeenginest { } }; +IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getWorkshopProfile); IMPLEMENT_VMETHOD_INTERPOSE(building_hook, getStockpileLinks); IMPLEMENT_VMETHOD_INTERPOSE(building_hook, updateAction); @@ -1448,6 +1533,7 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { 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), @@ -1480,6 +1566,7 @@ static void enable_hooks(bool 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); diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index 716dc89ba..47043cbb1 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -8,6 +8,8 @@ local dlg = require 'gui.dialogs' local plugin = require 'plugins.siege-engine' local wmap = df.global.world.map +local LEGENDARY = df.skill_rating.Legendary + -- Globals kept between script calls last_target_min = last_target_min or nil last_target_max = last_target_max or nil @@ -64,7 +66,10 @@ function SiegeEngine:onShow() end function SiegeEngine:onDestroy() - if self.building then + if self.save_profile then + plugin.saveWorkshopProfile(self.building) + end + if not self.no_select_building then self:selectBuilding(self.building, self.old_cursor, self.old_viewport, 10) end end @@ -221,12 +226,20 @@ function SiegeEngine:onRenderBody_main(dc) dc:newline():newline(1) dc:string("t",COLOR_LIGHTGREEN):string(": Take from stockpile"):newline(3) local links = plugin.getStockpileLinks(self.building) + local bottom = dc.height - 5 if links then dc:string("d",COLOR_LIGHTGREEN):string(": Delete, ") dc:string("o",COLOR_LIGHTGREEN):string(": Zoom"):newline() - self:renderStockpiles(dc, links, 19-dc:localY()) + self:renderStockpiles(dc, links, bottom-2-dc:localY()) + dc:newline():newline() end + local prof = self.building:getWorkshopProfile() or {} + dc:seek(1,math.max(dc:localY(),19)):string('ghjk',COLOR_LIGHTGREEN)dc:string(': ') + dc:string(df.skill_rating.attrs[prof.min_level or 0].caption):string('-') + dc:string(df.skill_rating.attrs[math.min(LEGENDARY,prof.max_level or 3000)].caption) + dc:newline():newline() + if self.target_select_first then self:renderTargetView(self.target_select_first, guidm.getCursorPos()) else @@ -295,6 +308,23 @@ function SiegeEngine:onInput_main(keys) self.mode = self.mode_pile self:sendInputToParent('CURSOR_DOWN_Z') self:sendInputToParent('CURSOR_UP_Z') + elseif keys.CUSTOM_G then + local prof = plugin.saveWorkshopProfile(self.building) + prof.min_level = math.max(0, prof.min_level-1) + plugin.saveWorkshopProfile(self.building) + elseif keys.CUSTOM_H then + local prof = plugin.saveWorkshopProfile(self.building) + prof.min_level = math.min(LEGENDARY, prof.min_level+1) + plugin.saveWorkshopProfile(self.building) + elseif keys.CUSTOM_J then + local prof = plugin.saveWorkshopProfile(self.building) + prof.max_level = math.max(0, math.min(LEGENDARY,prof.max_level)-1) + plugin.saveWorkshopProfile(self.building) + elseif keys.CUSTOM_K then + local prof = plugin.saveWorkshopProfile(self.building) + prof.max_level = math.min(LEGENDARY, prof.max_level+1) + if prof.max_level >= LEGENDARY then prof.max_level = 3000 end + plugin.saveWorkshopProfile(self.building) elseif self:simulateViewScroll(keys) then self.cursor = nil else @@ -439,7 +469,7 @@ function SiegeEngine:onInput(keys) self:dismiss() elseif keys.LEAVESCREEN_ALL then self:dismiss() - self.building = nil + self.no_select_building = true guidm.clearCursorPos() df.global.ui.main.mode = df.ui_sidebar_mode.Default df.global.world.selected_building = nil From 24b93ea61fd1cb6ad69c2e3beab182f5f60646c7 Mon Sep 17 00:00:00 2001 From: Timothy Collett Date: Thu, 13 Sep 2012 14:58:52 -0400 Subject: [PATCH 27/48] Library location fixing script --- library/CMakeLists.txt | 4 ++++ package/darwin/fix-libs.sh | 21 +++++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100755 package/darwin/fix-libs.sh diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 109a97e7c..536f4d34d 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -286,6 +286,10 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) +if(APPLE) + add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...") +endif() + IF(UNIX) if (APPLE) install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack diff --git a/package/darwin/fix-libs.sh b/package/darwin/fix-libs.sh new file mode 100755 index 000000000..cff98b6a6 --- /dev/null +++ b/package/darwin/fix-libs.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +BUILD_DIR=`pwd` + +echo "Fixing library dependencies in $BUILD_DIR/library" + +install_name_tool -change $BUILD_DIR/library/libdfhack.1.0.0.dylib @executable_path/hack/libdfhack.1.0.0.dylib library/libdfhack.1.0.0.dylib +install_name_tool -change $BUILD_DIR/library/libdfhack-client.dylib @executable_path/hack/libdfhack-client.dylib library/libdfhack-client.dylib +install_name_tool -change $BUILD_DIR/library/libdfhack-client.dylib @executable_path/hack/libdfhack-client.dylib library/dfhack-run +install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/libdfhack.1.0.0.dylib +install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/libdfhack-client.dylib +install_name_tool -change $BUILD_DIR/depends/protobuf/libprotobuf-lite.dylib @executable_path/hack/libprotobuf-lite.dylib library/dfhack-run +install_name_tool -change $BUILD_DIR/depends/lua/liblua.dylib @executable_path/hack/liblua.dylib library/libdfhack.1.0.0.dylib +install_name_tool -change @executable_path/../Frameworks/SDL.framework/Versions/A/SDL @executable_path/libs/SDL.framework/Versions/A/SDL library/libdfhack.1.0.0.dylib +install_name_tool -change /usr/local/lib/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack.1.0.0.dylib +install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack.1.0.0.dylib +install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/libdfhack-client.dylib +install_name_tool -change /opt/local/lib/i386/libstdc++.6.dylib @executable_path/libs/libstdc++.6.dylib library/dfhack-run +install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/libdfhack.1.0.0.dylib +install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/libdfhack-client.dylib +install_name_tool -change /opt/local/lib/i386/libgcc_s.1.dylib @executable_path/libs/libgcc_s.1.dylib library/dfhack-run \ No newline at end of file From 1325b70e4147996758ac434cbf898844f4ef6d6f Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 13 Sep 2012 14:42:17 -0500 Subject: [PATCH 28/48] Cleanup autolabor+cleanowned, fix depends/clsocket --- depends/clsocket | 2 +- plugins/autolabor.cpp | 349 +++++++++++++++++++---------------------- plugins/cleanowned.cpp | 16 +- 3 files changed, 169 insertions(+), 198 deletions(-) diff --git a/depends/clsocket b/depends/clsocket index 3808a8ac4..d0b2d0750 160000 --- a/depends/clsocket +++ b/depends/clsocket @@ -1 +1 @@ -Subproject commit 3808a8ac4fc1bbc0422492cb042099c47a312b58 +Subproject commit d0b2d0750dc2d529a152eba4f3f519f69ff7eab0 diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index c3a2b313e..c39b126c9 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -9,6 +9,7 @@ #include #include +#include "modules/Units.h" #include "modules/World.h" // DF data structure definition headers @@ -358,11 +359,11 @@ static const dwarf_state dwarf_states[] = { OTHER /* DrinkBlood */, OTHER /* ReportCrime */, OTHER /* ExecuteCriminal */, - BUSY /* TrainAnimal */, - BUSY /* CarveTrack */, - BUSY /* PushTrackVehicle */, - BUSY /* PlaceTrackVehicle */, - BUSY /* StoreItemInVehicle */ + BUSY /* TrainAnimal */, + BUSY /* CarveTrack */, + BUSY /* PushTrackVehicle */, + BUSY /* PlaceTrackVehicle */, + BUSY /* StoreItemInVehicle */ }; struct labor_info @@ -397,108 +398,108 @@ static int hauler_pct = 33; static std::vector labor_infos; static const struct labor_default default_labor_infos[] = { - /* MINE */ {AUTOMATIC, true, 2, 200, 0}, - /* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, - /* HAUL_BODY */ {HAULERS, false, 1, 200, 0}, - /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0}, - /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0}, - /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0}, - /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0}, - /* CLEAN */ {HAULERS, false, 1, 200, 0}, - /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0}, - /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0}, - /* DETAIL */ {AUTOMATIC, false, 1, 200, 0}, - /* MASON */ {AUTOMATIC, false, 1, 200, 0}, - /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0}, - /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0}, - /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0}, - /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0}, - /* SURGERY */ {AUTOMATIC, false, 1, 200, 0}, - /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0}, - /* SUTURING */ {AUTOMATIC, false, 1, 200, 0}, - /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0}, - /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0}, - /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0}, - /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0}, - /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0}, - /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0}, - /* LEATHER */ {AUTOMATIC, false, 1, 200, 0}, - /* TANNER */ {AUTOMATIC, false, 1, 200, 0}, - /* BREWER */ {AUTOMATIC, false, 1, 200, 0}, - /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0}, - /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* WEAVER */ {AUTOMATIC, false, 1, 200, 0}, - /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* MILLER */ {AUTOMATIC, false, 1, 200, 0}, - /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0}, - /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0}, - /* MILK */ {AUTOMATIC, false, 1, 200, 0}, - /* COOK */ {AUTOMATIC, false, 1, 200, 0}, - /* PLANT */ {AUTOMATIC, false, 1, 200, 0}, - /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0}, - /* FISH */ {AUTOMATIC, false, 1, 1, 0}, - /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0}, - /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0}, - /* HUNT */ {AUTOMATIC, true, 1, 1, 0}, - /* SMELT */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0}, - /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0}, - /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0}, - /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0}, - /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0}, - /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0}, - /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0}, - /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0}, - /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0}, - /* BOWYER */ {AUTOMATIC, false, 1, 200, 0}, - /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0}, - /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0}, - /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0}, - /* DYER */ {AUTOMATIC, false, 1, 200, 0}, - /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0}, - /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0}, - /* SHEARER */ {AUTOMATIC, false, 1, 200, 0}, - /* SPINNER */ {AUTOMATIC, false, 1, 200, 0}, - /* POTTERY */ {AUTOMATIC, false, 1, 200, 0}, - /* GLAZING */ {AUTOMATIC, false, 1, 200, 0}, - /* PRESSING */ {AUTOMATIC, false, 1, 200, 0}, - /* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) - /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, + /* MINE */ {AUTOMATIC, true, 2, 200, 0}, + /* HAUL_STONE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_WOOD */ {HAULERS, false, 1, 200, 0}, + /* HAUL_BODY */ {HAULERS, false, 1, 200, 0}, + /* HAUL_FOOD */ {HAULERS, false, 1, 200, 0}, + /* HAUL_REFUSE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_ITEM */ {HAULERS, false, 1, 200, 0}, + /* HAUL_FURNITURE */ {HAULERS, false, 1, 200, 0}, + /* HAUL_ANIMAL */ {HAULERS, false, 1, 200, 0}, + /* CLEAN */ {HAULERS, false, 1, 200, 0}, + /* CUTWOOD */ {AUTOMATIC, true, 1, 200, 0}, + /* CARPENTER */ {AUTOMATIC, false, 1, 200, 0}, + /* DETAIL */ {AUTOMATIC, false, 1, 200, 0}, + /* MASON */ {AUTOMATIC, false, 1, 200, 0}, + /* ARCHITECT */ {AUTOMATIC, false, 1, 200, 0}, + /* ANIMALTRAIN */ {AUTOMATIC, false, 1, 200, 0}, + /* ANIMALCARE */ {AUTOMATIC, false, 1, 200, 0}, + /* DIAGNOSE */ {AUTOMATIC, false, 1, 200, 0}, + /* SURGERY */ {AUTOMATIC, false, 1, 200, 0}, + /* BONE_SETTING */ {AUTOMATIC, false, 1, 200, 0}, + /* SUTURING */ {AUTOMATIC, false, 1, 200, 0}, + /* DRESSING_WOUNDS */ {AUTOMATIC, false, 1, 200, 0}, + /* FEED_WATER_CIVILIANS */ {AUTOMATIC, false, 200, 200, 0}, + /* RECOVER_WOUNDED */ {HAULERS, false, 1, 200, 0}, + /* BUTCHER */ {AUTOMATIC, false, 1, 200, 0}, + /* TRAPPER */ {AUTOMATIC, false, 1, 200, 0}, + /* DISSECT_VERMIN */ {AUTOMATIC, false, 1, 200, 0}, + /* LEATHER */ {AUTOMATIC, false, 1, 200, 0}, + /* TANNER */ {AUTOMATIC, false, 1, 200, 0}, + /* BREWER */ {AUTOMATIC, false, 1, 200, 0}, + /* ALCHEMIST */ {AUTOMATIC, false, 1, 200, 0}, + /* SOAP_MAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* WEAVER */ {AUTOMATIC, false, 1, 200, 0}, + /* CLOTHESMAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* MILLER */ {AUTOMATIC, false, 1, 200, 0}, + /* PROCESS_PLANT */ {AUTOMATIC, false, 1, 200, 0}, + /* MAKE_CHEESE */ {AUTOMATIC, false, 1, 200, 0}, + /* MILK */ {AUTOMATIC, false, 1, 200, 0}, + /* COOK */ {AUTOMATIC, false, 1, 200, 0}, + /* PLANT */ {AUTOMATIC, false, 1, 200, 0}, + /* HERBALIST */ {AUTOMATIC, false, 1, 200, 0}, + /* FISH */ {AUTOMATIC, false, 1, 1, 0}, + /* CLEAN_FISH */ {AUTOMATIC, false, 1, 200, 0}, + /* DISSECT_FISH */ {AUTOMATIC, false, 1, 200, 0}, + /* HUNT */ {AUTOMATIC, true, 1, 1, 0}, + /* SMELT */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_WEAPON */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_ARMOR */ {AUTOMATIC, false, 1, 200, 0}, + /* FORGE_FURNITURE */ {AUTOMATIC, false, 1, 200, 0}, + /* METAL_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* CUT_GEM */ {AUTOMATIC, false, 1, 200, 0}, + /* ENCRUST_GEM */ {AUTOMATIC, false, 1, 200, 0}, + /* WOOD_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* STONE_CRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* BONE_CARVE */ {AUTOMATIC, false, 1, 200, 0}, + /* GLASSMAKER */ {AUTOMATIC, false, 1, 200, 0}, + /* EXTRACT_STRAND */ {AUTOMATIC, false, 1, 200, 0}, + /* SIEGECRAFT */ {AUTOMATIC, false, 1, 200, 0}, + /* SIEGEOPERATE */ {AUTOMATIC, false, 1, 200, 0}, + /* BOWYER */ {AUTOMATIC, false, 1, 200, 0}, + /* MECHANIC */ {AUTOMATIC, false, 1, 200, 0}, + /* POTASH_MAKING */ {AUTOMATIC, false, 1, 200, 0}, + /* LYE_MAKING */ {AUTOMATIC, false, 1, 200, 0}, + /* DYER */ {AUTOMATIC, false, 1, 200, 0}, + /* BURN_WOOD */ {AUTOMATIC, false, 1, 200, 0}, + /* OPERATE_PUMP */ {AUTOMATIC, false, 1, 200, 0}, + /* SHEARER */ {AUTOMATIC, false, 1, 200, 0}, + /* SPINNER */ {AUTOMATIC, false, 1, 200, 0}, + /* POTTERY */ {AUTOMATIC, false, 1, 200, 0}, + /* GLAZING */ {AUTOMATIC, false, 1, 200, 0}, + /* PRESSING */ {AUTOMATIC, false, 1, 200, 0}, + /* BEEKEEPING */ {AUTOMATIC, false, 1, 1, 0}, // reduce risk of stuck beekeepers (see http://www.bay12games.com/dwarves/mantisbt/view.php?id=3981) + /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, /* PUSH_HAUL_VEHICLES */ {HAULERS, false, 1, 200, 0} }; static const int responsibility_penalties[] = { - 0, /* LAW_MAKING */ - 0, /* LAW_ENFORCEMENT */ - 3000, /* RECEIVE_DIPLOMATS */ - 0, /* MEET_WORKERS */ - 1000, /* MANAGE_PRODUCTION */ - 3000, /* TRADE */ - 1000, /* ACCOUNTING */ - 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ - 0, /* MAKE_INTRODUCTIONS */ - 0, /* MAKE_PEACE_AGREEMENTS */ - 0, /* MAKE_TOPIC_AGREEMENTS */ - 0, /* COLLECT_TAXES */ - 0, /* ESCORT_TAX_COLLECTOR */ - 0, /* EXECUTIONS */ - 0, /* TAME_EXOTICS */ - 0, /* RELIGION */ - 0, /* ATTACK_ENEMIES */ - 0, /* PATROL_TERRITORY */ - 0, /* MILITARY_GOALS */ - 0, /* MILITARY_STRATEGY */ - 0, /* UPGRADE_SQUAD_EQUIPMENT */ - 0, /* EQUIPMENT_MANIFESTS */ - 0, /* SORT_AMMUNITION */ - 0, /* BUILD_MORALE */ - 5000 /* HEALTH_MANAGEMENT */ + 0, /* LAW_MAKING */ + 0, /* LAW_ENFORCEMENT */ + 3000, /* RECEIVE_DIPLOMATS */ + 0, /* MEET_WORKERS */ + 1000, /* MANAGE_PRODUCTION */ + 3000, /* TRADE */ + 1000, /* ACCOUNTING */ + 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ + 0, /* MAKE_INTRODUCTIONS */ + 0, /* MAKE_PEACE_AGREEMENTS */ + 0, /* MAKE_TOPIC_AGREEMENTS */ + 0, /* COLLECT_TAXES */ + 0, /* ESCORT_TAX_COLLECTOR */ + 0, /* EXECUTIONS */ + 0, /* TAME_EXOTICS */ + 0, /* RELIGION */ + 0, /* ATTACK_ENEMIES */ + 0, /* PATROL_TERRITORY */ + 0, /* MILITARY_GOALS */ + 0, /* MILITARY_STRATEGY */ + 0, /* UPGRADE_SQUAD_EQUIPMENT */ + 0, /* EQUIPMENT_MANIFESTS */ + 0, /* SORT_AMMUNITION */ + 0, /* BUILD_MORALE */ + 5000 /* HEALTH_MANAGEMENT */ }; struct dwarf_info_t @@ -537,7 +538,7 @@ static void cleanup_state() labor_infos.clear(); } -static void reset_labor(df::enums::unit_labor::unit_labor labor) +static void reset_labor(df::unit_labor labor) { labor_infos[labor].set_minimum_dwarfs(default_labor_infos[labor].minimum_dwarfs); labor_infos[labor].set_maximum_dwarfs(default_labor_infos[labor].maximum_dwarfs); @@ -576,7 +577,7 @@ static void init_state() for (auto p = items.begin(); p != items.end(); p++) { string key = p->key(); - df::enums::unit_labor::unit_labor labor = (df::enums::unit_labor::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); + df::unit_labor labor = (df::unit_labor) atoi(key.substr(strlen("autolabor/labors/")).c_str()); if (labor >= 0 && labor <= labor_infos.size()) { labor_infos[labor].config = *p; @@ -597,7 +598,7 @@ static void init_state() labor_infos[i].is_exclusive = default_labor_infos[i].is_exclusive; labor_infos[i].active_dwarfs = 0; - reset_labor((df::enums::unit_labor::unit_labor) i); + reset_labor((df::unit_labor) i); } generate_labor_to_skill_map(); @@ -611,12 +612,12 @@ static void generate_labor_to_skill_map() // Generate labor -> skill mapping for (int i = 0; i <= ENUM_LAST_ITEM(unit_labor); i++) - labor_to_skill[i] = df::enums::job_skill::NONE; + labor_to_skill[i] = job_skill::NONE; FOR_ENUM_ITEMS(job_skill, skill) { int labor = ENUM_ATTR(job_skill, labor, skill); - if (labor != df::enums::unit_labor::NONE) + if (labor != unit_labor::NONE) { /* assert(labor >= 0); @@ -779,7 +780,7 @@ static void assign_labor(unit_labor::unit_labor labor, int value = dwarf_info[dwarf].mastery_penalty; - if (skill != df::enums::job_skill::NONE) + if (skill != job_skill::NONE) { int skill_level = 0; int skill_experience = 0; @@ -843,9 +844,9 @@ static void assign_labor(unit_labor::unit_labor labor, int max_dwarfs = labor_infos[labor].maximum_dwarfs(); // Special - don't assign hunt without a butchers, or fish without a fishery - if (df::enums::unit_labor::HUNT == labor && !has_butchers) + if (unit_labor::HUNT == labor && !has_butchers) min_dwarfs = max_dwarfs = 0; - if (df::enums::unit_labor::FISH == labor && !has_fishery) + if (unit_labor::FISH == labor && !has_fishery) min_dwarfs = max_dwarfs = 0; bool want_idle_dwarf = true; @@ -956,15 +957,15 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { df::building *build = world->buildings.all[i]; auto type = build->getType(); - if (df::enums::building_type::Workshop == type) + if (building_type::Workshop == type) { - auto subType = build->getSubtype(); - if (df::enums::workshop_type::Butchers == subType) + df::workshop_type subType = (df::workshop_type)build->getSubtype(); + if (workshop_type::Butchers == subType) has_butchers = true; - if (df::enums::workshop_type::Fishery == subType) + if (workshop_type::Fishery == subType) has_fishery = true; } - else if (df::enums::building_type::TradeDepot == type) + else if (building_type::TradeDepot == type) { df::building_tradedepotst* depot = (df::building_tradedepotst*) build; trader_requested = depot->trade_flags.bits.trader_requested; @@ -978,11 +979,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) } } - for (int i = 0; i < world->units.all.size(); ++i) + for (int i = 0; i < world->units.active.size(); ++i) { - df::unit* cre = world->units.all[i]; - if (cre->race == race && cre->civ_id == civ && !cre->flags1.bits.marauder && !cre->flags1.bits.diplomat && !cre->flags1.bits.merchant && - !cre->flags1.bits.dead && !cre->flags1.bits.forest) + df::unit* cre = world->units.active[i]; + if (Units::isCitizen(cre)) { if (cre->burrows.size() > 0) continue; // dwarfs assigned to burrows are skipped entirely @@ -1003,9 +1003,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { dwarf_info[dwarf].single_labor = -1; -// assert(dwarfs[dwarf]->status.souls.size() > 0); -// assert fails can cause DF to crash, so don't do that - if (dwarfs[dwarf]->status.souls.size() <= 0) continue; @@ -1076,7 +1073,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) - if (skill_class != df::enums::job_skill_class::Normal && skill_class != df::enums::job_skill_class::Medical) + if (skill_class != job_skill_class::Normal && skill_class != job_skill_class::Medical) continue; if (dwarf_info[dwarf].highest_skill < skill_level) @@ -1093,16 +1090,11 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty; - for (int labor = ENUM_FIRST_ITEM(unit_labor); labor <= ENUM_LAST_ITEM(unit_labor); labor++) + FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == df::enums::unit_labor::NONE) + if (labor == unit_labor::NONE) continue; - /* - assert(labor >= 0); - assert(labor < ARRAY_COUNT(labor_infos)); - */ - if (labor_infos[labor].is_exclusive && dwarfs[dwarf]->status.labors[labor]) dwarf_info[dwarf].mastery_penalty -= 100; } @@ -1120,15 +1112,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) for (auto p = dwarfs[dwarf]->status.misc_traits.begin(); p < dwarfs[dwarf]->status.misc_traits.end(); p++) { - // 7 / 0x7 = Newly arrived migrant, will not work yet - // 17 / 0x11 = On break - if ((*p)->id == 0x07 || (*p)->id == 0x11) + if ((*p)->id == misc_trait_type::Migrant || (*p)->id == misc_trait_type::OnBreak) is_on_break = true; } - if (dwarfs[dwarf]->profession == df::enums::profession::BABY || - dwarfs[dwarf]->profession == df::enums::profession::CHILD || - dwarfs[dwarf]->profession == df::enums::profession::DRUNK) + if (dwarfs[dwarf]->profession == profession::BABY || + dwarfs[dwarf]->profession == profession::CHILD || + dwarfs[dwarf]->profession == profession::DRUNK) { dwarf_info[dwarf].state = CHILD; } @@ -1146,18 +1136,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) else { int job = dwarfs[dwarf]->job.current_job->job_type; - - /* - assert(job >= 0); - assert(job < ARRAY_COUNT(dwarf_states)); - */ - if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) - dwarf_info[dwarf].state = dwarf_states[job]; - else - { - out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); - dwarf_info[dwarf].state = OTHER; - } + if (job >= 0 && job < ARRAY_COUNT(dwarf_states)) + dwarf_info[dwarf].state = dwarf_states[job]; + else + { + out.print("Dwarf %i \"%s\" has unknown job %i\n", dwarf, dwarfs[dwarf]->name.first_name.c_str(), job); + dwarf_info[dwarf].state = OTHER; + } } state_count[dwarf_info[dwarf].state]++; @@ -1170,14 +1155,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == df::enums::unit_labor::NONE) + if (labor == unit_labor::NONE) continue; - /* - assert(labor >= 0); - assert(labor < ARRAY_COUNT(labor_infos)); - */ - labor_infos[labor].active_dwarfs = 0; labors.push_back(labor); @@ -1217,11 +1197,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { auto labor = *lp; - /* - assert(labor >= 0); - assert(labor < ARRAY_COUNT(labor_infos)); - */ - assign_labor(labor, n_dwarfs, dwarf_info, trader_requested, dwarfs, has_butchers, has_fishery, out); } @@ -1241,7 +1216,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == df::enums::unit_labor::NONE) + if (labor == unit_labor::NONE) continue; if (labor_infos[labor].mode() != HAULERS) continue; @@ -1264,14 +1239,9 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == df::enums::unit_labor::NONE) + if (labor == unit_labor::NONE) continue; - /* - assert(labor >= 0); - assert(labor < ARRAY_COUNT(labor_infos)); - */ - if (labor_infos[labor].mode() != HAULERS) continue; @@ -1311,7 +1281,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) return CR_OK; } -void print_labor (df::enums::unit_labor::unit_labor labor, color_ostream &out) +void print_labor (df::unit_labor labor, color_ostream &out) { string labor_name = ENUM_KEY_STR(unit_labor, labor); out << labor_name << ": "; @@ -1358,7 +1328,6 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_OK; } - else if (parameters.size() == 2 && parameters[0] == "haulpct") { if (!enable_autolabor) @@ -1371,15 +1340,15 @@ command_result autolabor (color_ostream &out, std::vector & parame hauler_pct = pct; return CR_OK; } - else if (parameters.size() == 2 || parameters.size() == 3) { - + else if (parameters.size() == 2 || parameters.size() == 3) + { if (!enable_autolabor) { out << "Error: The plugin is not enabled." << endl; return CR_FAILURE; } - df::enums::unit_labor::unit_labor labor = df::enums::unit_labor::NONE; + df::unit_labor labor = unit_labor::NONE; FOR_ENUM_ITEMS(unit_labor, test_labor) { @@ -1387,7 +1356,7 @@ command_result autolabor (color_ostream &out, std::vector & parame labor = test_labor; } - if (labor == df::enums::unit_labor::NONE) + if (labor == unit_labor::NONE) { out.printerr("Could not find labor %s.\n", parameters[0].c_str()); return CR_WRONG_USAGE; @@ -1430,7 +1399,8 @@ command_result autolabor (color_ostream &out, std::vector & parame return CR_OK; } - else if (parameters.size() == 1 && parameters[0] == "reset-all") { + else if (parameters.size() == 1 && parameters[0] == "reset-all") + { if (!enable_autolabor) { out << "Error: The plugin is not enabled." << endl; @@ -1439,12 +1409,13 @@ command_result autolabor (color_ostream &out, std::vector & parame for (int i = 0; i < labor_infos.size(); i++) { - reset_labor((df::enums::unit_labor::unit_labor) i); + reset_labor((df::unit_labor) i); } out << "All labors reset." << endl; return CR_OK; } - else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") { + else if (parameters.size() == 1 && parameters[0] == "list" || parameters[0] == "status") + { if (!enable_autolabor) { out << "Error: The plugin is not enabled." << endl; @@ -1467,7 +1438,7 @@ command_result autolabor (color_ostream &out, std::vector & parame { FOR_ENUM_ITEMS(unit_labor, labor) { - if (labor == df::enums::unit_labor::NONE) + if (labor == unit_labor::NONE) continue; print_labor(labor, out); @@ -1571,7 +1542,7 @@ static int stockcheck(color_ostream &out, vector & parameters) { df::building *build = world->buildings.all[i]; auto type = build->getType(); - if (df::enums::building_type::Stockpile == type) + if (building_type::Stockpile == type) { df::building_stockpilest *sp = virtual_cast(build); StockpileInfo *spi = new StockpileInfo(sp); @@ -1580,7 +1551,7 @@ static int stockcheck(color_ostream &out, vector & parameters) } - std::vector &items = world->items.other[df::enums::items_other_id::ANY_FREE]; + std::vector &items = world->items.other[items_other_id::ANY_FREE]; // Precompute a bitmask with the bad flags df::item_flags bad_flags; @@ -1602,13 +1573,13 @@ static int stockcheck(color_ostream &out, vector & parameters) // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG df::item_type typ = item->getType(); - if (typ != df::enums::item_type::MEAT && - typ != df::enums::item_type::FISH && - typ != df::enums::item_type::FISH_RAW && - typ != df::enums::item_type::PLANT && - typ != df::enums::item_type::CHEESE && - typ != df::enums::item_type::FOOD && - typ != df::enums::item_type::EGG) + if (typ != item_type::MEAT && + typ != item_type::FISH && + typ != item_type::FISH_RAW && + typ != item_type::PLANT && + typ != item_type::CHEESE && + typ != item_type::FOOD && + typ != item_type::EGG) continue; df::item *container = 0; @@ -1673,11 +1644,11 @@ static int stockcheck(color_ostream &out, vector & parameters) if (building) { df::building_type btype = building->getType(); - if (btype == df::enums::building_type::TradeDepot || - btype == df::enums::building_type::Wagon) + if (btype == building_type::TradeDepot || + btype == building_type::Wagon) continue; // items in trade depot or the embark wagon do not rot - if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox) + if (typ == item_type::EGG && btype ==building_type::NestBox) continue; // eggs in nest box do not rot } diff --git a/plugins/cleanowned.cpp b/plugins/cleanowned.cpp index a9d461d2f..cd01fd616 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -116,14 +116,14 @@ command_result df_cleanowned (color_ostream &out, vector & parameters) } else if (item->flags.bits.on_ground) { - int32_t type = item->getType(); - if((df::enums::item_type::item_type)type == item_type::MEAT || - (df::enums::item_type::item_type)type == item_type::FISH || - (df::enums::item_type::item_type)type == item_type::VERMIN || - (df::enums::item_type::item_type)type == item_type::PET || - (df::enums::item_type::item_type)type == item_type::PLANT || - (df::enums::item_type::item_type)type == item_type::CHEESE || - (df::enums::item_type::item_type)type == item_type::FOOD + df::item_type type = item->getType(); + if(type == item_type::MEAT || + type == item_type::FISH || + type == item_type::VERMIN || + type == item_type::PET || + type == item_type::PLANT || + type == item_type::CHEESE || + type == item_type::FOOD ) { confiscate = true; From c927623050708da1276e109899549b6f2577180f Mon Sep 17 00:00:00 2001 From: Quietust Date: Thu, 13 Sep 2012 15:42:51 -0500 Subject: [PATCH 29/48] Rework handling of column positions, and insert a new "Happiness" column --- plugins/manipulator.cpp | 157 ++++++++++++++++++++++++++++------------ 1 file changed, 111 insertions(+), 46 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 429bcc173..f40969655 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -252,6 +252,7 @@ struct UnitInfo enum altsort_mode { ALTSORT_NAME, ALTSORT_PROFESSION, + ALTSORT_HAPPINESS, ALTSORT_MAX }; @@ -275,6 +276,14 @@ bool sortByProfession (const UnitInfo *d1, const UnitInfo *d2) return (d1->profession < d2->profession); } +bool sortByHappiness (const UnitInfo *d1, const UnitInfo *d2) +{ + if (descending) + return (d1->unit->status.happiness > d2->unit->status.happiness); + else + return (d1->unit->status.happiness < d2->unit->status.happiness); +} + bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) { if (sort_skill != job_skill::NONE) @@ -310,6 +319,14 @@ bool sortBySkill (const UnitInfo *d1, const UnitInfo *d2) return sortByName(d1, d2); } +enum display_columns { + DISP_COLUMN_HAPPINESS, + DISP_COLUMN_NAME, + DISP_COLUMN_PROFESSION, + DISP_COLUMN_LABORS, + DISP_COLUMN_MAX, +}; + class viewscreen_unitlaborsst : public dfhack_viewscreen { public: void feed(set *events); @@ -328,10 +345,11 @@ protected: vector units; altsort_mode altsort; - int first_row, sel_row; + int first_row, sel_row, num_rows; int first_column, sel_column; - int height, name_width, prof_width, labors_width; + int col_widths[DISP_COLUMN_MAX]; + int col_offsets[DISP_COLUMN_MAX]; void calcSize (); }; @@ -374,34 +392,52 @@ viewscreen_unitlaborsst::viewscreen_unitlaborsst(vector &src) void viewscreen_unitlaborsst::calcSize() { - height = gps->dimy - 10; - if (height > units.size()) - height = units.size(); - - name_width = prof_width = labors_width = 0; - for (int i = 4; i < gps->dimx; i++) + num_rows = gps->dimy - 10; + if (num_rows > units.size()) + num_rows = units.size(); + + int num_columns = gps->dimx - DISP_COLUMN_MAX - 1; + for (int i = 0; i < DISP_COLUMN_MAX; i++) + col_widths[i] = 0; + while (num_columns > 0) { - // 20% for Name, 20% for Profession, 60% for Labors - switch ((i - 4) % 5) + num_columns--; + // need at least 4 digits for happiness + if (col_widths[DISP_COLUMN_HAPPINESS] < 4) + { + col_widths[DISP_COLUMN_HAPPINESS]++; + continue; + } + // of remaining, 20% for Name, 20% for Profession, 60% for Labors + switch (num_columns % 5) { case 0: case 2: case 4: - labors_width++; + col_widths[DISP_COLUMN_LABORS]++; break; case 1: - name_width++; + col_widths[DISP_COLUMN_NAME]++; break; case 3: - prof_width++; + col_widths[DISP_COLUMN_PROFESSION]++; break; } } - while (labors_width > NUM_COLUMNS) + + while (col_widths[DISP_COLUMN_LABORS] > NUM_COLUMNS) + { + col_widths[DISP_COLUMN_LABORS]--; + if (col_widths[DISP_COLUMN_LABORS] & 1) + col_widths[DISP_COLUMN_NAME]++; + else + col_widths[DISP_COLUMN_PROFESSION]++; + } + + for (int i = 0; i < DISP_COLUMN_MAX; i++) { - if (labors_width & 1) - name_width++; + if (i == 0) + col_offsets[i] = 1; else - prof_width++; - labors_width--; + col_offsets[i] = col_offsets[i - 1] + col_widths[i - 1] + 1; } // don't adjust scroll position immediately after the window opened @@ -409,20 +445,20 @@ void viewscreen_unitlaborsst::calcSize() return; // if the window grows vertically, scroll upward to eliminate blank rows from the bottom - if (first_row > units.size() - height) - first_row = units.size() - height; + if (first_row > units.size() - num_rows) + first_row = units.size() - num_rows; // if it shrinks vertically, scroll downward to keep the cursor visible - if (first_row < sel_row - height + 1) - first_row = sel_row - height + 1; + if (first_row < sel_row - num_rows + 1) + first_row = sel_row - num_rows + 1; // if the window grows horizontally, scroll to the left to eliminate blank columns from the right - if (first_column > NUM_COLUMNS - labors_width) - first_column = NUM_COLUMNS - labors_width; + if (first_column > NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS]) + first_column = NUM_COLUMNS - col_widths[DISP_COLUMN_LABORS]; // if it shrinks horizontally, scroll to the right to keep the cursor visible - if (first_column < sel_column - labors_width + 1) - first_column = sel_column - labors_width + 1; + if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1) + first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1; } void viewscreen_unitlaborsst::feed(set *events) @@ -453,8 +489,8 @@ void viewscreen_unitlaborsst::feed(set *events) if (sel_row < first_row) first_row = sel_row; - if (first_row < sel_row - height + 1) - first_row = sel_row - height + 1; + if (first_row < sel_row - num_rows + 1) + first_row = sel_row - num_rows + 1; if (events->count(interface_key::CURSOR_LEFT) || events->count(interface_key::CURSOR_UPLEFT) || events->count(interface_key::CURSOR_DOWNLEFT)) sel_column--; @@ -489,8 +525,8 @@ void viewscreen_unitlaborsst::feed(set *events) if (sel_column < first_column) first_column = sel_column; - if (first_column < sel_column - labors_width + 1) - first_column = sel_column - labors_width + 1; + if (first_column < sel_column - col_widths[DISP_COLUMN_LABORS] + 1) + first_column = sel_column - col_widths[DISP_COLUMN_LABORS] + 1; UnitInfo *cur = units[sel_row]; if (events->count(interface_key::SELECT) && (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE)) @@ -556,6 +592,9 @@ void viewscreen_unitlaborsst::feed(set *events) case ALTSORT_PROFESSION: std::sort(units.begin(), units.end(), sortByProfession); break; + case ALTSORT_HAPPINESS: + std::sort(units.begin(), units.end(), sortByHappiness); + break; } } if (events->count(interface_key::CHANGETAB)) @@ -566,6 +605,9 @@ void viewscreen_unitlaborsst::feed(set *events) altsort = ALTSORT_PROFESSION; break; case ALTSORT_PROFESSION: + altsort = ALTSORT_HAPPINESS; + break; + case ALTSORT_HAPPINESS: altsort = ALTSORT_NAME; break; } @@ -605,7 +647,7 @@ void viewscreen_unitlaborsst::render() Screen::clear(); Screen::drawBorder(" Dwarf Manipulator - Manage Labors "); - for (int col = 0; col < labors_width; col++) + for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++) { int col_offset = col + first_column; if (col_offset >= NUM_COLUMNS) @@ -620,8 +662,8 @@ void viewscreen_unitlaborsst::render() bg = 7; } - Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 1); - Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 2); + Screen::paintTile(Screen::Pen(columns[col_offset].label[0], fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 1); + Screen::paintTile(Screen::Pen(columns[col_offset].label[1], fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 2); df::profession profession = columns[col_offset].profession; if ((profession != profession::NONE) && (ui->race_id != -1)) { @@ -630,11 +672,11 @@ void viewscreen_unitlaborsst::render() Screen::Pen(' ', fg, 0, graphics.profession_add_color[creature_graphics_role::DEFAULT][profession], graphics.profession_texpos[creature_graphics_role::DEFAULT][profession]), - 1 + name_width + 1 + prof_width + 1 + col, 3); + col_offsets[DISP_COLUMN_LABORS] + col, 3); } } - for (int row = 0; row < height; row++) + for (int row = 0; row < num_rows; row++) { int row_offset = row + first_row; if (row_offset >= units.size()) @@ -643,6 +685,26 @@ void viewscreen_unitlaborsst::render() UnitInfo *cur = units[row_offset]; df::unit *unit = cur->unit; int8_t fg = 15, bg = 0; + + int happy = cur->unit->status.happiness; + string happiness = stl_sprintf("%4i", happy); + if (happy == 0) // miserable + fg = 13; // 5:1 + else if (happy <= 25) // very unhappy + fg = 12; // 4:1 + else if (happy <= 50) // unhappy + fg = 4; // 4:0 + else if (happy < 75) // fine + fg = 14; // 6:1 + else if (happy < 125) // quite content + fg = 6; // 6:0 + else if (happy < 150) // happy + fg = 2; // 2:0 + else // ecstatic + fg = 10; // 2:1 + Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_HAPPINESS], 4 + row, happiness); + + fg = 15; if (row_offset == sel_row) { fg = 0; @@ -650,18 +712,18 @@ void viewscreen_unitlaborsst::render() } string name = cur->name; - name.resize(name_width); - Screen::paintString(Screen::Pen(' ', fg, bg), 1, 4 + row, name); + name.resize(col_widths[DISP_COLUMN_NAME]); + Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_NAME], 4 + row, name); string profession = cur->profession; - profession.resize(prof_width); + profession.resize(col_widths[DISP_COLUMN_PROFESSION]); fg = cur->color; bg = 0; - Screen::paintString(Screen::Pen(' ', fg, bg), 1 + name_width + 1, 4 + row, profession); + Screen::paintString(Screen::Pen(' ', fg, bg), col_offsets[DISP_COLUMN_PROFESSION], 4 + row, profession); // Print unit's skills and labor assignments - for (int col = 0; col < labors_width; col++) + for (int col = 0; col < col_widths[DISP_COLUMN_LABORS]; col++) { int col_offset = col + first_column; fg = 15; @@ -693,7 +755,7 @@ void viewscreen_unitlaborsst::render() } else bg = 4; - Screen::paintTile(Screen::Pen(c, fg, bg), 1 + name_width + 1 + prof_width + 1 + col, 4 + row); + Screen::paintTile(Screen::Pen(c, fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 4 + row); } } @@ -703,17 +765,17 @@ void viewscreen_unitlaborsst::render() { df::unit *unit = cur->unit; int x = 1; - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->transname); + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->transname); x += cur->transname.length(); if (cur->transname.length()) { - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ", "); + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ", "); x += 2; } - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, cur->profession); + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, cur->profession); x += cur->profession.length(); - Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + height + 2, ": "); + Screen::paintString(Screen::Pen(' ', 15, 0), x, 3 + num_rows + 2, ": "); x += 2; string str; @@ -740,7 +802,7 @@ void viewscreen_unitlaborsst::render() else str = stl_sprintf("Not %s (0/500)", ENUM_ATTR_STR(job_skill, caption_noun, columns[sel_column].skill)); } - Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + height + 2, str); + Screen::paintString(Screen::Pen(' ', 9, 0), x, 3 + num_rows + 2, str); canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE); } @@ -779,6 +841,9 @@ void viewscreen_unitlaborsst::render() case ALTSORT_PROFESSION: OutputString(15, x, gps->dimy - 2, "Profession"); break; + case ALTSORT_HAPPINESS: + OutputString(15, x, gps->dimy - 2, "Happiness"); + break; default: OutputString(15, x, gps->dimy - 2, "Unknown"); break; From aaf5d181bd2a54e378c91b7a02a558126c4d5105 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 14 Sep 2012 12:14:36 +0400 Subject: [PATCH 30/48] Add yet one more performance-oriented tweak for temperature updates. --- dfhack.init-example | 13 +++++--- plugins/tweak.cpp | 61 +++++++++++++++++++++++++++++++++++++ scripts/fix/stable-temp.lua | 33 +++++++++++++++----- scripts/setfps.lua | 10 ++++++ 4 files changed, 105 insertions(+), 12 deletions(-) create mode 100644 scripts/setfps.lua diff --git a/dfhack.init-example b/dfhack.init-example index 5af527099..a9b69b826 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -51,7 +51,7 @@ keybinding add Shift-G "job-material GLASS_GREEN" keybinding add Ctrl-M@dwarfmode/QueryBuilding/Some gui/mechanisms # browse rooms of same owner -keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work +keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list # interface for the liquids plugin keybinding add Alt-L@dwarfmode/LookAround gui/liquids @@ -59,9 +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 -################### -# UI logic tweaks # -################### +############################ +# UI and game logic tweaks # +############################ # stabilize the cursor of dwarfmode when switching menus tweak stable-cursor @@ -74,3 +74,8 @@ tweak readable-build-plate # improve FPS by squashing endless item temperature update loops tweak stable-temp + +# speed up items reaching temp equilibrium with environment by +# 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 diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index fbea30231..de7695fb3 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -89,6 +89,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector 0 && temp != temperature && max_heat_ticks > 0) + { + int spec = getSpecHeat(); + if (spec != 60001) + rate_mult = std::max(map_temp_mult, spec/max_heat_ticks/abs(temp - temperature)); + } + + return INTERPOSE_NEXT(updateTemperature)(temp, local, contained, adjust, rate_mult); + } + + DEFINE_VMETHOD_INTERPOSE(bool, adjustTemperature, (uint16_t temp, int32_t rate_mult)) + { + if (map_temp_mult > 0) + rate_mult = map_temp_mult; + + return INTERPOSE_NEXT(adjustTemperature)(temp, rate_mult); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTempFromMap); +IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, updateTemperature); +IMPLEMENT_VMETHOD_INTERPOSE(fast_heat_hook, adjustTemperature); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -430,6 +480,17 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, adjustTemperature), parameters); enable_hook(out, INTERPOSE_HOOK(stable_temp_hook, updateContaminants), parameters); } + else if (cmd == "fast-heat") + { + if (parameters.size() < 2) + return CR_WRONG_USAGE; + max_heat_ticks = atoi(parameters[1].c_str()); + if (max_heat_ticks <= 0) + parameters[1] = "disable"; + enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTempFromMap), parameters); + enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTemperature), parameters); + enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, adjustTemperature), parameters); + } else return CR_WRONG_USAGE; diff --git a/scripts/fix/stable-temp.lua b/scripts/fix/stable-temp.lua index d06d0fcce..27a88ef7b 100644 --- a/scripts/fix/stable-temp.lua +++ b/scripts/fix/stable-temp.lua @@ -1,5 +1,9 @@ -- Reset item temperature to the value of their tile. +local args = {...} + +local apply = (args[1] == 'apply') + local count = 0 local types = {} @@ -9,13 +13,16 @@ local function update_temp(item,btemp) local tid = item:getType() types[tid] = (types[tid] or 0) + 1 end - item.temperature = btemp - item.temperature_fraction = 0 - if item.contaminants then - for _,c in ipairs(item.contaminants) do - c.temperature = btemp - c.temperature_fraction = 0 + if apply then + item.temperature = btemp + item.temperature_fraction = 0 + + if item.contaminants then + for _,c in ipairs(item.contaminants) do + c.temperature = btemp + c.temperature_fraction = 0 + end end end @@ -23,7 +30,9 @@ local function update_temp(item,btemp) update_temp(sub,btemp) end - item:checkTemperatureDamage() + if apply then + item:checkTemperatureDamage() + end end local last_frame = df.global.world.frame_counter-1 @@ -39,7 +48,11 @@ for _,item in ipairs(df.global.world.items.all) do end end -print('Items updated: '..count) +if apply then + print('Items updated: '..count) +else + print('Items not in equilibrium: '..count) +end local tlist = {} for k,_ in pairs(types) do tlist[#tlist+1] = k end @@ -47,3 +60,7 @@ table.sort(tlist, function(a,b) return types[a] > types[b] end) for _,k in ipairs(tlist) do print(' '..df.item_type[k]..':', types[k]) end + +if not apply then + print("Use 'fix/stable-temp apply' to force-change temperature.") +end diff --git a/scripts/setfps.lua b/scripts/setfps.lua new file mode 100644 index 000000000..690f82702 --- /dev/null +++ b/scripts/setfps.lua @@ -0,0 +1,10 @@ +-- Set the FPS cap at runtime. + +local cap = ... +local capnum = tonumber(cap) + +if not capnum or capnum < 1 then + qerror('Invalid FPS cap value: '..cap) +end + +df.global.enabler.fps = capnum From d22591e24050b8200899a0de8854c39efff9effd Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 14 Sep 2012 12:35:55 +0400 Subject: [PATCH 31/48] Fix a file descriptor leak and a crash in linux getMemRanges. --- library/Process-linux.cpp | 5 +++++ plugins/tweak.cpp | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 4a66470f9..1fecbab78 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -127,6 +127,9 @@ void Process::getMemRanges( vector & ranges ) char permissions[5]; // r/-, w/-, x/-, p/s, 0 FILE *mapFile = ::fopen("/proc/self/maps", "r"); + if (!mapFile) + return; + size_t start, end, offset, device1, device2, node; while (fgets(buffer, 1024, mapFile)) @@ -148,6 +151,8 @@ void Process::getMemRanges( vector & ranges ) temp.valid = true; ranges.push_back(temp); } + + fclose(mapFile); } uint32_t Process::getBase() diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index de7695fb3..bebc346c5 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -89,10 +89,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector \n" " Further improves temperature updates by ensuring that 1 degree of\n" " item temperature is crossed in no more than specified number of frames\n" - " when updating from the environment temperature.\n" + " when updating from the environment temperature. Use 0 to disable.\n" )); return CR_OK; } From 68bfc63b7d0804ac5229623480e47f297783f502 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 14 Sep 2012 14:48:46 +0400 Subject: [PATCH 32/48] Add a NEWS file intended for incremental construction of release notes. --- NEWS | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 NEWS diff --git a/NEWS b/NEWS new file mode 100644 index 000000000..43707f9a7 --- /dev/null +++ b/NEWS @@ -0,0 +1,61 @@ +DFHack v0.34.11-r2 (UNRELEASED) + + Internals: + - full support for Mac OS X. + - a plugin that adds scripting in ruby. + - support for interposing virtual methods in DF from C++ plugins. + - support for creating new interface screens from C++ and lua. + - added various other API functions. + Notable bugfixes: + - better terminal reset after exit on linux. + - seedwatch now works on reclaim. + - the sort plugin won't crash on cages anymore. + Misc improvements: + - autodump: can move items to any walkable tile, not just floors. + - stripcaged: by default keep armor, new dumparmor option. + - zone: allow non-domesticated birds in nestboxes. + - workflow: quality range in constraints. + - cleanplants: new command to remove rain water from plants. + - liquids: can paint permaflow, i.e. what makes rivers power water wheels. + - prospect: pre-embark prospector accounts for caves & magma sea in its estimate. + - rename: supports renaming stockpiles, workshops, traps, siege engines. + New tweaks: + - tweak stable-cursor: keeps exact cursor position between d/k/t/q/v etc menus. + - tweak patrol-duty: makes Train orders reduce patrol timer, like the binary patch does. + - 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. + New scripts: + - fixnaked: removes thoughts about nakedness. + - setfps: set FPS cap at runtime, in case you want slow motion or speed-up. + - fix/population-cap: run after every migrant wave to prevent exceeding the cap. + - fix/stable-temp: counts items with temperature updates; does instant one-shot stable-temp. + New GUI scripts: + - gui/mechanisms: browse mechanism links of the current building. + - gui/room-list: browse other rooms owned by the unit when assigning one. + - gui/liquids: a GUI front-end for the liquids plugin. + - gui/rename: renaming stockpiles, workshops and units via an in-game dialog. + - gui/power-meter: front-end for the Power Meter plugin. + - gui/siege-engine: front-end for the Siege Engine plugin. + Autolabor plugin: + - can set nonidle hauler percentage. + - broker excluded from all labors when needed at depot. + - likewise, anybody with a scheduled diplomat meeting. + 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 + 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. + 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. + New Siege Engine plugin (INCOMPLETE): + 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. + From 24772f4dbcfcfbf0ac3d29f72d3bda19566d8530 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 14 Sep 2012 18:49:02 +0400 Subject: [PATCH 33/48] Add an api function for destroying items. --- LUA_API.rst | 4 ++++ Lua API.html | 3 +++ library/LuaApi.cpp | 7 +++++++ library/include/modules/Items.h | 3 +++ library/modules/Items.cpp | 32 ++++++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+) diff --git a/LUA_API.rst b/LUA_API.rst index 1ffdada0a..c5f9a1c58 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -999,6 +999,10 @@ Items module Move the item to the unit inventory. Returns *false* if impossible. +* ``dfhack.items.remove(item[, no_uncat])`` + + Removes the item, and marks it for garbage collection unless ``no_uncat`` is true. + * ``dfhack.items.makeProjectile(item)`` Turns the item into a projectile, and returns the new object, or *nil* if impossible. diff --git a/Lua API.html b/Lua API.html index 168f7dcc6..07f038bc4 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1213,6 +1213,9 @@ Returns false in case of error.

  • dfhack.items.moveToInventory(item,unit,use_mode,body_part)

    Move the item to the unit inventory. Returns false if impossible.

  • +
  • dfhack.items.remove(item[, no_uncat])

    +

    Removes the item, and marks it for garbage collection unless no_uncat is true.

    +
  • dfhack.items.makeProjectile(item)

    Turns the item into a projectile, and returns the new object, or nil if impossible.

  • diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f69fa7a1b..f8497569e 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -886,6 +886,12 @@ static bool items_moveToInventory return Items::moveToInventory(mc, item, unit, mode, body_part); } +static bool items_remove(df::item *item, bool no_uncat) +{ + MapExtras::MapCache mc; + return Items::remove(mc, item, no_uncat); +} + static df::proj_itemst *items_makeProjectile(df::item *item) { MapExtras::MapCache mc; @@ -904,6 +910,7 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPN(moveToBuilding, items_moveToBuilding), WRAPN(moveToInventory, items_moveToInventory), WRAPN(makeProjectile, items_makeProjectile), + WRAPN(remove, items_remove), { NULL, NULL } }; diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 7493d22fc..81c8e1285 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -157,6 +157,9 @@ DFHACK_EXPORT bool moveToBuilding(MapExtras::MapCache &mc, df::item *item, df::b DFHACK_EXPORT bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *unit, df::unit_inventory_item::T_mode mode = df::unit_inventory_item::Carried, int body_part = -1); +/// Makes the item removed and marked for garbage collection +DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat = false); + /// Detaches the items from its current location and turns it into a projectile DFHACK_EXPORT df::proj_itemst *makeProjectile(MapExtras::MapCache &mc, df::item *item); } diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 751797f06..b8c697a48 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -730,6 +730,18 @@ static bool detachItem(MapExtras::MapCache &mc, df::item *item) item->flags.bits.in_inventory = false; return true; } + else if (item->flags.bits.removed) + { + item->flags.bits.removed = false; + + if (item->flags.bits.garbage_collect) + { + item->flags.bits.garbage_collect = false; + item->categorize(true); + } + + return true; + } else return false; } @@ -871,6 +883,26 @@ bool DFHack::Items::moveToInventory( return true; } +bool Items::remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat) +{ + CHECK_NULL_POINTER(item); + + auto pos = getPosition(item); + + if (!detachItem(mc, item)) + return false; + + if (pos.isValid()) + item->pos = pos; + + if (!no_uncat) + item->uncategorize(); + + item->flags.bits.removed = true; + item->flags.bits.garbage_collect = !no_uncat; + return true; +} + df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item) { CHECK_NULL_POINTER(item); From 811c096c0ecdcde1d0d19be8e4996996bde27995 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 14 Sep 2012 20:22:49 +0400 Subject: [PATCH 34/48] Vaporize liquids from barrels, and destroy bin contents in siege engine. --- library/xml | 2 +- plugins/devel/dumpmats.cpp | 53 +++++--------------- plugins/devel/siege-engine.cpp | 89 ++++++++++++++++++++++++++++++++-- 3 files changed, 99 insertions(+), 45 deletions(-) diff --git a/library/xml b/library/xml index 2bc8fbdf7..ee2b63a8f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 2bc8fbdf71143398817d31e06e169a01cce37c50 +Subproject commit ee2b63a8ffdbce66489148ca2a9803db1d0b9090 diff --git a/plugins/devel/dumpmats.cpp b/plugins/devel/dumpmats.cpp index ba888e7cf..0af1fce50 100644 --- a/plugins/devel/dumpmats.cpp +++ b/plugins/devel/dumpmats.cpp @@ -11,6 +11,7 @@ #include "df/matter_state.h" #include "df/descriptor_color.h" #include "df/item_type.h" +#include "df/strain_type.h" using std::string; using std::vector; @@ -195,47 +196,17 @@ command_result df_dumpmats (color_ostream &out, vector ¶meters) if (mat->molar_mass != 0xFBBC7818) out.print("\t[MOLAR_MASS:%i]\n", mat->molar_mass); - if (mat->strength.impact_yield != 10000) - out.print("\t[IMPACT_YIELD:%i]\n", mat->strength.impact_yield); - if (mat->strength.impact_fracture != 10000) - out.print("\t[IMPACT_FRACTURE:%i]\n", mat->strength.impact_fracture); - if (mat->strength.impact_strain_at_yield != 0) - out.print("\t[IMPACT_STRAIN_AT_YIELD:%i]\n", mat->strength.impact_strain_at_yield); - - if (mat->strength.compressive_yield != 10000) - out.print("\t[COMPRESSIVE_YIELD:%i]\n", mat->strength.compressive_yield); - if (mat->strength.compressive_fracture != 10000) - out.print("\t[COMPRESSIVE_FRACTURE:%i]\n", mat->strength.compressive_fracture); - if (mat->strength.compressive_strain_at_yield != 0) - out.print("\t[COMPRESSIVE_STRAIN_AT_YIELD:%i]\n", mat->strength.compressive_strain_at_yield); - - if (mat->strength.tensile_yield != 10000) - out.print("\t[TENSILE_YIELD:%i]\n", mat->strength.tensile_yield); - if (mat->strength.tensile_fracture != 10000) - out.print("\t[TENSILE_FRACTURE:%i]\n", mat->strength.tensile_fracture); - if (mat->strength.tensile_strain_at_yield != 0) - out.print("\t[TENSILE_STRAIN_AT_YIELD:%i]\n", mat->strength.tensile_strain_at_yield); - - if (mat->strength.torsion_yield != 10000) - out.print("\t[TORSION_YIELD:%i]\n", mat->strength.torsion_yield); - if (mat->strength.torsion_fracture != 10000) - out.print("\t[TORSION_FRACTURE:%i]\n", mat->strength.torsion_fracture); - if (mat->strength.torsion_strain_at_yield != 0) - out.print("\t[TORSION_STRAIN_AT_YIELD:%i]\n", mat->strength.torsion_strain_at_yield); - - if (mat->strength.shear_yield != 10000) - out.print("\t[SHEAR_YIELD:%i]\n", mat->strength.shear_yield); - if (mat->strength.shear_fracture != 10000) - out.print("\t[SHEAR_FRACTURE:%i]\n", mat->strength.shear_fracture); - if (mat->strength.shear_strain_at_yield != 0) - out.print("\t[SHEAR_STRAIN_AT_YIELD:%i]\n", mat->strength.shear_strain_at_yield); - - if (mat->strength.bending_yield != 10000) - out.print("\t[BENDING_YIELD:%i]\n", mat->strength.bending_yield); - if (mat->strength.bending_fracture != 10000) - out.print("\t[BENDING_FRACTURE:%i]\n", mat->strength.bending_fracture); - if (mat->strength.bending_strain_at_yield != 0) - out.print("\t[BENDING_STRAIN_AT_YIELD:%i]\n", mat->strength.bending_strain_at_yield); + FOR_ENUM_ITEMS(strain_type, strain) + { + auto name = ENUM_KEY_STR(strain_type,strain); + + if (mat->strength.yield[strain] != 10000) + out.print("\t[%s_YIELD:%i]\n", name.c_str(), mat->strength.yield[strain]); + if (mat->strength.fracture[strain] != 10000) + out.print("\t[%s_FRACTURE:%i]\n", name.c_str(), mat->strength.fracture[strain]); + if (mat->strength.strain_at_yield[strain] != 0) + out.print("\t[%s_STRAIN_AT_YIELD:%i]\n", name.c_str(), mat->strength.strain_at_yield[strain]); + } if (mat->strength.max_edge != 0) out.print("\t[MAX_EDGE:%i]\n", mat->strength.max_edge); diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index a41bfe5f7..d1e34ced9 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -45,11 +46,14 @@ #include "df/unit_misc_trait.h" #include "df/job.h" #include "df/job_item.h" -#include "df/item.h" +#include "df/item_actual.h" #include "df/items_other_id.h" #include "df/building_stockpilest.h" #include "df/stockpile_links.h" #include "df/workshop_profile.h" +#include "df/strain_type.h" +#include "df/material.h" +#include "df/flow_type.h" #include "MiscUtils.h" @@ -162,6 +166,63 @@ static void random_direction(float &x, float &y, float &z) z = 1.0f - 2.0f*d; } +static const int WEAR_TICKS = 806400; + +static bool apply_impact_damage(df::item *item, int minv, int maxv) +{ + MaterialInfo info(item); + if (!info.isValid()) + { + item->setWear(3); + return false; + } + + auto &strength = info.material->strength; + + // Use random strain type excluding COMPRESSIVE (conveniently last) + int type = random_int(strain_type::COMPRESSIVE); + int power = minv + random_int(maxv-minv+1); + + // High elasticity materials just bend + if (strength.strain_at_yield[type] >= 5000) + return true; + + // Instant fracture? + int fracture = strength.fracture[type]; + if (fracture <= power) + { + item->setWear(3); + return false; + } + + // Impact within elastic strain range? + int yield = strength.yield[type]; + if (yield > power) + return true; + + // Can wear? + auto actual = virtual_cast(item); + if (!actual) + return false; + + // Transform plastic deformation to wear + int max_wear = WEAR_TICKS * 4; + int cur_wear = WEAR_TICKS * actual->wear + actual->wear_timer; + cur_wear += int64_t(power - yield)*max_wear/(fracture - yield); + + if (cur_wear >= max_wear) + { + actual->wear = 3; + return false; + } + else + { + actual->wear = cur_wear / WEAR_TICKS; + actual->wear_timer = cur_wear % WEAR_TICKS; + return true; + } +} + /* * Configuration object */ @@ -1363,6 +1424,10 @@ struct projectile_hook : df::proj_itemst { float speed = 100000.0f / (fall_delay + 1); int min_zspeed = (fall_delay+1)*4900; + float bonus = 1.0f + 0.1f*(origin_pos.z -cur_pos.z); + bonus *= 1.0f + (distance_flown - 60) / 200.0f; + speed *= bonus; + // Flight direction vector df::coord dist = target_pos - origin_pos; float vx = dist.x, vy = dist.y, vz = fabs(dist.z); @@ -1383,10 +1448,28 @@ struct projectile_hook : df::proj_itemst { for (size_t i = 0; i < contents.size(); i++) { auto child = contents[i]; + + // Liquids are vaporized so that they cover nearby units + if (child->isLiquid()) + { + auto flow = Maps::spawnFlow( + cur_pos, + flow_type::MaterialVapor, + child->getMaterial(), child->getMaterialIndex(), + 100 + ); + + // should it leave a puddle too?.. + if (flow && Items::remove(mc, child)) + continue; + } + auto proj = Items::makeProjectile(mc, child); if (!proj) continue; - proj->flags.bits.no_impact_destroy = true; + bool keep = apply_impact_damage(child, 50000, int(250000*bonus)); + + proj->flags.bits.no_impact_destroy = keep; //proj->flags.bits.bouncing = true; proj->flags.bits.piercing = true; proj->flags.bits.parabolic = true; @@ -1403,7 +1486,7 @@ struct projectile_hook : df::proj_itemst { proj->speed_x = int(speed * sx); proj->speed_y = int(speed * sy); - proj->speed_z = int(speed * sz); + proj->speed_z = std::max(min_zspeed, int(speed * sz)); } } From 000e3baf27e3d811e673bca08e4381c1f2c632b7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 14 Sep 2012 20:57:03 +0400 Subject: [PATCH 35/48] Implement skill-based miss probability in siege engine. --- plugins/devel/siege-engine.cpp | 83 +++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 11 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index d1e34ced9..b8a2f087b 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -1286,6 +1286,12 @@ static int proposeUnitHits(lua_State *L) * Projectile hook */ +static const int offsets[8][2] = { + { -1, -1 }, { 0, -1 }, { 1, -1 }, + { -1, 0 }, { 1, 0 }, + { -1, 1 }, { 0, 1 }, { 1, 1 } +}; + struct projectile_hook : df::proj_itemst { typedef df::proj_itemst interpose_base; @@ -1293,6 +1299,9 @@ struct projectile_hook : df::proj_itemst { { target_pos = path.target; + // Debug + Maps::getTileOccupancy(path.goal)->bits.arrow_color = COLOR_LIGHTMAGENTA; + PathMetrics raytrace(path); // Materialize map blocks, or the projectile will crash into them @@ -1320,7 +1329,53 @@ struct projectile_hook : df::proj_itemst { fall_threshold = std::min(fall_threshold, engine->fire_range.second); } - void aimAtArea(EngineInfo *engine) + void aimAtPoint(EngineInfo *engine, int skill, const ProjectilePath &path) + { + df::coord fail_target = path.goal; + + orient_engine(engine->bld, path.goal); + + // Debug + Maps::getTileOccupancy(path.goal)->bits.arrow_color = COLOR_LIGHTRED; + + // Dabbling always hit in 7x7 area + if (skill < skill_rating::Novice) + { + fail_target.x += random_int(7)-3; + fail_target.y += random_int(7)-3; + aimAtPoint(engine, ProjectilePath(path.origin, fail_target)); + return; + } + + // Exact hit chance + float hit_chance = 1.04f - powf(0.8f, skill); + + if (float(rand())/RAND_MAX < hit_chance) + { + aimAtPoint(engine, path); + return; + } + + // Otherwise perturb + if (skill <= skill_rating::Proficient) + { + // 5x5 + fail_target.x += random_int(5)-2; + fail_target.y += random_int(5)-2; + } + else + { + // 3x3 + int idx = random_int(8); + fail_target.x += offsets[idx][0]; + fail_target.y += offsets[idx][1]; + } + + ProjectilePath fail(path.origin, fail_target, path.fudge_delta, path.fudge_factor); + aimAtPoint(engine, fail); + } + + void aimAtArea(EngineInfo *engine, int skill) { df::coord target, last_passable; df::coord tbase = engine->target.first; @@ -1343,7 +1398,7 @@ struct projectile_hook : df::proj_itemst { if (raytrace.hits() && engine->isInRange(raytrace.goal_step)) { - aimAtPoint(engine, path); + aimAtPoint(engine, skill, path); return; } } @@ -1351,7 +1406,7 @@ struct projectile_hook : df::proj_itemst { if (!last_passable.isValid()) last_passable = target; - aimAtPoint(engine, ProjectilePath(engine->center, last_passable)); + aimAtPoint(engine, skill, ProjectilePath(engine->center, last_passable)); } static int safeAimProjectile(lua_State *L) @@ -1373,9 +1428,9 @@ struct projectile_hook : df::proj_itemst { lua_call(L, 5, 1); if (lua_isnil(L, -1)) - proj->aimAtArea(engine); + proj->aimAtArea(engine, skill); else - proj->aimAtPoint(engine, decode_path(L, -1, engine->center)); + proj->aimAtPoint(engine, skill, decode_path(L, -1, engine->center)); return 0; } @@ -1396,13 +1451,19 @@ struct projectile_hook : df::proj_itemst { int skill = getOperatorSkill(engine->bld, true); - lua_pushcfunction(L, safeAimProjectile); - lua_pushlightuserdata(L, this); - lua_pushlightuserdata(L, engine); - lua_pushinteger(L, skill); + // Dabbling can't aim + if (skill < skill_rating::Novice) + aimAtArea(engine, skill); + else + { + lua_pushcfunction(L, safeAimProjectile); + lua_pushlightuserdata(L, this); + lua_pushlightuserdata(L, engine); + lua_pushinteger(L, skill); - if (!Lua::Core::SafeCall(out, 3, 0)) - aimAtArea(engine); + if (!Lua::Core::SafeCall(out, 3, 0)) + aimAtArea(engine, skill); + } switch (item->getType()) { From 58fda716e6c1feee85ce7fb15d913c87444c3feb Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 16 Sep 2012 17:06:31 -0500 Subject: [PATCH 36/48] Explicit cast is required for MSVC. --- plugins/devel/siege-engine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index b8a2f087b..5e5cf5d74 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -1491,7 +1491,7 @@ struct projectile_hook : df::proj_itemst { // Flight direction vector df::coord dist = target_pos - origin_pos; - float vx = dist.x, vy = dist.y, vz = fabs(dist.z); + float vx = dist.x, vy = dist.y, vz = fabs((float)dist.z); normalize(vx, vy, vz); int start_z = 0; From c1e20c6f0565007c47cce9aaa06199a061dac20e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 17 Sep 2012 12:47:18 +0400 Subject: [PATCH 37/48] Follow changes to structures. --- library/include/DataFuncs.h | 88 +++++++++++++++++++++++-------------- library/modules/Gui.cpp | 4 +- library/xml | 2 +- plugins/sort.cpp | 2 +- 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index 52039566c..01a798e34 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -85,7 +85,7 @@ namespace df { static const bool is_method = true; \ }; -#define INSTANTIATE_WRAPPERS(Count, FArgs, Args, Loads) \ +#define INSTANTIATE_WRAPPERS2(Count, FArgs, Args, Loads) \ template struct function_wrapper { \ static const int num_args = Count; \ static void execute(lua_State *state, int base, void (*cb) FArgs) { Loads; INVOKE_VOID(cb Args); } \ @@ -105,79 +105,103 @@ namespace df { LOAD_CLASS(); Loads; INVOKE_RV((self->*cb) Args); } \ }; +#define INSTANTIATE_WRAPPERS(Count, FArgs, OFArgs, Args, OArgs, Loads) \ + INSTANTIATE_WRAPPERS2(Count, FArgs, Args, Loads) \ + INSTANTIATE_WRAPPERS2(Count, OFArgs, OArgs, LOAD_OSTREAM(out); Loads) + #define FW_TARGSC #define FW_TARGS INSTANTIATE_RETURN_TYPE(()) -INSTANTIATE_WRAPPERS(0, (), (), ;) -INSTANTIATE_WRAPPERS(0, (OSTREAM_ARG), (out), LOAD_OSTREAM(out);) +INSTANTIATE_WRAPPERS(0, (), (OSTREAM_ARG), (), (out), ;) #undef FW_TARGS #undef FW_TARGSC #define FW_TARGSC FW_TARGS, #define FW_TARGS class A1 INSTANTIATE_RETURN_TYPE((A1)) -INSTANTIATE_WRAPPERS(1, (A1), (vA1), LOAD_ARG(A1);) -INSTANTIATE_WRAPPERS(1, (OSTREAM_ARG,A1), (out,vA1), LOAD_OSTREAM(out); LOAD_ARG(A1);) +INSTANTIATE_WRAPPERS(1, (A1), (OSTREAM_ARG,A1), (vA1), (out,vA1), LOAD_ARG(A1);) #undef FW_TARGS #define FW_TARGS class A1, class A2 INSTANTIATE_RETURN_TYPE((A1,A2)) -INSTANTIATE_WRAPPERS(2, (A1,A2), (vA1,vA2), LOAD_ARG(A1); LOAD_ARG(A2);) -INSTANTIATE_WRAPPERS(2, (OSTREAM_ARG,A1,A2), (out,vA1,vA2), - LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2);) +INSTANTIATE_WRAPPERS(2, (A1,A2), (OSTREAM_ARG,A1,A2), (vA1,vA2), (out,vA1,vA2), + LOAD_ARG(A1); LOAD_ARG(A2);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3 INSTANTIATE_RETURN_TYPE((A1,A2,A3)) -INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (vA1,vA2,vA3), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) -INSTANTIATE_WRAPPERS(3, (OSTREAM_ARG,A1,A2,A3), (out,vA1,vA2,vA3), - LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) +INSTANTIATE_WRAPPERS(3, (A1,A2,A3), (OSTREAM_ARG,A1,A2,A3), (vA1,vA2,vA3), (out,vA1,vA2,vA3), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3, class A4 INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4)) -INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (vA1,vA2,vA3,vA4), +INSTANTIATE_WRAPPERS(4, (A1,A2,A3,A4), (OSTREAM_ARG,A1,A2,A3,A4), + (vA1,vA2,vA3,vA4), (out,vA1,vA2,vA3,vA4), LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);) -INSTANTIATE_WRAPPERS(4, (OSTREAM_ARG,A1,A2,A3,A4), (out,vA1,vA2,vA3,vA4), - LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3, class A4, class A5 INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5)) -INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (vA1,vA2,vA3,vA4,vA5), - LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);) -INSTANTIATE_WRAPPERS(5, (OSTREAM_ARG,A1,A2,A3,A4,A5), (out,vA1,vA2,vA3,vA4,vA5), - LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); - LOAD_ARG(A3); LOAD_ARG(A4); LOAD_ARG(A5);) +INSTANTIATE_WRAPPERS(5, (A1,A2,A3,A4,A5), (OSTREAM_ARG,A1,A2,A3,A4,A5), + (vA1,vA2,vA3,vA4,vA5), (out,vA1,vA2,vA3,vA4,vA5), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6 INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6)) -INSTANTIATE_WRAPPERS(6, (A1,A2,A3,A4,A5,A6), (vA1,vA2,vA3,vA4,vA5,vA6), - LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); - LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);) -INSTANTIATE_WRAPPERS(6, (OSTREAM_ARG,A1,A2,A3,A4,A5,A6), (out,vA1,vA2,vA3,vA4,vA5,vA6), - LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); - LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6);) +INSTANTIATE_WRAPPERS(6, (A1,A2,A3,A4,A5,A6), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6), + (vA1,vA2,vA3,vA4,vA5,vA6), (out,vA1,vA2,vA3,vA4,vA5,vA6), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7 INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7)) -INSTANTIATE_WRAPPERS(7, (A1,A2,A3,A4,A5,A6,A7), (vA1,vA2,vA3,vA4,vA5,vA6,vA7), - LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); - LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6); - LOAD_ARG(A7);) -INSTANTIATE_WRAPPERS(7, (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7), - LOAD_OSTREAM(out); LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); - LOAD_ARG(A4); LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7);) +INSTANTIATE_WRAPPERS(7, (A1,A2,A3,A4,A5,A6,A7), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7);) #undef FW_TARGS #define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8 INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8)) +INSTANTIATE_WRAPPERS(8, (A1,A2,A3,A4,A5,A6,A7,A8), (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8), (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);) +#undef FW_TARGS + +#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9)) +INSTANTIATE_WRAPPERS(9, (A1,A2,A3,A4,A5,A6,A7,A8,A9), + (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9), + (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8); + LOAD_ARG(A9);) +#undef FW_TARGS + +#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10)) +INSTANTIATE_WRAPPERS(10, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10), + (OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10), + (vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10), + (out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10), + LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4); + LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8); + LOAD_ARG(A9); LOAD_ARG(A10);) +#undef FW_TARGS + +#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11 +INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11)) #undef FW_TARGS #undef FW_TARGSC #undef INSTANTIATE_WRAPPERS +#undef INSTANTIATE_WRAPPERS2 #undef INVOKE_VOID #undef INVOKE_RV #undef LOAD_CLASS diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 91df14eaf..b0cfda670 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -54,7 +54,7 @@ using namespace DFHack; #include "df/viewscreen_joblistst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_itemst.h" -#include "df/viewscreen_layerst.h" +#include "df/viewscreen_layer.h" #include "df/viewscreen_layer_workshop_profilest.h" #include "df/viewscreen_layer_noblelistst.h" #include "df/viewscreen_layer_overall_healthst.h" @@ -95,7 +95,7 @@ using df::global::selection_rect; using df::global::ui_menu_width; using df::global::ui_area_map_width; -static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx) +static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx) { return virtual_cast(vector_get(layer->layer_objects,idx)); } diff --git a/library/xml b/library/xml index ee2b63a8f..a6b95f1c4 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit ee2b63a8ffdbce66489148ca2a9803db1d0b9090 +Subproject commit a6b95f1c42991e485f7e0bb5d029a5eca14ce9ae diff --git a/plugins/sort.cpp b/plugins/sort.cpp index ff51fc773..4b2bf7bbd 100644 --- a/plugins/sort.cpp +++ b/plugins/sort.cpp @@ -228,7 +228,7 @@ static void sort_null_first(vector ¶meters) vector_insert_at(parameters, 0, std::string("(vector_get(layer->layer_objects,idx)); } From f2fde21b10c087fd95948a5f40ef972ac6688718 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 17 Sep 2012 14:45:22 +0400 Subject: [PATCH 38/48] Implement a slightly more sensible aiming AI in siege engine. --- plugins/devel/siege-engine.cpp | 76 +++++++++++- plugins/lua/siege-engine.lua | 208 +++++++++++++++++++++++++++++++-- 2 files changed, 270 insertions(+), 14 deletions(-) diff --git a/plugins/devel/siege-engine.cpp b/plugins/devel/siege-engine.cpp index 5e5cf5d74..3b95aba35 100644 --- a/plugins/devel/siege-engine.cpp +++ b/plugins/devel/siege-engine.cpp @@ -112,7 +112,7 @@ static bool is_in_range(const coord_range &target, df::coord pos) static std::pair get_engine_range(df::building_siegeenginest *bld) { if (bld->type == siegeengine_type::Ballista) - return std::make_pair(0, 200); + return std::make_pair(1, 200); else return std::make_pair(30, 100); } @@ -291,7 +291,7 @@ static EngineInfo *find_engine(df::building *bld, bool create = false) ); obj->is_catapult = (ebld->type == siegeengine_type::Catapult); obj->proj_speed = 2; - obj->hit_delay = 3; + obj->hit_delay = obj->is_catapult ? 2 : -1; obj->fire_range = get_engine_range(ebld); obj->ammo_vector_id = job_item_vector_id::BOULDER; @@ -1107,6 +1107,9 @@ struct UnitPath { float time = unit->counters.job_counter+0.5f; float speed = Units::computeMovementSpeed(unit)/100.0f; + if (unit->counters.unconscious > 0) + time += unit->counters.unconscious; + for (size_t i = 0; i < upath.size(); i++) { df::coord new_pos = upath[i]; @@ -1282,6 +1285,74 @@ static int proposeUnitHits(lua_State *L) return 1; } +static int computeNearbyWeight(lua_State *L) +{ + auto engine = find_engine(L, 1); + luaL_checktype(L, 2, LUA_TTABLE); + luaL_checktype(L, 3, LUA_TTABLE); + const char *fname = luaL_optstring(L, 4, "nearby_weight"); + + std::vector units; + std::vector weights; + + lua_pushnil(L); + + while (lua_next(L, 3)) + { + df::unit *unit; + if (lua_isnumber(L, -2)) + unit = df::unit::find(lua_tointeger(L, -2)); + else + unit = Lua::CheckDFObject(L, -2); + if (!unit) + continue; + units.push_back(UnitPath::get(unit)); + weights.push_back(lua_tonumber(L, -1)); + lua_pop(L, 1); + } + + lua_pushnil(L); + + while (lua_next(L, 2)) + { + Lua::StackUnwinder frame(L, 1); + + lua_getfield(L, frame[1], "unit"); + df::unit *unit = Lua::CheckDFObject(L, -1); + + lua_getfield(L, frame[1], "time"); + float time = luaL_checknumber(L, lua_gettop(L)); + + df::coord pos; + + lua_getfield(L, frame[1], "pos"); + if (lua_isnil(L, -1)) + { + if (!unit) luaL_error(L, "either unit or pos is required"); + pos = UnitPath::get(unit)->posAtTime(time); + } + else + Lua::CheckDFAssign(L, &pos, -1); + + float sum = 0.0f; + + for (size_t i = 0; i < units.size(); i++) + { + if (units[i]->unit == unit) + continue; + + auto diff = units[i]->posAtTime(time) - pos; + float dist = 1 + sqrtf(diff.x*diff.x + diff.y*diff.y + diff.z*diff.z); + sum += weights[i]/(dist*dist); + } + + lua_pushnumber(L, sum); + lua_setfield(L, frame[1], fname); + } + + return 0; +} + /* * Projectile hook */ @@ -1698,6 +1769,7 @@ DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(traceUnitPath), DFHACK_LUA_COMMAND(unitPosAtTime), DFHACK_LUA_COMMAND(proposeUnitHits), + DFHACK_LUA_COMMAND(computeNearbyWeight), DFHACK_LUA_END }; diff --git a/plugins/lua/siege-engine.lua b/plugins/lua/siege-engine.lua index 89c47659d..33e120feb 100644 --- a/plugins/lua/siege-engine.lua +++ b/plugins/lua/siege-engine.lua @@ -8,37 +8,221 @@ local _ENV = mkmodule('plugins.siege-engine') * clearTargetArea(building) * setTargetArea(building, point1, point2) -> true/false ---]] + * isLinkedToPile(building,pile) -> true/false + * getStockpileLinks(building) -> {pile} + * addStockpileLink(building,pile) -> true/false + * removeStockpileLink(building,pile) -> true/false + + * saveWorkshopProfile(building) -> profile + + * getAmmoItem(building) -> item_type + * setAmmoItem(building,item_type) -> true/false + + * isPassableTile(pos) -> true/false + * isTreeTile(pos) -> true/false + * isTargetableTile(pos) -> true/false + + * getTileStatus(building,pos) -> 'invalid/ok/out_of_range/blocked/semiblocked' + * paintAimScreen(building,view_pos_xyz,left_top_xy,size_xy) + + * canTargetUnit(unit) -> true/false + + proj_info = { target = pos, [delta = float/pos], [factor = int] } + + * projPosAtStep(building,proj_info,step) -> pos + * projPathMetrics(building,proj_info) -> { + hit_type = 'wall/floor/ceiling/map_edge/tree', + collision_step = int, + collision_z_step = int, + goal_distance = int, + goal_step = int/nil, + goal_z_step = int/nil, + status = 'ok/out_of_range/blocked' + } + + * adjustToTarget(building,pos) -> pos,ok=true/false + + * traceUnitPath(unit) -> { {x=int,y=int,z=int[,from=time][,to=time]} } + * unitPosAtTime(unit, time) -> pos + + * proposeUnitHits(building) -> { { + pos=pos, unit=unit, time=float, dist=int, + [lmargin=float,] [rmargin=float,] + } } + + * computeNearbyWeight(building,hits,{[id/unit]=score}[,fname]) + +]] Z_STEP_COUNT = 15 Z_STEP = 1/31 +function getMetrics(engine, path) + path.metrics = path.metrics or projPathMetrics(engine, path) + return path.metrics +end + function findShotHeight(engine, target) local path = { target = target, delta = 0.0 } - if projPathMetrics(engine, path).goal_step then + if getMetrics(engine, path).goal_step then return path end - for i = 1,Z_STEP_COUNT do - path.delta = i*Z_STEP - if projPathMetrics(engine, path).goal_step then - return path + local tpath = { target = target, delta = Z_STEP_COUNT*Z_STEP } + + if getMetrics(engine, tpath).goal_step then + for i = 1,Z_STEP_COUNT-1 do + path = { target = target, delta = i*Z_STEP } + if getMetrics(engine, path).goal_step then + return path + end + end + + return tpath + end + + tpath = { target = target, delta = -Z_STEP_COUNT*Z_STEP } + + if getMetrics(engine, tpath).goal_step then + for i = 1,Z_STEP_COUNT-1 do + path = { target = target, delta = -i*Z_STEP } + if getMetrics(engine, path).goal_step then + return path + end + end + + return tpath + end +end + +function findReachableTargets(engine, targets) + local reachable = {} + for _,tgt in ipairs(targets) do + tgt.path = findShotHeight(engine, tgt.pos) + if tgt.path then + table.insert(reachable, tgt) + end + end + return reachable +end + +recent_targets = recent_targets or {} + +if dfhack.is_core_context then + dfhack.onStateChange[_ENV] = function(code) + if code == SC_MAP_LOADED then + recent_targets = {} + end + end +end + +function saveRecent(unit) + local id = unit.id + local tgt = recent_targets + tgt[id] = (tgt[id] or 0) + 1 + dfhack.timeout(3, 'days', function() + tgt[id] = math.max(0, tgt[id]-1) + end) +end + +function getBaseUnitWeight(unit) + if dfhack.units.isCitizen(unit) then + return -10 + elseif unit.flags1.diplomat or unit.flags1.merchant then + return -2 + elseif unit.flags1.tame and unit.civ_id == df.global.ui.civ_id then + return -1 + else + local rv = 1 + if unit.flags1.marauder then rv = rv + 0.5 end + if unit.flags1.active_invader then rv = rv + 1 end + if unit.flags1.invader_origin then rv = rv + 1 end + if unit.flags1.invades then rv = rv + 1 end + if unit.flags1.hidden_ambusher then rv = rv + 1 end + return rv + end +end + +function getUnitWeight(unit) + local base = getBaseUnitWeight(unit) + return base * math.pow(0.7, recent_targets[unit.id] or 0) +end + +function unitWeightCache() + local cache = {} + return cache, function(unit) + local id = unit.id + cache[id] = cache[id] or getUnitWeight(unit) + return cache[id] + end +end + +function scoreTargets(engine, reachable) + local ucache, get_weight = unitWeightCache() + + for _,tgt in ipairs(reachable) do + tgt.score = get_weight(tgt.unit) + if tgt.lmargin and tgt.lmargin < 3 then + tgt.score = tgt.score * tgt.lmargin / 3 + end + if tgt.rmargin and tgt.rmargin < 3 then + tgt.score = tgt.score * tgt.rmargin / 3 end + end + + computeNearbyWeight(engine, reachable, ucache) + + for _,tgt in ipairs(reachable) do + tgt.score = (tgt.score + tgt.nearby_weight*0.7) * math.pow(0.995, tgt.time/3) + end + + table.sort(reachable, function(a,b) + return a.score > b.score or (a.score == b.score and a.time < b.time) + end) +end + +function pickUniqueTargets(reachable) + local unique = {} - path.delta = -i*Z_STEP - if projPathMetrics(engine, path).goal_step then - return path + if #reachable > 0 then + local pos_table = {} + local first_score = reachable[1].score + + for i,tgt in ipairs(reachable) do + if tgt.score < 0 or tgt.score < 0.1*first_score then + break + end + local x,y,z = pos2xyz(tgt.pos) + local key = x..':'..y..':'..z + if pos_table[key] then + table.insert(pos_table[key].units, tgt.unit) + else + table.insert(unique, tgt) + pos_table[key] = tgt + tgt.units = { tgt.unit } + end end end + + return unique end function doAimProjectile(engine, item, target_min, target_max, skill) print(item, df.skill_rating[skill]) + local targets = proposeUnitHits(engine) - if #targets > 0 then - local rnd = math.random(#targets) - return findShotHeight(engine, targets[rnd].pos) + local reachable = findReachableTargets(engine, targets) + scoreTargets(engine, reachable) + local unique = pickUniqueTargets(reachable) + + if #unique > 0 then + local cnt = math.max(math.min(#unique,5), math.min(10, math.floor(#unique/2))) + local rnd = math.random(cnt) + for _,u in ipairs(unique[rnd].units) do + saveRecent(u) + end + return unique[rnd].path end end From 82e870c8ddc9b64370c3828d1d4807306ac72887 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 17 Sep 2012 14:59:59 +0400 Subject: [PATCH 39/48] Move siege engine out of devel. --- NEWS | 10 ++++++---- plugins/CMakeLists.txt | 1 + plugins/devel/CMakeLists.txt | 1 - plugins/{devel => }/siege-engine.cpp | 0 scripts/gui/siege-engine.lua | 1 + 5 files changed, 8 insertions(+), 5 deletions(-) rename plugins/{devel => }/siege-engine.cpp (100%) diff --git a/NEWS b/NEWS index 43707f9a7..4294bedb4 100644 --- a/NEWS +++ b/NEWS @@ -53,9 +53,11 @@ DFHack v0.34.11-r2 (UNRELEASED) When activated, implements a pressure plate modification that detects power in gear boxes built on the four adjacent N/S/W/E tiles. The gui/power-meter script implements the build configuration UI. - New Siege Engine plugin (INCOMPLETE): + New Siege Engine plugin: When enabled and configured via gui/siege-engine, allows aiming siege engines - at a designated rectangular area across Z levels. Also supports loading catapults - with non-boulder projectiles, taking from a stockpile, and restricting operator - skill range, like with ordinary workshops. + at a designated rectangular area with 360 degree fire range and across Z levels. + Also supports loading catapults with non-boulder projectiles, taking from a stockpile, + and restricting operator skill range like with ordinary workshops. + Disclaimer: not in any way to undermine the future siege update from Toady, but the aiming + logic of existing engines hasn't been updated since 2D, and is almost useless as/is. diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6e207385e..8511d86c6 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -119,6 +119,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(steam-engine steam-engine.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 39e8f7b60..134d5cb67 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -18,7 +18,6 @@ DFHACK_PLUGIN(stripcaged stripcaged.cpp) DFHACK_PLUGIN(rprobe rprobe.cpp) DFHACK_PLUGIN(nestboxes nestboxes.cpp) DFHACK_PLUGIN(vshook vshook.cpp) -DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/siege-engine.cpp b/plugins/siege-engine.cpp similarity index 100% rename from plugins/devel/siege-engine.cpp rename to plugins/siege-engine.cpp diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index 47043cbb1..7a76d7673 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -21,6 +21,7 @@ local item_choices = { { caption = 'trap components', item_type = df.item_type.TRAPCOMP }, { caption = 'bins', item_type = df.item_type.BIN }, { caption = 'barrels', item_type = df.item_type.BARREL }, + { caption = 'cages', item_type = df.item_type.CAGE }, { caption = 'anything', item_type = -1 }, } From 613063cef4d87b3b4307144b85da60dc40daceb3 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 17 Sep 2012 17:19:24 +0400 Subject: [PATCH 40/48] Add a tweak to fix subtractDimension of small amounts. --- dfhack.init-example | 7 ++++ plugins/tweak.cpp | 91 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) diff --git a/dfhack.init-example b/dfhack.init-example index a9b69b826..b8b53cad7 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -59,6 +59,9 @@ keybinding add Alt-L@dwarfmode/LookAround gui/liquids # machine power sensitive pressure plate construction keybinding add Ctrl-Shift-M@dwarfmode/Build/Position/Trap gui/power-meter +# siege engine control +keybinding add Alt-A@dwarfmode/QueryBuilding/Some/SiegeEngine gui/siege-engine + ############################ # UI and game logic tweaks # ############################ @@ -79,3 +82,7 @@ tweak stable-temp # capping the rate to no less than 1 degree change per 500 frames # Note: will also cause stuff to melt faster in magma etc tweak fast-heat 500 + +# stop stacked liquid/bar/thread/cloth items from lasting forever +# if used in reactions that use only a fraction of the dimension. +tweak fix-dimensions diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index bebc346c5..4fef285f9 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -33,6 +33,10 @@ #include "df/ui_build_selector.h" #include "df/building_trapst.h" #include "df/item_actual.h" +#include "df/item_liquipowder.h" +#include "df/item_barst.h" +#include "df/item_threadst.h" +#include "df/item_clothst.h" #include "df/contaminant.h" #include @@ -93,6 +97,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector stack_size <= 1) return; + int rem = delta % dim; + if (rem == 0) return; + // If destroys, pass through + int intv = delta / dim; + if (intv >= self->stack_size) return; + // Subtract int part + delta = rem; + self->stack_size -= intv; + if (self->stack_size <= 1) return; + + // If kills the item or cannot split, round up. + if (!self->flags.bits.in_inventory || !Items::getContainer(self)) + { + delta = dim; + return; + } + + // Otherwise split the stack + color_ostream_proxy out(Core::getInstance().getConsole()); + out.print("fix-dimensions: splitting stack #%d for delta %d.\n", self->id, delta); + + auto copy = self->splitStack(self->stack_size-1, true); + if (copy) copy->categorize(true); +} + +struct dimension_lqp_hook : df::item_liquipowder { + typedef df::item_liquipowder interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) + { + correct_dimension(this, delta, dimension); + return INTERPOSE_NEXT(subtractDimension)(delta); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dimension_lqp_hook, subtractDimension); + +struct dimension_bar_hook : df::item_barst { + typedef df::item_barst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) + { + correct_dimension(this, delta, dimension); + return INTERPOSE_NEXT(subtractDimension)(delta); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dimension_bar_hook, subtractDimension); + +struct dimension_thread_hook : df::item_threadst { + typedef df::item_threadst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) + { + correct_dimension(this, delta, dimension); + return INTERPOSE_NEXT(subtractDimension)(delta); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dimension_thread_hook, subtractDimension); + +struct dimension_cloth_hook : df::item_clothst { + typedef df::item_clothst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, subtractDimension, (int32_t delta)) + { + correct_dimension(this, delta, dimension); + return INTERPOSE_NEXT(subtractDimension)(delta); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(dimension_cloth_hook, subtractDimension); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -491,6 +575,13 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, updateTemperature), parameters); enable_hook(out, INTERPOSE_HOOK(fast_heat_hook, adjustTemperature), parameters); } + else if (cmd == "fix-dimensions") + { + enable_hook(out, INTERPOSE_HOOK(dimension_lqp_hook, subtractDimension), parameters); + enable_hook(out, INTERPOSE_HOOK(dimension_bar_hook, subtractDimension), parameters); + enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters); + enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters); + } else return CR_WRONG_USAGE; From 36e44c682cc2cecb552eca8dfc75ad1a436086cc Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Mon, 17 Sep 2012 21:15:51 +0400 Subject: [PATCH 41/48] Add a plugin implementing 'add spatter to item' reactions. --- NEWS | 5 +- library/include/modules/Materials.h | 5 + library/modules/Materials.cpp | 13 + library/xml | 2 +- plugins/CMakeLists.txt | 4 + plugins/add-spatter.cpp | 409 +++++++++++++++++++++ plugins/cleaners.cpp | 13 +- plugins/raw/entity_default.diff | 29 ++ plugins/raw/material_template_default.diff | 10 + plugins/raw/reaction_spatter.txt | 41 +++ 10 files changed, 526 insertions(+), 5 deletions(-) create mode 100644 plugins/add-spatter.cpp create mode 100644 plugins/raw/entity_default.diff create mode 100644 plugins/raw/material_template_default.diff create mode 100644 plugins/raw/reaction_spatter.txt diff --git a/NEWS b/NEWS index 4294bedb4..fdc69ac53 100644 --- a/NEWS +++ b/NEWS @@ -60,4 +60,7 @@ DFHack v0.34.11-r2 (UNRELEASED) and restricting operator skill range like with ordinary workshops. Disclaimer: not in any way to undermine the future siege update from Toady, but the aiming logic of existing engines hasn't been updated since 2D, and is almost useless as/is. - + New Add Spatter plugin: + Detects reactions with certain names in the raws, and changes them from adding + improvements to adding item contaminants. This allows directly covering items + with poisons. The added spatters are immune both to water and 'clean items'. diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 76c89de30..fb5a6353c 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -131,6 +131,11 @@ namespace DFHack bool findPlant(const std::string &token, const std::string &subtoken); bool findCreature(const std::string &token, const std::string &subtoken); + bool findProduct(df::material *material, const std::string &name); + bool findProduct(const MaterialInfo &info, const std::string &name) { + return findProduct(info.material, name); + } + std::string getToken(); std::string toString(uint16_t temp = 10015, bool named = true); diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 50cf21a9c..db9c9c7df 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -283,6 +283,19 @@ bool MaterialInfo::findCreature(const std::string &token, const std::string &sub return decode(-1); } +bool MaterialInfo::findProduct(df::material *material, const std::string &name) +{ + if (!material || name.empty()) + return decode(-1); + + auto &pids = material->reaction_product.id; + for (size_t i = 0; i < pids.size(); i++) + if ((*pids[i]) == name) + return decode(material->reaction_product.material, i); + + return decode(-1); +} + std::string MaterialInfo::getToken() { if (isNone()) diff --git a/library/xml b/library/xml index a6b95f1c4..260ff4a1d 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit a6b95f1c42991e485f7e0bb5d029a5eca14ce9ae +Subproject commit 260ff4a1ddcfd54d0143aa6d908a93c4ff709c87 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8511d86c6..0b0ad0461 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -47,6 +47,9 @@ install(DIRECTORY lua/ install(DIRECTORY raw/ DESTINATION ${DFHACK_DATA_DESTINATION}/raw FILES_MATCHING PATTERN "*.txt") +install(DIRECTORY raw/ + DESTINATION ${DFHACK_DATA_DESTINATION}/raw + FILES_MATCHING PATTERN "*.diff") # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) @@ -120,6 +123,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(steam-engine steam-engine.cpp) DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(siege-engine siege-engine.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(add-spatter add-spatter.cpp) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp new file mode 100644 index 000000000..ed5f47f7b --- /dev/null +++ b/plugins/add-spatter.cpp @@ -0,0 +1,409 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "df/item_liquid_miscst.h" +#include "df/item_constructed.h" +#include "df/builtin_mats.h" +#include "df/world.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/job_item_ref.h" +#include "df/ui.h" +#include "df/report.h" +#include "df/reaction.h" +#include "df/reaction_reagent_itemst.h" +#include "df/reaction_product_item_improvementst.h" +#include "df/reaction_product_improvement_flags.h" +#include "df/matter_state.h" +#include "df/contaminant.h" + +#include "MiscUtils.h" + +using std::vector; +using std::string; +using std::stack; +using namespace DFHack; +using namespace df::enums; + +using df::global::gps; +using df::global::world; +using df::global::ui; + +typedef df::reaction_product_item_improvementst improvement_product; + +DFHACK_PLUGIN("add-spatter"); + +struct ReagentSource { + int idx; + df::reaction_reagent *reagent; + + ReagentSource() : idx(-1), reagent(NULL) {} +}; + +struct MaterialSource : ReagentSource { + bool product; + std::string product_name; + + int mat_type, mat_index; + + MaterialSource() : product(false), mat_type(-1), mat_index(-1) {} +}; + +struct ProductInfo { + df::reaction *react; + improvement_product *product; + + ReagentSource object; + MaterialSource material; + + bool isValid() { + return object.reagent && (material.mat_type >= 0 || material.reagent); + } +}; + +struct ReactionInfo { + df::reaction *react; + + std::vector products; +}; + +static std::map reactions; +static std::map products; + +static ReactionInfo *find_reaction(const std::string &name) +{ + auto it = reactions.find(name); + return (it != reactions.end()) ? &it->second : NULL; +} + +static bool is_add_spatter(const std::string &name) +{ + return name.size() > 12 && memcmp(name.data(), "SPATTER_ADD_", 12) == 0; +} + +static void find_material(int *type, int *index, df::item *input, MaterialSource &mat) +{ + if (input && mat.reagent) + { + MaterialInfo info(input); + + if (mat.product) + { + if (!info.findProduct(info, mat.product_name)) + { + color_ostream_proxy out(Core::getInstance().getConsole()); + out.printerr("Cannot find product '%s'\n", mat.product_name.c_str()); + } + } + + *type = info.type; + *index = info.index; + } + else + { + *type = mat.mat_type; + *index = mat.mat_index; + } +} + +static bool has_contaminant(df::item_actual *item, int type, int index) +{ + auto cont = item->contaminants; + if (!cont) + return false; + + for (size_t i = 0; i < cont->size(); i++) + { + auto cur = (*cont)[i]; + if (cur->mat_type == type && cur->mat_index == index) + return true; + } + + return false; +} + +/* + * Hooks + */ + +typedef std::map > item_table; + +static void index_items(item_table &table, df::job *job, ReactionInfo *info) +{ + for (int i = job->items.size()-1; i >= 0; i--) + { + auto iref = job->items[i]; + if (iref->job_item_idx < 0) continue; + auto iitem = job->job_items[iref->job_item_idx]; + + if (iitem->contains.empty()) + { + table[iitem->reagent_index].push_back(iref->item); + } + else + { + std::vector contents; + Items::getContainedItems(iref->item, &contents); + + for (int j = contents.size()-1; j >= 0; j--) + { + for (int k = iitem->contains.size()-1; k >= 0; k--) + { + int ridx = iitem->contains[k]; + auto reag = info->react->reagents[ridx]; + + if (reag->matches(contents[j], info->react, iitem->reaction_id)) + table[ridx].push_back(contents[j]); + } + } + } + } +} + +df::item* find_item(ReagentSource &info, item_table &table) +{ + if (!info.reagent) + return NULL; + if (table[info.idx].empty()) + return NULL; + return table[info.idx].back(); +} + +struct item_hook : df::item_constructed { + typedef df::item_constructed interpose_base; + + DEFINE_VMETHOD_INTERPOSE(bool, isImprovable, (df::job *job, int16_t mat_type, int32_t mat_index)) + { + ReactionInfo *info; + + if (job && job->job_type == job_type::CustomReaction && + (info = find_reaction(job->reaction_name)) != NULL) + { + if (!contaminants || contaminants->empty()) + return true; + + item_table table; + index_items(table, job, info); + + for (size_t i = 0; i < info->products.size(); i++) + { + auto &product = info->products[i]; + + int mattype, matindex; + auto material = find_item(info->products[i].material, table); + + find_material(&mattype, &matindex, material, product.material); + + if (mattype < 0 || has_contaminant(this, mattype, matindex)) + return false; + } + + return true; + } + + return INTERPOSE_NEXT(isImprovable)(job, mat_type, mat_index); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(item_hook, isImprovable); + +df::item* find_item( + ReagentSource &info, + std::vector *in_reag, + std::vector *in_items +) { + if (!info.reagent) + return NULL; + for (int i = in_items->size(); i >= 0; i--) + if ((*in_reag)[i] == info.reagent) + return (*in_items)[i]; + return NULL; +} + +struct product_hook : improvement_product { + typedef improvement_product interpose_base; + + DEFINE_VMETHOD_INTERPOSE( + void, produce, + (df::unit *unit, std::vector *out_items, + std::vector *in_reag, + std::vector *in_items, + int32_t quantity, int16_t skill, + df::historical_entity *entity, df::world_site *site) + ) { + if (auto product = products[this]) + { + auto object = find_item(product->object, in_reag, in_items); + auto material = find_item(product->material, in_reag, in_items); + + if (object && (material || !product->material.reagent)) + { + int mattype, matindex; + find_material(&mattype, &matindex, material, product->material); + + object->addContaminant( + mattype, matindex, + matter_state::Liquid, // TODO: heuristics or by reagent name + object->getTemperature(), + probability, // used as size + -1, + 0x8000 // not washed by water, and 'clean items' safe. + ); + } + + return; + } + + INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce); + +/* + * Scan raws for matching reactions. + */ + +static void find_reagent( + color_ostream &out, ReagentSource &info, df::reaction *react, std::string name +) { + for (size_t i = 0; i < react->reagents.size(); i++) + { + if (react->reagents[i]->code != name) + continue; + + info.idx = i; + info.reagent = react->reagents[i]; + return; + } + + out.printerr("Invalid reagent name '%s' in '%s'\n", name.c_str(), react->code.c_str()); +} + +static void parse_product( + color_ostream &out, ProductInfo &info, df::reaction *react, improvement_product *prod +) { + using namespace df::enums::reaction_product_improvement_flags; + + info.react = react; + info.product = prod; + + find_reagent(out, info.object, react, prod->target_reagent); + + auto ritem = strict_virtual_cast(info.object.reagent); + if (ritem) + ritem->flags1.bits.improvable = true; + + info.material.mat_type = prod->mat_type; + info.material.mat_index = prod->mat_index; + + if (prod->flags.is_set(GET_MATERIAL_PRODUCT)) + { + find_reagent(out, info.material, react, prod->get_material.reagent_code); + + info.material.product = true; + info.material.product_name = prod->get_material.product_code; + } + else if (prod->flags.is_set(GET_MATERIAL_SAME)) + { + find_reagent(out, info.material, react, prod->get_material.reagent_code); + } +} + +static bool find_reactions(color_ostream &out) +{ + reactions.clear(); + products.clear(); + + auto &rlist = world->raws.reactions; + + for (size_t i = 0; i < rlist.size(); i++) + { + if (!is_add_spatter(rlist[i]->code)) + continue; + + reactions[rlist[i]->code].react = rlist[i]; + } + + for (auto it = reactions.begin(); it != reactions.end(); ++it) + { + auto &prod = it->second.react->products; + auto &out_prod = it->second.products; + + for (size_t i = 0; i < prod.size(); i++) + { + auto itprod = strict_virtual_cast(prod[i]); + if (!itprod) continue; + + out_prod.push_back(ProductInfo()); + parse_product(out, out_prod.back(), it->second.react, itprod); + } + + for (size_t i = 0; i < prod.size(); i++) + { + if (out_prod[i].isValid()) + products[out_prod[i].product] = &out_prod[i]; + } + } + + return !products.empty(); +} + +static void enable_hooks(bool enable) +{ + INTERPOSE_HOOK(item_hook, isImprovable).apply(enable); + INTERPOSE_HOOK(product_hook, produce).apply(enable); +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_MAP_LOADED: + if (find_reactions(out)) + { + out.print("Detected spatter add reactions - enabling plugin.\n"); + enable_hooks(true); + } + else + enable_hooks(false); + break; + case SC_MAP_UNLOADED: + enable_hooks(false); + reactions.clear(); + products.clear(); + break; + default: + break; + } + + return CR_OK; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + if (Core::getInstance().isMapLoaded()) + plugin_onstatechange(out, SC_MAP_LOADED); + + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + enable_hooks(false); + return CR_OK; +} diff --git a/plugins/cleaners.cpp b/plugins/cleaners.cpp index c0301de7b..319b83c1f 100644 --- a/plugins/cleaners.cpp +++ b/plugins/cleaners.cpp @@ -81,11 +81,18 @@ command_result cleanitems (color_ostream &out) df::item_actual *item = (df::item_actual *)world->items.all[i]; if (item->contaminants && item->contaminants->size()) { + std::vector saved; for (size_t j = 0; j < item->contaminants->size(); j++) - delete item->contaminants->at(j); + { + auto obj = (*item->contaminants)[j]; + if (obj->flags.whole & 0x8000) // DFHack-generated contaminant + saved.push_back(obj); + else + delete obj; + } cleaned_items++; - cleaned_total += item->contaminants->size(); - item->contaminants->clear(); + cleaned_total += item->contaminants->size() - saved.size(); + item->contaminants->swap(saved); } } if (cleaned_total) diff --git a/plugins/raw/entity_default.diff b/plugins/raw/entity_default.diff new file mode 100644 index 000000000..a99f8ebba --- /dev/null +++ b/plugins/raw/entity_default.diff @@ -0,0 +1,29 @@ +--- ../objects.old/entity_default.txt 2012-09-17 17:59:28.853898702 +0400 ++++ entity_default.txt 2012-09-17 17:59:28.684899429 +0400 +@@ -49,6 +49,7 @@ + [TRAPCOMP:ITEM_TRAPCOMP_SPIKEDBALL] + [TRAPCOMP:ITEM_TRAPCOMP_LARGESERRATEDDISC] + [TRAPCOMP:ITEM_TRAPCOMP_MENACINGSPIKE] ++ [TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON] + [TOY:ITEM_TOY_PUZZLEBOX] + [TOY:ITEM_TOY_BOAT] + [TOY:ITEM_TOY_HAMMER] +@@ -204,6 +205,8 @@ + [PERMITTED_JOB:WAX_WORKER] + [PERMITTED_BUILDING:SOAP_MAKER] + [PERMITTED_BUILDING:SCREW_PRESS] ++ [PERMITTED_BUILDING:STEAM_ENGINE] ++ [PERMITTED_BUILDING:MAGMA_STEAM_ENGINE] + [PERMITTED_REACTION:TAN_A_HIDE] + [PERMITTED_REACTION:RENDER_FAT] + [PERMITTED_REACTION:MAKE_SOAP_FROM_TALLOW] +@@ -248,6 +251,9 @@ + [PERMITTED_REACTION:ROSE_GOLD_MAKING] + [PERMITTED_REACTION:BISMUTH_BRONZE_MAKING] + [PERMITTED_REACTION:ADAMANTINE_WAFERS] ++ [PERMITTED_REACTION:STOKE_BOILER] ++ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_WEAPON] ++ [PERMITTED_REACTION:SPATTER_ADD_EXTRACT_AMMO] + [WORLD_CONSTRUCTION:TUNNEL] + [WORLD_CONSTRUCTION:BRIDGE] + [WORLD_CONSTRUCTION:ROAD] diff --git a/plugins/raw/material_template_default.diff b/plugins/raw/material_template_default.diff new file mode 100644 index 000000000..8b6ef327b --- /dev/null +++ b/plugins/raw/material_template_default.diff @@ -0,0 +1,10 @@ +--- ../objects.old/material_template_default.txt 2012-09-17 17:59:28.907898469 +0400 ++++ material_template_default.txt 2012-09-17 17:59:28.695899382 +0400 +@@ -2374,6 +2374,7 @@ + [MAX_EDGE:500] + [ABSORPTION:100] + [LIQUID_MISC_CREATURE] ++ [REACTION_CLASS:CREATURE_EXTRACT] + [ROTS] + + This is for creatures that are "made of fire". Right now there isn't a good format for that. diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt new file mode 100644 index 000000000..b31d82fa0 --- /dev/null +++ b/plugins/raw/reaction_spatter.txt @@ -0,0 +1,41 @@ +reaction_spatter + +[OBJECT:REACTION] + +Reaction name must start with 'SPATTER_ADD_': + +[REACTION:SPATTER_ADD_EXTRACT_WEAPON] + [NAME:cover weapon with extract] + [BUILDING:CRAFTSMAN:CUSTOM_ALT_V] + [SKILL:DYER] + [ADVENTURE_MODE_ENABLED] + [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:10] + [REACTION_CLASS:CREATURE_EXTRACT] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be the last reagent: + [REAGENT:object:1:WEAPON:NONE:NONE:NONE] + [PRESERVE_REAGENT] + The probability is used as spatter size instead: + [IMPROVEMENT:100:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] + +[REACTION:SPATTER_ADD_EXTRACT_AMMO] + [NAME:cover ammo with extract] + [BUILDING:CRAFTSMAN:CUSTOM_ALT_M] + [SKILL:DYER] + [ADVENTURE_MODE_ENABLED] + [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:10] + [REACTION_CLASS:CREATURE_EXTRACT] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be the last reagent: + [REAGENT:object:1:AMMO:NONE:NONE:NONE] + [PRESERVE_REAGENT] + The probability is used as spatter size instead: + [IMPROVEMENT:100:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] From 2c0a8a9544f81834b0a6e7ac9bf78db8aa5008d1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 18 Sep 2012 00:24:59 +0400 Subject: [PATCH 42/48] Tweak new plugin descriptions in the NEWS document. --- NEWS | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/NEWS b/NEWS index fdc69ac53..68551d385 100644 --- a/NEWS +++ b/NEWS @@ -44,23 +44,30 @@ DFHack v0.34.11-r2 (UNRELEASED) New Dwarf Manipulator plugin: Open the unit list, and press 'l' to access a Dwarf Therapist like UI in the game. New Steam Engine plugin: - Dwarven Water Reactors don't make any sense whatsoever, so this is a potential - replacement for those concerned by it. The plugin detects if a workshop with a + Dwarven Water Reactors don't make any sense whatsoever and cause lag, so this may be + a replacement for those concerned by it. The plugin detects if a workshop with a certain name is in the raws used by the current world, and provides the necessary behavior. See hack/raw/*_steam_engine.txt for the necessary raw definitions. - Note: Stuff like animal treadmills might be more period, but can't be done with dfhack. + Note: Stuff like animal treadmills might be more period, but absolutely can't be + done with tools dfhack has access to. New Power Meter plugin: When activated, implements a pressure plate modification that detects power in gear boxes built on the four adjacent N/S/W/E tiles. The gui/power-meter script implements - the build configuration UI. + the necessary build configuration UI. New Siege Engine plugin: When enabled and configured via gui/siege-engine, allows aiming siege engines - at a designated rectangular area with 360 degree fire range and across Z levels. + at a designated rectangular area with 360 degree fire range and across Z levels; + this works by rewriting the projectile trajectory immediately after it appears. Also supports loading catapults with non-boulder projectiles, taking from a stockpile, and restricting operator skill range like with ordinary workshops. - Disclaimer: not in any way to undermine the future siege update from Toady, but the aiming - logic of existing engines hasn't been updated since 2D, and is almost useless as/is. + Disclaimer: not in any way to undermine the future siege update from Toady, but + the aiming logic of existing engines hasn't been updated since 2D, and is almost + useless above ground :(. Again, things like making siegers bring their own engines + is totally out of the scope of dfhack and can only be done by Toady. New Add Spatter plugin: Detects reactions with certain names in the raws, and changes them from adding improvements to adding item contaminants. This allows directly covering items with poisons. The added spatters are immune both to water and 'clean items'. + Intended to give some use to all those giant cave spider poison barrels brought + by the caravans. + From be928a9dc537290522577cde41211637f3b6f165 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 18 Sep 2012 10:40:14 +0400 Subject: [PATCH 43/48] Fix a data structure integrity bug in VMethodInterposeLinkBase. This causes assertion failure and abort later on. --- library/VTableInterpose.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/library/VTableInterpose.cpp b/library/VTableInterpose.cpp index 583ef5184..046425653 100644 --- a/library/VTableInterpose.cpp +++ b/library/VTableInterpose.cpp @@ -438,6 +438,8 @@ void VMethodInterposeLinkBase::remove() if (next) prev->child_next.insert(next); + else + prev->child_hosts.insert(host); } } From d70a79deb99a3c4ae6458317ee1111928f3db401 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 18 Sep 2012 13:11:11 +0400 Subject: [PATCH 44/48] Follow changes in XML defs. --- library/modules/Gui.cpp | 14 +++++++------- library/xml | 2 +- plugins/add-spatter.cpp | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index b0cfda670..1662f4467 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -332,9 +332,9 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_military) focus += "/" + enum_item_key(screen->page); int cur_list; - if (list1->bright) cur_list = 0; - else if (list2->bright) cur_list = 1; - else if (list3->bright) cur_list = 2; + if (list1->active) cur_list = 0; + else if (list2->active) cur_list = 1; + else if (list3->active) cur_list = 2; else return; switch (screen->page) @@ -420,7 +420,7 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_assigntrade) if (unsigned(list_idx) >= num_lists) return; - if (list1->bright) + if (list1->active) focus += "/Groups"; else focus += "/Items"; @@ -458,10 +458,10 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile) focus += "/On"; - if (list2->bright || list3->bright || screen->list_ids.empty()) { + if (list2->active || list3->active || screen->list_ids.empty()) { focus += "/" + enum_item_key(screen->cur_list); - if (list3->bright) + if (list3->active) focus += (screen->item_names.empty() ? "/None" : "/Item"); } } @@ -844,7 +844,7 @@ static df::item *getAnyItem(df::viewscreen *top) { auto list1 = getLayerList(screen, 0); auto list2 = getLayerList(screen, 1); - if (!list1 || !list2 || !list2->bright) + if (!list1 || !list2 || !list2->active) return NULL; int list_idx = vector_get(screen->visible_lists, list1->cursor, (int16_t)-1); diff --git a/library/xml b/library/xml index 260ff4a1d..8a78bfa21 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 260ff4a1ddcfd54d0143aa6d908a93c4ff709c87 +Subproject commit 8a78bfa218817765b0a80431e0cf25435ffb2179 diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index ed5f47f7b..dda4ca2ba 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -167,7 +167,7 @@ static void index_items(item_table &table, df::job *job, ReactionInfo *info) int ridx = iitem->contains[k]; auto reag = info->react->reagents[ridx]; - if (reag->matches(contents[j], info->react, iitem->reaction_id)) + if (reag->matchesChild(contents[j], info->react, iitem->reaction_id)) table[ridx].push_back(contents[j]); } } From f2e7ee4756813e1f60043d47942d18d4b7b814b5 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 18 Sep 2012 13:15:25 +0400 Subject: [PATCH 45/48] Tweak the add spatter plugin. --- plugins/add-spatter.cpp | 42 +++++++-- plugins/raw/reaction_spatter.txt | 141 ++++++++++++++++++++++++++----- 2 files changed, 155 insertions(+), 28 deletions(-) diff --git a/plugins/add-spatter.cpp b/plugins/add-spatter.cpp index dda4ca2ba..35ea11ef5 100644 --- a/plugins/add-spatter.cpp +++ b/plugins/add-spatter.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -121,20 +122,22 @@ static void find_material(int *type, int *index, df::item *input, MaterialSource } } -static bool has_contaminant(df::item_actual *item, int type, int index) +static int has_contaminant(df::item_actual *item, int type, int index) { auto cont = item->contaminants; if (!cont) - return false; + return 0; + + int size = 0; for (size_t i = 0; i < cont->size(); i++) { auto cur = (*cont)[i]; if (cur->mat_type == type && cur->mat_index == index) - return true; + size += cur->size; } - return false; + return size; } /* @@ -209,7 +212,7 @@ struct item_hook : df::item_constructed { find_material(&mattype, &matindex, material, product.material); - if (mattype < 0 || has_contaminant(this, mattype, matindex)) + if (mattype < 0 || has_contaminant(this, mattype, matindex) >= 50) return false; } @@ -253,15 +256,36 @@ struct product_hook : improvement_product { if (object && (material || !product->material.reagent)) { + using namespace df::enums::improvement_type; + int mattype, matindex; find_material(&mattype, &matindex, material, product->material); + df::matter_state state = matter_state::Liquid; + + switch (improvement_type) + { + case COVERED: + if (flags.is_set(reaction_product_improvement_flags::GLAZED)) + state = matter_state::Solid; + break; + case BANDS: + state = matter_state::Paste; + break; + case SPIKES: + state = matter_state::Powder; + break; + default: + break; + } + + int rating = unit ? Units::getEffectiveSkill(unit, df::job_skill(skill)) : 0; + int size = int(probability*(1.0f + 0.06f*rating)); // +90% at legendary + object->addContaminant( - mattype, matindex, - matter_state::Liquid, // TODO: heuristics or by reagent name + mattype, matindex, state, object->getTemperature(), - probability, // used as size - -1, + size, -1, 0x8000 // not washed by water, and 'clean items' safe. ); } diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt index b31d82fa0..229e531c8 100644 --- a/plugins/raw/reaction_spatter.txt +++ b/plugins/raw/reaction_spatter.txt @@ -4,38 +4,141 @@ reaction_spatter Reaction name must start with 'SPATTER_ADD_': -[REACTION:SPATTER_ADD_EXTRACT_WEAPON] - [NAME:cover weapon with extract] - [BUILDING:CRAFTSMAN:CUSTOM_ALT_V] - [SKILL:DYER] +[REACTION:SPATTER_ADD_OBJECT_LIQUID] + [NAME:coat object with liquid] [ADVENTURE_MODE_ENABLED] - [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE] - [MIN_DIMENSION:10] + [SKILL:WAX_WORKING] + [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:150] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be after the input mat, so that it is known: + [REAGENT:object:1:NONE:NONE:NONE:NONE] + [PRESERVE_REAGENT] + Need some excuse why the spatter is water-resistant: + [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:FAT][UNROTTEN] + The probability is used as spatter size; Legendary gives +90%: + COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder + [IMPROVEMENT:800:object:BANDS:GET_MATERIAL_FROM_REAGENT:extract:NONE] + +[REACTION:SPATTER_ADD_WEAPON_EXTRACT] + [NAME:coat weapon with extract] + [BUILDING:CRAFTSMAN:NONE] + [SKILL:WAX_WORKING] + [REAGENT:extract:150:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:150] [REACTION_CLASS:CREATURE_EXTRACT] [REAGENT:extract container:1:NONE:NONE:NONE:NONE] [CONTAINS:extract] [PRESERVE_REAGENT] [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] - The object to improve must be the last reagent: + The object to improve must be after the input mat, so that it is known: [REAGENT:object:1:WEAPON:NONE:NONE:NONE] [PRESERVE_REAGENT] - The probability is used as spatter size instead: - [IMPROVEMENT:100:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] + Need some excuse why the spatter is water-resistant: + [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN] + The probability is used as spatter size; Legendary gives +90%: + COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder + [IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] -[REACTION:SPATTER_ADD_EXTRACT_AMMO] - [NAME:cover ammo with extract] - [BUILDING:CRAFTSMAN:CUSTOM_ALT_M] - [SKILL:DYER] - [ADVENTURE_MODE_ENABLED] - [REAGENT:extract:10:LIQUID_MISC:NONE:NONE:NONE] - [MIN_DIMENSION:10] +[REACTION:SPATTER_ADD_AMMO_EXTRACT] + [NAME:coat ammo with extract] + [BUILDING:CRAFTSMAN:NONE] + [SKILL:WAX_WORKING] + [REAGENT:extract:50:LIQUID_MISC:NONE:NONE:NONE] + [MIN_DIMENSION:50] + [REACTION_CLASS:CREATURE_EXTRACT] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be after the input mat, so that it is known: + [REAGENT:object:1:AMMO:NONE:NONE:NONE] + [PRESERVE_REAGENT] + Need some excuse why the spatter is water-resistant: + [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN] + The probability is used as spatter size; Legendary gives +90%: + COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder + [IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] + +[REACTION:SPATTER_ADD_WEAPON_GCS] + [NAME:coat weapon with GCS venom] + [BUILDING:CRAFTSMAN:NONE] + [SKILL:WAX_WORKING] + [REAGENT:extract:150:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] + [MIN_DIMENSION:150] + [REACTION_CLASS:CREATURE_EXTRACT] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be after the input mat, so that it is known: + [REAGENT:object:1:WEAPON:NONE:NONE:NONE] + [PRESERVE_REAGENT] + Need some excuse why the spatter is water-resistant: + [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN] + The probability is used as spatter size; Legendary gives +90%: + COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder + [IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] + +[REACTION:SPATTER_ADD_AMMO_GCS] + [NAME:coat ammo with GCS venom] + [BUILDING:CRAFTSMAN:NONE] + [SKILL:WAX_WORKING] + [REAGENT:extract:50:LIQUID_MISC:NONE:CAVE_SPIDER_GIANT:POISON] + [MIN_DIMENSION:50] + [REACTION_CLASS:CREATURE_EXTRACT] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be after the input mat, so that it is known: + [REAGENT:object:1:AMMO:NONE:NONE:NONE] + [PRESERVE_REAGENT] + Need some excuse why the spatter is water-resistant: + [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN] + The probability is used as spatter size; Legendary gives +90%: + COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder + [IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] + +[REACTION:SPATTER_ADD_WEAPON_GDS] + [NAME:coat weapon with GDS venom] + [BUILDING:CRAFTSMAN:NONE] + [SKILL:WAX_WORKING] + [REAGENT:extract:150:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] + [MIN_DIMENSION:150] + [REACTION_CLASS:CREATURE_EXTRACT] + [REAGENT:extract container:1:NONE:NONE:NONE:NONE] + [CONTAINS:extract] + [PRESERVE_REAGENT] + [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] + The object to improve must be after the input mat, so that it is known: + [REAGENT:object:1:WEAPON:NONE:NONE:NONE] + [PRESERVE_REAGENT] + Need some excuse why the spatter is water-resistant: + [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN] + The probability is used as spatter size; Legendary gives +90%: + COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder + [IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] + +[REACTION:SPATTER_ADD_AMMO_GDS] + [NAME:coat ammo with GDS venom] + [BUILDING:CRAFTSMAN:NONE] + [SKILL:WAX_WORKING] + [REAGENT:extract:50:LIQUID_MISC:NONE:SCORPION_DESERT_GIANT:POISON] + [MIN_DIMENSION:50] [REACTION_CLASS:CREATURE_EXTRACT] [REAGENT:extract container:1:NONE:NONE:NONE:NONE] [CONTAINS:extract] [PRESERVE_REAGENT] [DOES_NOT_DETERMINE_PRODUCT_AMOUNT] - The object to improve must be the last reagent: + The object to improve must be after the input mat, so that it is known: [REAGENT:object:1:AMMO:NONE:NONE:NONE] [PRESERVE_REAGENT] - The probability is used as spatter size instead: - [IMPROVEMENT:100:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] + Need some excuse why the spatter is water-resistant: + [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:TALLOW][UNROTTEN] + The probability is used as spatter size; Legendary gives +90%: + COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder + [IMPROVEMENT:200:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] From a7998f71a2ee95d2d21f34468761118fd6b8585f Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 18 Sep 2012 17:39:37 +0400 Subject: [PATCH 46/48] Add a tweak workaround for the issue with container reactions in advmode. --- NEWS | 2 + dfhack.init-example | 4 ++ plugins/raw/reaction_spatter.txt | 2 +- plugins/tweak.cpp | 71 ++++++++++++++++++++++++++++++++ 4 files changed, 78 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 68551d385..22f64d7d6 100644 --- a/NEWS +++ b/NEWS @@ -25,6 +25,8 @@ DFHack v0.34.11-r2 (UNRELEASED) - tweak readable-build-plate: fix unreadable truncation in unit pressure plate build ui. - tweak stable-temp: fixes bug 6012; may improve FPS by 50-100% on a slow item-heavy fort. - tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster. + - tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc. + - tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode. New scripts: - fixnaked: removes thoughts about nakedness. - setfps: set FPS cap at runtime, in case you want slow motion or speed-up. diff --git a/dfhack.init-example b/dfhack.init-example index b8b53cad7..83c3641b6 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -86,3 +86,7 @@ tweak fast-heat 500 # stop stacked liquid/bar/thread/cloth items from lasting forever # if used in reactions that use only a fraction of the dimension. tweak fix-dimensions + +# make reactions requiring containers usable in advmode - the issue is +# that the screen asks for those reagents to be selected directly +tweak advmode-contained diff --git a/plugins/raw/reaction_spatter.txt b/plugins/raw/reaction_spatter.txt index 229e531c8..085be7fdd 100644 --- a/plugins/raw/reaction_spatter.txt +++ b/plugins/raw/reaction_spatter.txt @@ -21,7 +21,7 @@ Reaction name must start with 'SPATTER_ADD_': [REAGENT:grease:1:GLOB:NONE:NONE:NONE][REACTION_CLASS:FAT][UNROTTEN] The probability is used as spatter size; Legendary gives +90%: COVERED = liquid, GLAZED = solid, BANDS = paste, SPIKES = powder - [IMPROVEMENT:800:object:BANDS:GET_MATERIAL_FROM_REAGENT:extract:NONE] + [IMPROVEMENT:800:object:COVERED:GET_MATERIAL_FROM_REAGENT:extract:NONE] [REACTION:SPATTER_ADD_WEAPON_EXTRACT] [NAME:coat weapon with extract] diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 4fef285f9..fb286e0d7 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -29,6 +29,7 @@ #include "df/criminal_case.h" #include "df/unit_inventory_item.h" #include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_layer_unit_actionst.h" #include "df/squad_order_trainst.h" #include "df/ui_build_selector.h" #include "df/building_trapst.h" @@ -38,6 +39,10 @@ #include "df/item_threadst.h" #include "df/item_clothst.h" #include "df/contaminant.h" +#include "df/layer_object.h" +#include "df/reaction.h" +#include "df/reaction_reagent_itemst.h" +#include "df/reaction_reagent_flags.h" #include @@ -100,6 +105,10 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector *input)) + { + auto old_reaction = cur_reaction; + auto old_reagent = reagent; + + INTERPOSE_NEXT(feed)(input); + + if (cur_reaction && (cur_reaction != old_reaction || reagent != old_reagent)) + { + old_reagent = reagent; + + // Skip reagents already contained by others + while (reagent < (int)cur_reaction->reagents.size()-1) + { + if (!cur_reaction->reagents[reagent]->flags.bits.IN_CONTAINER) + break; + reagent++; + } + + if (old_reagent != reagent) + { + // Reproduces a tiny part of the orginal screen code + choice_items.clear(); + + auto preagent = cur_reaction->reagents[reagent]; + reagent_amnt_left = preagent->quantity; + + for (int i = held_items.size()-1; i >= 0; i--) + { + if (!preagent->matchesRoot(held_items[i], cur_reaction->index)) + continue; + if (linear_index(sel_items, held_items[i]) >= 0) + continue; + choice_items.push_back(held_items[i]); + } + + layer_objects[6]->setListLength(choice_items.size()); + + if (!choice_items.empty()) + { + layer_objects[4]->active = layer_objects[5]->active = false; + layer_objects[6]->active = true; + } + else if (layer_objects[6]->active) + { + layer_objects[6]->active = false; + layer_objects[5]->active = true; + } + } + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(advmode_contained_hook, feed); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -582,6 +649,10 @@ static command_result tweak(color_ostream &out, vector ¶meters) enable_hook(out, INTERPOSE_HOOK(dimension_thread_hook, subtractDimension), parameters); enable_hook(out, INTERPOSE_HOOK(dimension_cloth_hook, subtractDimension), parameters); } + else if (cmd == "advmode-contained") + { + enable_hook(out, INTERPOSE_HOOK(advmode_contained_hook, feed), parameters); + } else return CR_WRONG_USAGE; From 57b72831ca700ab556566a85f2245e014ca96c30 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 18 Sep 2012 20:30:25 +0400 Subject: [PATCH 47/48] Overhaul the concept of lua 'class' initialization yet again. --- library/lua/class.lua | 150 ++++++++++++++++++++++++++++++++++ library/lua/dfhack.lua | 24 ++---- library/lua/gui.lua | 33 +++++--- library/lua/gui/dialogs.lua | 99 +++++++++++----------- library/lua/gui/dwarfmode.lua | 4 +- scripts/gui/hello-world.lua | 20 +++-- scripts/gui/liquids.lua | 25 ++---- scripts/gui/mechanisms.lua | 10 +-- scripts/gui/power-meter.lua | 8 +- scripts/gui/room-list.lua | 23 +++--- scripts/gui/siege-engine.lua | 39 +++++---- 11 files changed, 285 insertions(+), 150 deletions(-) create mode 100644 library/lua/class.lua diff --git a/library/lua/class.lua b/library/lua/class.lua new file mode 100644 index 000000000..7b142e499 --- /dev/null +++ b/library/lua/class.lua @@ -0,0 +1,150 @@ +-- A trivial reloadable class system + +local _ENV = mkmodule('class') + +-- Metatable template for a class +class_obj = {} or class_obj + +-- Methods shared by all classes +common_methods = {} or common_methods + +-- Forbidden names for class fields and methods. +reserved_names = { super = true, ATTRS = true } + +-- Attribute table metatable +attrs_meta = {} or attrs_meta + +-- Create or updates a class; a class has metamethods and thus own metatable. +function defclass(class,parent) + class = class or {} + + local meta = getmetatable(class) + if not meta then + meta = {} + setmetatable(class, meta) + end + + for k,v in pairs(class_obj) do meta[k] = v end + + meta.__index = parent or common_methods + + local attrs = rawget(class, 'ATTRS') or {} + setmetatable(attrs, attrs_meta) + + rawset(class, 'super', parent) + rawset(class, 'ATTRS', attrs) + rawset(class, '__index', rawget(class, '__index') or class) + + return class +end + +-- An instance uses the class as metatable +function mkinstance(class,table) + table = table or {} + setmetatable(table, class) + return table +end + +-- Patch the stubs in the global environment +dfhack.BASE_G.defclass = _ENV.defclass +dfhack.BASE_G.mkinstance = _ENV.mkinstance + +-- Just verify the name, and then set. +function class_obj:__newindex(name,val) + if reserved_names[name] or common_methods[name] then + error('Method name '..name..' is reserved.') + end + rawset(self, name, val) +end + +function attrs_meta:__call(attrs) + for k,v in pairs(attrs) do + self[k] = v + end +end + +local function apply_attrs(obj, attrs, init_table) + for k,v in pairs(attrs) do + if v == DEFAULT_NIL then + v = nil + end + obj[k] = init_table[k] or v + end +end + +local function invoke_before_rec(self, class, method, ...) + local meta = getmetatable(class) + if meta then + local fun = rawget(class, method) + if fun then + fun(self, ...) + end + + invoke_before_rec(self, meta.__index, method, ...) + end +end + +local function invoke_after_rec(self, class, method, ...) + local meta = getmetatable(class) + if meta then + invoke_after_rec(self, meta.__index, method, ...) + + local fun = rawget(class, method) + if fun then + fun(self, ...) + end + end +end + +local function init_attrs_rec(obj, class, init_table) + local meta = getmetatable(class) + if meta then + init_attrs_rec(obj, meta.__index, init_table) + apply_attrs(obj, rawget(class, 'ATTRS'), init_table) + end +end + +-- Call metamethod constructs the object +function class_obj:__call(init_table) + -- The table is assumed to be a scratch temporary. + -- If it is not, copy it yourself before calling. + init_table = init_table or {} + + local obj = mkinstance(self) + + -- This initialization sequence is broadly based on how the + -- Common Lisp initialize-instance generic function works. + + -- preinit screens input arguments in subclass to superclass order + invoke_before_rec(obj, self, 'preinit', init_table) + -- initialize the instance table from init table + init_attrs_rec(obj, self, init_table) + -- init in superclass -> subclass + invoke_after_rec(obj, self, 'init', init_table) + -- postinit in superclass -> subclass + invoke_after_rec(obj, self, 'postinit', init_table) + + return obj +end + +-- Common methods for all instances: + +function common_methods:callback(method, ...) + return dfhack.curry(self[method], self, ...) +end + +function common_methods:assign(data) + for k,v in pairs(data) do + self[k] = v + end +end + +function common_methods:invoke_before(method, ...) + return invoke_before_rec(self, getmetatable(self), method, ...) +end + +function common_methods:invoke_after(method, ...) + return invoke_after_rec(self, getmetatable(self), method, ...) +end + +return _ENV diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index baf0d42e0..ce3be5a87 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -113,26 +113,14 @@ function rawset_default(target,source) end end -function defclass(class,parent) - class = class or {} - rawset_default(class, { __index = class }) - if parent then - setmetatable(class, parent) - else - rawset_default(class, { - init_fields = rawset_default, - callback = function(self, name, ...) - return dfhack.curry(self[name], self, ...) - end - }) - end - return class +DEFAULT_NIL = DEFAULT_NIL or {} -- Unique token + +function defclass(...) + return require('class').defclass(...) end -function mkinstance(class,table) - table = table or {} - setmetatable(table, class) - return table +function mkinstance(...) + return require('class').mkinstance(...) end -- Misc functions diff --git a/library/lua/gui.lua b/library/lua/gui.lua index f9b6ab6d2..6eaa98606 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -74,18 +74,23 @@ end Painter = defclass(Painter, nil) -function Painter.new(rect, pen) - rect = rect or mkdims_wh(0,0,dscreen.getWindowSize()) - local self = { - x1 = rect.x1, clip_x1 = rect.x1, - y1 = rect.y1, clip_y1 = rect.y1, - x2 = rect.x2, clip_x2 = rect.x2, - y2 = rect.y2, clip_y2 = rect.y2, +function Painter:init(args) + local rect = args.rect or mkdims_wh(0,0,dscreen.getWindowSize()) + local crect = args.clip_rect or rect + self:assign{ + x = rect.x1, y = rect.y1, + x1 = rect.x1, clip_x1 = crect.x1, + y1 = rect.y1, clip_y1 = crect.y1, + x2 = rect.x2, clip_x2 = crect.x2, + y2 = rect.y2, clip_y2 = crect.y2, width = rect.x2-rect.x1+1, height = rect.y2-rect.y1+1, - cur_pen = to_pen(nil, pen or COLOR_GREY) + cur_pen = to_pen(nil, args.pen or COLOR_GREY) } - return mkinstance(Painter, self):seek(0,0) +end + +function Painter.new(rect, pen) + return Painter{ rect = rect, pen = pen } end function Painter:isValidPos() @@ -213,9 +218,8 @@ Screen = defclass(Screen) Screen.text_input_mode = false -function Screen:init() +function Screen:postinit() self:updateLayout() - return self end Screen.isDismissed = dscreen.isDismissed @@ -344,7 +348,12 @@ end FramedScreen = defclass(FramedScreen, Screen) -FramedScreen.frame_style = BOUNDARY_FRAME +FramedScreen.ATTRS{ + frame_style = BOUNDARY_FRAME, + frame_title = DEFAULT_NIL, + frame_width = DEFAULT_NIL, + frame_height = DEFAULT_NIL, +} local function hint_coord(gap,hint) if hint and hint > 0 then diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index eb883465f..b1a96a558 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -10,24 +10,21 @@ local dscreen = dfhack.screen MessageBox = defclass(MessageBox, gui.FramedScreen) MessageBox.focus_path = 'MessageBox' -MessageBox.frame_style = gui.GREY_LINE_FRAME - -function MessageBox:init(info) - info = info or {} - self:init_fields{ - text = info.text or {}, - frame_title = info.title, - frame_width = info.frame_width, - on_accept = info.on_accept, - on_cancel = info.on_cancel, - on_close = info.on_close, - text_pen = info.text_pen - } - if type(self.text) == 'string' then - self.text = utils.split_string(self.text, "\n") + +MessageBox.ATTRS{ + frame_style = gui.GREY_LINE_FRAME, + -- new attrs + text = {}, + on_accept = DEFAULT_NIL, + on_cancel = DEFAULT_NIL, + on_close = DEFAULT_NIL, + text_pen = DEFAULT_NIL, +} + +function MessageBox:preinit(info) + if type(info.text) == 'string' then + info.text = utils.split_string(info.text, "\n") end - gui.FramedScreen.init(self, info) - return self end function MessageBox:getWantedFrameSize() @@ -82,9 +79,8 @@ function MessageBox:onInput(keys) end function showMessage(title, text, tcolor, on_close) - mkinstance(MessageBox):init{ - text = text, - title = title, + MessageBox{ + frame_title = title, text = text, text_pen = tcolor, on_close = on_close @@ -92,8 +88,8 @@ function showMessage(title, text, tcolor, on_close) end function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel) - mkinstance(MessageBox):init{ - title = title, + MessageBox{ + frame_title = title, text = text, text_pen = tcolor, on_accept = on_accept, @@ -105,25 +101,23 @@ InputBox = defclass(InputBox, MessageBox) InputBox.focus_path = 'InputBox' -function InputBox:init(info) - info = info or {} - self:init_fields{ - input = info.input or '', - input_pen = info.input_pen, - on_input = info.on_input, - } - MessageBox.init(self, info) - self.on_accept = nil - return self +InputBox.ATTRS{ + input = '', + input_pen = DEFAULT_NIL, + on_input = DEFAULT_NIL, +} + +function InputBox:preinit(info) + info.on_accept = nil end function InputBox:getWantedFrameSize() - local mw, mh = MessageBox.getWantedFrameSize(self) + local mw, mh = InputBox.super.getWantedFrameSize(self) return mw, mh+2 end function InputBox:onRenderBody(dc) - MessageBox.onRenderBody(self, dc) + InputBox.super.onRenderBody(self, dc) dc:newline(1) dc:pen(self.input_pen or COLOR_LIGHTCYAN) @@ -161,8 +155,8 @@ function InputBox:onInput(keys) end function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) - mkinstance(InputBox):init{ - title = title, + InputBox{ + frame_title = title, text = text, text_pen = tcolor, input = input, @@ -176,27 +170,28 @@ ListBox = defclass(ListBox, MessageBox) ListBox.focus_path = 'ListBox' +ListBox.ATTRS{ + selection = 0, + choices = {}, + select_pen = DEFAULT_NIL, + on_input = DEFAULT_NIL +} + +function InputBox:preinit(info) + info.on_accept = nil +end + function ListBox:init(info) - info = info or {} - self:init_fields{ - selection = info.selection or 0, - choices = info.choices or {}, - select_pen = info.select_pen, - on_input = info.on_input, - page_top = 0 - } - MessageBox.init(self, info) - self.on_accept = nil - return self + self.page_top = 0 end function ListBox:getWantedFrameSize() - local mw, mh = MessageBox.getWantedFrameSize(self) + local mw, mh = ListBox.super.getWantedFrameSize(self) return mw, mh+#self.choices end function ListBox:onRenderBody(dc) - MessageBox.onRenderBody(self, dc) + ListBox.super.onRenderBody(self, dc) dc:newline(1) @@ -220,6 +215,7 @@ function ListBox:onRenderBody(dc) end end end + function ListBox:moveCursor(delta) local newsel=self.selection+delta if #self.choices ~=0 then @@ -229,6 +225,7 @@ function ListBox:moveCursor(delta) end self.selection=newsel end + function ListBox:onInput(keys) if keys.SELECT then self:dismiss() @@ -257,8 +254,8 @@ function ListBox:onInput(keys) end function showListPrompt(title, text, tcolor, choices, on_input, on_cancel, min_width) - mkinstance(ListBox):init{ - title = title, + ListBox{ + frame_title = title, text = text, text_pen = tcolor, choices = choices, diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 661e15591..ba3cfbe6c 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -353,7 +353,7 @@ end MenuOverlay = defclass(MenuOverlay, DwarfOverlay) function MenuOverlay:updateLayout() - DwarfOverlay.updateLayout(self) + MenuOverlay.super.updateLayout(self) self.frame_rect = self.df_layout.menu end @@ -361,7 +361,7 @@ MenuOverlay.getWindowSize = gui.FramedScreen.getWindowSize MenuOverlay.getMousePos = gui.FramedScreen.getMousePos function MenuOverlay:onAboutToShow(below) - DwarfOverlay.onAboutToShow(self,below) + MenuOverlay.super.onAboutToShow(self,below) self:updateLayout() if not self.df_layout.menu then diff --git a/scripts/gui/hello-world.lua b/scripts/gui/hello-world.lua index 80986bbf6..c8cd3bd01 100644 --- a/scripts/gui/hello-world.lua +++ b/scripts/gui/hello-world.lua @@ -4,19 +4,21 @@ local gui = require 'gui' local text = 'Woohoo, lua viewscreen :)' -local screen = mkinstance(gui.FramedScreen, { +local screen = gui.FramedScreen{ frame_style = gui.GREY_LINE_FRAME, frame_title = 'Hello World', frame_width = #text+6, frame_height = 3, - onRenderBody = function(self, dc) - dc:seek(3,1):string(text, COLOR_LIGHTGREEN) - end, - onInput = function(self,keys) - if keys.LEAVESCREEN or keys.SELECT then - self:dismiss() - end +} + +function screen:onRenderBody(dc) + dc:seek(3,1):string(text, COLOR_LIGHTGREEN) +end + +function screen:onInput(keys) + if keys.LEAVESCREEN or keys.SELECT then + self:dismiss() end -}):init() +end screen:show() diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua index 89f08b7cf..cddb9f01d 100644 --- a/scripts/gui/liquids.lua +++ b/scripts/gui/liquids.lua @@ -53,13 +53,7 @@ local permaflows = { Toggle = defclass(Toggle) -function Toggle:init(items) - self:init_fields{ - items = items, - selected = 1 - } - return self -end +Toggle.ATTRS{ items = {}, selected = 1 } function Toggle:get() return self.items[self.selected] @@ -89,16 +83,14 @@ LiquidsUI = defclass(LiquidsUI, guidm.MenuOverlay) LiquidsUI.focus_path = 'liquids' function LiquidsUI:init() - self:init_fields{ - brush = mkinstance(Toggle):init(brushes), - paint = mkinstance(Toggle):init(paints), - flow = mkinstance(Toggle):init(flowbits), - set = mkinstance(Toggle):init(setmode), - permaflow = mkinstance(Toggle):init(permaflows), + self:assign{ + brush = Toggle{ items = brushes }, + paint = Toggle{ items = paints }, + flow = Toggle{ items = flowbits }, + set = Toggle{ items = setmode }, + permaflow = Toggle{ items = permaflows }, amount = 7, } - guidm.MenuOverlay.init(self) - return self end function LiquidsUI:onDestroy() @@ -201,6 +193,7 @@ function LiquidsUI:onRenderBody(dc) end function ensure_blocks(cursor, size, cb) + size = size or xyz2pos(1,1,1) local cx,cy,cz = pos2xyz(cursor) local all = true for x=1,size.x or 1,16 do @@ -298,5 +291,5 @@ if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/LookAround') then qerror("This script requires the main dwarfmode view in 'k' mode") end -local list = mkinstance(LiquidsUI):init() +local list = LiquidsUI() list:show() diff --git a/scripts/gui/mechanisms.lua b/scripts/gui/mechanisms.lua index c14bfcbe9..d1e8ec803 100644 --- a/scripts/gui/mechanisms.lua +++ b/scripts/gui/mechanisms.lua @@ -43,13 +43,11 @@ MechanismList = defclass(MechanismList, guidm.MenuOverlay) MechanismList.focus_path = 'mechanisms' -function MechanismList:init(building) - self:init_fields{ +function MechanismList:init(info) + self:assign{ links = {}, selected = 1 } - guidm.MenuOverlay.init(self) - self:fillList(building) - return self + self:fillList(info.building) end function MechanismList:fillList(building) @@ -126,6 +124,6 @@ if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') t qerror("This script requires the main dwarfmode view in 'q' mode") end -local list = mkinstance(MechanismList):init(df.global.world.selected_building) +local list = MechanismList{ building = df.global.world.selected_building } list:show() list:changeSelected(1) diff --git a/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua index 8baf43e7e..6c2f699ac 100644 --- a/scripts/gui/power-meter.lua +++ b/scripts/gui/power-meter.lua @@ -13,15 +13,13 @@ PowerMeter = defclass(PowerMeter, guidm.MenuOverlay) PowerMeter.focus_path = 'power-meter' function PowerMeter:init() - self:init_fields{ + self:assign{ min_power = 0, max_power = -1, invert = false, } - guidm.MenuOverlay.init(self) - return self end function PowerMeter:onShow() - guidm.MenuOverlay.onShow(self) + PowerMeter.super.onShow(self) -- Send an event to update the errors bselector.plate_info.flags.whole = 0 @@ -112,5 +110,5 @@ then qerror("This script requires the main dwarfmode view in build pressure plate mode") end -local list = mkinstance(PowerMeter):init() +local list = PowerMeter() list:show() diff --git a/scripts/gui/room-list.lua b/scripts/gui/room-list.lua index a4507466f..0de82db5f 100644 --- a/scripts/gui/room-list.lua +++ b/scripts/gui/room-list.lua @@ -78,15 +78,17 @@ RoomList = defclass(RoomList, guidm.MenuOverlay) RoomList.focus_path = 'room-list' -function RoomList:init(unit) +RoomList.ATTRS{ unit = DEFAULT_NIL } + +function RoomList:init(info) + local unit = info.unit local base_bld = df.global.world.selected_building - self:init_fields{ - unit = unit, base_building = base_bld, + self:assign{ + base_building = base_bld, items = {}, selected = 1, own_rooms = {}, spouse_rooms = {} } - guidm.MenuOverlay.init(self) self.old_viewport = self:getViewport() self.old_cursor = guidm.getCursorPos() @@ -115,8 +117,6 @@ function RoomList:init(unit) self.items = concat_lists({self.base_item}, self.items) ::found:: end - - return self end local sex_char = { [0] = 12, [1] = 11 } @@ -235,12 +235,13 @@ function RoomList:onInput(keys) end local focus = dfhack.gui.getCurFocus() -if focus == 'dwarfmode/QueryBuilding/Some' then - local base = df.global.world.selected_building - mkinstance(RoomList):init(base.owner):show() -elseif focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then + +if focus == 'dwarfmode/QueryBuilding/Some/Assign/Unit' then local unit = df.global.ui_building_assign_units[df.global.ui_building_item_cursor] - mkinstance(RoomList):init(unit):show() + RoomList{ unit = unit }:show() +elseif string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some') then + local base = df.global.world.selected_building + RoomList{ unit = base.owner }:show() else qerror("This script requires the main dwarfmode view in 'q' mode") end diff --git a/scripts/gui/siege-engine.lua b/scripts/gui/siege-engine.lua index 7a76d7673..c98cb1676 100644 --- a/scripts/gui/siege-engine.lua +++ b/scripts/gui/siege-engine.lua @@ -34,30 +34,29 @@ SiegeEngine = defclass(SiegeEngine, guidm.MenuOverlay) SiegeEngine.focus_path = 'siege-engine' -function SiegeEngine:init(building) - self:init_fields{ - building = building, - center = utils.getBuildingCenter(building), +SiegeEngine.ATTRS{ building = DEFAULT_NIL } + +function SiegeEngine:init() + self:assign{ + center = utils.getBuildingCenter(self.building), selected_pile = 1, + mode_main = { + render = self:callback 'onRenderBody_main', + input = self:callback 'onInput_main', + }, + mode_aim = { + render = self:callback 'onRenderBody_aim', + input = self:callback 'onInput_aim', + }, + mode_pile = { + render = self:callback 'onRenderBody_pile', + input = self:callback 'onInput_pile', + } } - guidm.MenuOverlay.init(self) - self.mode_main = { - render = self:callback 'onRenderBody_main', - input = self:callback 'onInput_main', - } - self.mode_aim = { - render = self:callback 'onRenderBody_aim', - input = self:callback 'onInput_aim', - } - self.mode_pile = { - render = self:callback 'onRenderBody_pile', - input = self:callback 'onInput_pile', - } - return self end function SiegeEngine:onShow() - guidm.MenuOverlay.onShow(self) + SiegeEngine.super.onShow(self) self.old_cursor = guidm.getCursorPos() self.old_viewport = self:getViewport() @@ -487,5 +486,5 @@ if not df.building_siegeenginest:is_instance(building) then qerror("A siege engine must be selected") end -local list = mkinstance(SiegeEngine):init(df.global.world.selected_building) +local list = SiegeEngine{ building = building } list:show() From a4799a384b2f33be5b3fcf43b462175de6ce7f65 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 18 Sep 2012 20:45:59 +0400 Subject: [PATCH 48/48] Catch C++ exceptions in dfhack.buildings.setSize --- library/LuaApi.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index f8497569e..d73d14cf9 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1084,7 +1084,9 @@ static int buildings_getCorrectSize(lua_State *state) return 5; } -static int buildings_setSize(lua_State *state) +namespace { + +int buildings_setSize(lua_State *state) { auto bld = Lua::CheckDFObject(state, 1); df::coord2d size(luaL_optint(state, 2, 1), luaL_optint(state, 3, 1)); @@ -1105,11 +1107,13 @@ static int buildings_setSize(lua_State *state) return 1; } +} + static const luaL_Reg dfhack_buildings_funcs[] = { { "findAtTile", buildings_findAtTile }, { "findCivzonesAt", buildings_findCivzonesAt }, { "getCorrectSize", buildings_getCorrectSize }, - { "setSize", buildings_setSize }, + { "setSize", &Lua::CallWithCatchWrapper }, { NULL, NULL } };