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/36] 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/36] 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/36] 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/36] 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 9c6fcee9a9b03b42648152a549fc019f05fff468 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 11:23:00 +0400 Subject: [PATCH 05/36] Add steam engine documentation, and use barrel quality in efficiency calc. --- plugins/devel/item_trapcomp_steam_engine.txt | 4 +- plugins/devel/steam-engine.cpp | 82 +++++++++++++++++++- 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/plugins/devel/item_trapcomp_steam_engine.txt b/plugins/devel/item_trapcomp_steam_engine.txt index bae6f5b22..c35f6ef45 100644 --- a/plugins/devel/item_trapcomp_steam_engine.txt +++ b/plugins/devel/item_trapcomp_steam_engine.txt @@ -4,8 +4,8 @@ item_trapcomp_steam_engine [ITEM_TRAPCOMP:ITEM_TRAPCOMP_STEAM_PISTON] [NAME:piston:pistons] -[ADJECTIVE:huge] -[SIZE:1600] +[ADJECTIVE:heavy] +[SIZE:1800] [HITS:1] [MATERIAL_SIZE:6] [METAL] diff --git a/plugins/devel/steam-engine.cpp b/plugins/devel/steam-engine.cpp index 5c344f788..cacfc6e16 100644 --- a/plugins/devel/steam-engine.cpp +++ b/plugins/devel/steam-engine.cpp @@ -34,6 +34,82 @@ #include "MiscUtils.h" +/* + * This plugin implements a steam engine workshop. It activates + * if there are any workshops in the raws with STEAM_ENGINE in + * their token, and provides the necessary behavior. + * + * Construction: + * + * The workshop needs water as its input, which it takes via a + * passable floor tile below it, like usual magma workshops do. + * The magma version also needs magma. + * + * ISSUE: Since this building is a machine, and machine collapse + * code cannot be modified, it would collapse over true open space. + * As a loophole, down stair provides support to machines, while + * being passable, so use them. + * + * After constructing the building itself, machines can be connected + * to the edge tiles that look like gear boxes. Their exact position + * is extracted from the workshop raws. + * + * ISSUE: Like with collapse above, part of the code involved in + * machine connection cannot be modified. As a result, the workshop + * can only immediately connect to machine components built AFTER it. + * This also means that engines cannot be chained without intermediate + * short axles that can be built later. + * + * Operation: + * + * In order to operate the engine, queue the Stoke Boiler job. + * A furnace operator will come, possibly bringing a bar of fuel, + * and perform it. As a result, a "boiling water" item will appear + * in the 't' view of the workshop. + * + * Note: The completion of the job will actually consume one unit + * of appropriate liquids from below the workshop. + * + * Every such item gives 100 power, up to a limit of 300 for coal, + * and 500 for a magma engine. The building can host twice that + * amount of items to provide longer autonomous running. When the + * boiler gets filled to capacity, all queued jobs are suspended; + * once it drops back to 3+1 or 5+1 items, they are re-enabled. + * + * While the engine is providing power, steam is being consumed. + * The consumption speed includes a fixed 10% waste rate, and + * the remaining 90% are applied proportionally to the actual + * load in the machine. With the engine at nominal 300 power with + * 150 load in the system, it will consume steam for actual + * 300*(10% + 90%*150/300) = 165 power. + * + * Masterpiece mechanism and chain will decrease the mechanical + * power drawn by the engine itself from 10 to 5. Masterpiece + * barrel decreases waste rate by 4%. Masterpiece piston and pipe + * decrease it by further 4%, and also decrease the whole steam + * use rate by 10%. + * + * Explosions: + * + * The engine must be constructed using barrel, pipe and piston + * from fire-safe, or in the magma version magma-safe metals. + * + * During operation weak parts get gradually worn out, and + * eventually the engine explodes. It should also explode if + * toppled during operation by a building destroyer, or a + * tantruming dwarf. + * + * Save files: + * + * It should be safe to load and view fortresses using engines + * from a DF version without DFHack installed, except that in such + * case the engines won't work. However actually making modifications + * to them, or machines they connect to (including by pulling levers), + * can easily result in inconsistent state once this plugin is + * available again. The effects may be as weird as negative power + * being generated. + */ + using std::vector; using std::string; using std::stack; @@ -465,9 +541,11 @@ struct workshop_hook : df::building_workshopst { power_rate = 0.0f; } // waste rate: 1-10% depending on piston assembly quality - float waste = 0.1f - 0.015f * get_component_quality(1); + float piston_qual = get_component_quality(1); + float waste = 0.1f - 0.016f * 0.5f * (piston_qual + get_component_quality(2)); + float efficiency_coeff = 1.0f - 0.02f * piston_qual; // apply rate and waste factor - ticks *= (waste + 0.9f*power_rate)*power_level; + ticks *= (waste + 0.9f*power_rate)*power_level*efficiency_coeff; // end result return std::max(1, int(ticks)); } From 27f169e298e658f3957aa2db1f76fe8aa20caef7 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 17:37:36 +0400 Subject: [PATCH 06/36] Provide a partial application utility function to lua. Implemented in C++ for efficiency. --- LUA_API.rst | 10 ++++ Lua API.html | 107 +++++++++++++++++++++++------------------ library/LuaTools.cpp | 34 +++++++++++++ library/lua/dfhack.lua | 8 ++- 4 files changed, 110 insertions(+), 49 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 542034f40..22130efd6 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -550,6 +550,16 @@ Exception handling The default value of the ``verbose`` argument of ``err:tostring()``. +Miscellaneous +------------- + +* ``dfhack.curry(func,args...)``, or ``curry(func,args...)`` + + Returns a closure that invokes the function with args combined + both from the curry call and the closure call itself. I.e. + ``curry(func,a,b)(c,d)`` equals ``func(a,b,c,d)``. + + Locking and finalization ------------------------ diff --git a/Lua API.html b/Lua API.html index 63a4c8547..f6f2d42b3 100644 --- a/Lua API.html +++ b/Lua API.html @@ -337,42 +337,43 @@ ul.auto-toc {
  • Native utilities
  • -
  • C++ function wrappers
    -

    Gui module

    +

    Gui module

    • dfhack.gui.getCurViewscreen([skip_dismissed])

      Returns the topmost viewscreen. If skip_dismissed is true, @@ -1032,7 +1043,7 @@ above operations accordingly. If enabled, pauses and zooms to position.

    -

    Job module

    +

    Job module

    • dfhack.job.cloneJobStruct(job)

      Creates a deep copy of the given job.

      @@ -1069,7 +1080,7 @@ a lua list containing them.

    -

    Units module

    +

    Units module

    • dfhack.units.getPosition(unit)

      Returns true x,y,z of the unit, or nil if invalid; may be not equal to unit.pos if caged.

      @@ -1130,7 +1141,7 @@ or raws. The ignore_noble boolean disables the
    -

    Items module

    +

    Items module

    • dfhack.items.getPosition(item)

      Returns true x,y,z of the item, or nil if invalid; may be not equal to item.pos if in inventory.

      @@ -1173,7 +1184,7 @@ Returns false in case of error.

    -

    Maps module

    +

    Maps module

    • dfhack.maps.getSize()

      Returns map size in blocks: x, y, z

      @@ -1221,7 +1232,7 @@ burrows, or the presence of invaders.

    -

    Burrows module

    +

    Burrows module

    • dfhack.burrows.findByName(name)

      Returns the burrow pointer or nil.

      @@ -1256,7 +1267,7 @@ burrows, or the presence of invaders.

    -

    Buildings module

    +

    Buildings module

    • dfhack.buildings.setOwner(item,unit)

      Replaces the owner of the building. If unit is nil, removes ownership. @@ -1400,7 +1411,7 @@ can be determined this way, constructBuilding

    -

    Constructions module

    +

    Constructions module

    • dfhack.constructions.designateNew(pos,type,item_type,mat_index)

      Designates a new construction at given position. If there already is @@ -1416,7 +1427,7 @@ Returns true, was_only_planned if removed; or false if none fo

    -

    Screen API

    +

    Screen API

    The screen module implements support for drawing to the tiled screen of the game. Note that drawing only has any effect when done from callbacks, so it can only be feasibly used in the core context.

    @@ -1555,7 +1566,7 @@ options; if multiple interpretations exist, the table will contain multiple keys
    -

    Internal API

    +

    Internal API

    These functions are intended for the use by dfhack developers, and are only documented here for completeness:

      @@ -1603,7 +1614,7 @@ Returns: found_index, or nil if end reached.

    -

    Core interpreter context

    +

    Core interpreter context

    While plugins can create any number of interpreter instances, there is one special context managed by dfhack core. It is the only context that can receive events from DF and plugins.

    @@ -1634,7 +1645,7 @@ Using timeout_active(id,nil) cancels the timer
  • -

    Event type

    +

    Event type

    An event is a native object transparently wrapping a lua table, and implementing a __call metamethod. When it is invoked, it loops through the table with next and calls all contained values. @@ -1666,7 +1677,7 @@ order using dfhack.safecall.

    -

    Lua Modules

    +

    Lua Modules

    DFHack sets up the lua interpreter so that the built-in require function can be used to load shared lua code from hack/lua/. The dfhack namespace reference itself may be obtained via @@ -1695,7 +1706,7 @@ in this document.

    -

    Global environment

    +

    Global environment

    A number of variables and functions are provided in the base global environment by the mandatory init file dfhack.lua:

      @@ -1736,7 +1747,7 @@ Returns nil if any of obj or indices is nil, or a numeric inde
    -

    utils

    +

    utils

    • utils.compare(a,b)

      Comparator function; returns -1 if a<b, 1 if a>b, 0 otherwise.

      @@ -1849,7 +1860,7 @@ throws an error.

    -

    dumper

    +

    dumper

    A third-party lua table dumper module from http://lua-users.org/wiki/DataDumper. Defines one function:

    @@ -1863,14 +1874,14 @@ the other arguments see the original documentation link above.

    -

    Plugins

    +

    Plugins

    DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.

    The following plugins have lua support.

    -

    burrows

    +

    burrows

    Implements extended burrow manipulations.

    Events:

      @@ -1908,13 +1919,13 @@ set is the same as used by the command line.

      The lua module file also re-exports functions from dfhack.burrows.

    -

    sort

    +

    sort

    Does not export any native functions as of now. Instead, it calls lua code to perform the actual ordering of list items.

    -

    Scripts

    +

    Scripts

    Any files with the .lua extension placed into hack/scripts/* are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 7c2c8f8d6..9f0477538 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1211,6 +1211,39 @@ static int dfhack_open_plugin(lua_State *L) return 0; } +static int dfhack_curry_wrap(lua_State *L) +{ + int nargs = lua_gettop(L); + int ncurry = lua_tointeger(L, lua_upvalueindex(1)); + int scount = nargs + ncurry; + + luaL_checkstack(L, ncurry, "stack overflow in curry"); + + // Insert values in O(N+M) by first shifting the existing data + lua_settop(L, scount); + for (int i = 0; i < nargs; i++) + lua_copy(L, nargs-i, scount-i); + for (int i = 1; i <= ncurry; i++) + lua_copy(L, lua_upvalueindex(i+1), i); + + lua_callk(L, scount-1, LUA_MULTRET, 0, lua_gettop); + + return lua_gettop(L); +} + +static int dfhack_curry(lua_State *L) +{ + luaL_checkany(L, 1); + if (lua_isnil(L, 1)) + luaL_argerror(L, 1, "nil function in curry"); + if (lua_gettop(L) == 1) + return 1; + lua_pushinteger(L, lua_gettop(L)); + lua_insert(L, 1); + lua_pushcclosure(L, dfhack_curry_wrap, lua_gettop(L)); + return 1; +} + bool Lua::IsCoreContext(lua_State *state) { // This uses a private field of the lua state to @@ -1234,6 +1267,7 @@ static const luaL_Reg dfhack_funcs[] = { { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, { "open_plugin", dfhack_open_plugin }, + { "curry", dfhack_curry }, { NULL, NULL } }; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 2cbd019a6..a1e899761 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -46,6 +46,7 @@ end -- Error handling safecall = dfhack.safecall +curry = dfhack.curry function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) @@ -118,7 +119,12 @@ function defclass(class,parent) if parent then setmetatable(class, parent) else - rawset_default(class, { init_fields = rawset_default }) + rawset_default(class, { + init_fields = rawset_default, + callback = function(self, name, ...) + return dfhack.curry(self[name], self, ...) + end + }) end return class end From 57086ac56eb489abd0c7759aed084020edc71148 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 19:45:45 +0400 Subject: [PATCH 07/36] Add stock MessageBox and InputBox dialog screens for lua scripts. --- LUA_API.rst | 8 ++ Lua API.html | 6 ++ library/LuaApi.cpp | 2 + library/LuaTools.cpp | 3 + library/Process-darwin.cpp | 8 ++ library/Process-linux.cpp | 8 ++ library/Process-windows.cpp | 5 ++ library/include/MemAccess.h | 3 + library/lua/gui.lua | 12 ++- library/lua/gui/dialogs.lua | 175 ++++++++++++++++++++++++++++++++++++ library/lua/utils.lua | 13 +++ 11 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 library/lua/gui/dialogs.lua diff --git a/LUA_API.rst b/LUA_API.rst index 22130efd6..799f623eb 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -553,6 +553,10 @@ Exception handling Miscellaneous ------------- +* ``dfhack.VERSION`` + + DFHack version string constant. + * ``dfhack.curry(func,args...)``, or ``curry(func,args...)`` Returns a closure that invokes the function with args combined @@ -719,6 +723,10 @@ can be omitted. Returns the dfhack directory path, i.e. ``".../df/hack/"``. +* ``dfhack.getTickCount()`` + + Returns the tick count in ms, exactly as DF ui uses. + * ``dfhack.isWorldLoaded()`` Checks if the world is loaded. diff --git a/Lua API.html b/Lua API.html index f6f2d42b3..f05ee5511 100644 --- a/Lua API.html +++ b/Lua API.html @@ -846,6 +846,9 @@ following properties:

    Miscellaneous

      +
    • dfhack.VERSION

      +

      DFHack version string constant.

      +
    • dfhack.curry(func,args...), or curry(func,args...)

      Returns a closure that invokes the function with args combined both from the curry call and the closure call itself. I.e. @@ -985,6 +988,9 @@ can be omitted.

    • dfhack.getHackPath()

      Returns the dfhack directory path, i.e. ".../df/hack/".

    • +
    • dfhack.getTickCount()

      +

      Returns the tick count in ms, exactly as DF ui uses.

      +
    • dfhack.isWorldLoaded()

      Checks if the world is loaded.

    • diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 4e57b113f..1dcb001f1 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -728,6 +728,7 @@ static std::string getOSType() } static std::string getDFVersion() { return Core::getInstance().vinfo->getVersion(); } +static uint32_t getTickCount() { return Core::getInstance().p->getTickCount(); } static std::string getDFPath() { return Core::getInstance().p->getPath(); } static std::string getHackPath() { return Core::getInstance().getHackPath(); } @@ -739,6 +740,7 @@ static const LuaWrapper::FunctionReg dfhack_module[] = { WRAP(getOSType), WRAP(getDFVersion), WRAP(getDFPath), + WRAP(getTickCount), WRAP(getHackPath), WRAP(isWorldLoaded), WRAP(isMapLoaded), diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 9f0477538..a283d215c 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -1580,6 +1580,9 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN); lua_setfield(state, -2, "BASE_G"); + lua_pushstring(state, DFHACK_VERSION); + lua_setfield(state, -2, "VERSION"); + lua_pushboolean(state, IsCoreContext(state)); lua_setfield(state, -2, "is_core_context"); diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index 5a97d9e00..3893cfc5f 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -27,6 +27,7 @@ distribution. #include #include #include +#include #include @@ -262,6 +263,13 @@ bool Process::getThreadIDs(vector & threads ) return true; } +uint32_t Process::getTickCount() +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return (tp.tv_sec * 1000) + (tp.tv_usec / 1000); +} + string Process::getPath() { char path[1024]; diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index fe8647845..4a66470f9 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -27,6 +27,7 @@ distribution. #include #include #include +#include #include #include @@ -192,6 +193,13 @@ bool Process::getThreadIDs(vector & threads ) return true; } +uint32_t Process::getTickCount() +{ + struct timeval tp; + gettimeofday(&tp, NULL); + return (tp.tv_sec * 1000) + (tp.tv_usec / 1000); +} + string Process::getPath() { const char * cwd_name = "/proc/self/cwd"; diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 7eb6ff5f7..db58c4d33 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -410,6 +410,11 @@ string Process::doReadClassName (void * vptr) return raw; } +uint32_t Process::getTickCount() +{ + return GetTickCount(); +} + string Process::getPath() { HMODULE hmod; diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 0e5f618e2..a226018a6 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -281,6 +281,9 @@ namespace DFHack /// get the DF Process FilePath std::string getPath(); + /// millisecond tick count, exactly as DF uses + uint32_t getTickCount(); + /// modify permisions of memory range bool setPermisions(const t_memrange & range,const t_memrange &trgrange); diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 9e189ea13..23904c14f 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -94,6 +94,9 @@ function Painter:isValidPos() end function Painter:viewport(x,y,w,h) + if type(x) == 'table' then + x,y,w,h = x.x1, x.y1, x.width, x.height + end local x1,y1 = self.x1+x, self.y1+y local x2,y2 = x1+w-1, y1+h-1 local vp = { @@ -353,11 +356,16 @@ local function hint_coord(gap,hint) end end +function FramedScreen:getWantedFrameSize() + return self.frame_width, self.frame_height +end + function FramedScreen:updateFrameSize() local sw, sh = dscreen.getWindowSize() local iw, ih = sw-2, sh-2 - local width = math.min(self.frame_width or iw, iw) - local height = math.min(self.frame_height or ih, ih) + local fw, fh = self:getWantedFrameSize() + local width = math.min(fw or iw, iw) + local height = math.min(fh or ih, ih) local gw, gh = iw-width, ih-height local x1, y1 = hint_coord(gw,self.frame_xhint), hint_coord(gh,self.frame_yhint) self.frame_rect = mkdims_wh(x1+1,y1+1,width,height) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua new file mode 100644 index 000000000..e6d30c970 --- /dev/null +++ b/library/lua/gui/dialogs.lua @@ -0,0 +1,175 @@ +-- Some simple dialog screens + +local _ENV = mkmodule('gui.dialogs') + +local gui = require('gui') +local utils = require('utils') + +local dscreen = dfhack.screen + +MessageBox = defclass(MessageBox, gui.FramedScreen) + +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") + end + gui.FramedScreen.init(self, info) + return self +end + +function MessageBox:getWantedFrameSize() + local text = self.text + local w = #(self.frame_title or '') + 2 + w = math.max(w, 20) + w = math.max(self.frame_width or w, w) + for _, l in ipairs(text) do + w = math.max(w, #l) + end + local h = #text+1 + if h > 1 then + h = h+1 + end + return w, #text+2 +end + +function MessageBox:onRenderBody(dc) + if #self.text > 0 then + dc:newline(1):pen(self.text_pen or COLOR_GREY) + for _, l in ipairs(self.text or {}) do + dc:string(l):newline(1) + end + end + + if self.on_accept then + local x,y = self.frame_rect.x1+1, self.frame_rect.y2+1 + dscreen.paintString({fg=COLOR_LIGHTGREEN},x,y,'ESC') + dscreen.paintString({fg=COLOR_GREY},x+3,y,'/') + dscreen.paintString({fg=COLOR_LIGHTGREEN},x+4,y,'y') + end +end + +function MessageBox:onDestroy() + if self.on_close then + self.on_close() + end +end + +function MessageBox:onInput(keys) + if keys.MENU_CONFIRM then + self:dismiss() + if self.on_accept then + self.on_accept() + end + elseif keys.LEAVESCREEN or (keys.SELECT and not self.on_accept) then + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + end +end + +function showMessage(title, text, tcolor, on_close) + mkinstance(MessageBox):init{ + text = text, + title = title, + text = text, + text_pen = tcolor, + on_close = on_close + }:show() +end + +function showYesNoPrompt(title, text, tcolor, on_accept, on_cancel) + mkinstance(MessageBox):init{ + title = title, + text = text, + text_pen = tcolor, + on_accept = on_accept, + on_cancel = on_cancel, + }:show() +end + +InputBox = defclass(InputBox, MessageBox) + +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 +end + +function InputBox:getWantedFrameSize() + local mw, mh = MessageBox.getWantedFrameSize(self) + return mw, mh+2 +end + +function InputBox:onRenderBody(dc) + MessageBox.onRenderBody(self, dc) + + dc:newline(1) + dc:pen(self.input_pen or COLOR_LIGHTCYAN) + dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) + + local cursor = '_' + if math.floor(dfhack.getTickCount()/500) % 2 == 0 then + cursor = ' ' + end + local txt = self.input .. cursor + if #txt > dc.width-2 then + txt = string.sub(txt, #txt-dc.width+3) + -- Add prefix arrow + dc:advance(-1):char(27) + end + dc:string(txt) +end + +function InputBox:onInput(keys) + if keys.SELECT then + self:dismiss() + if self.on_input then + self.on_input(self.input) + end + elseif keys.LEAVESCREEN then + self:dismiss() + if self.on_cancel then + self.on_cancel() + end + elseif keys._STRING then + if keys._STRING == 0 then + self.input = string.sub(self.input, 1, #self.input-1) + else + self.input = self.input .. string.char(keys._STRING) + end + end +end + +function showInputPrompt(title, text, tcolor, input, on_input, on_cancel, min_width) + mkinstance(InputBox):init{ + title = title, + text = text, + text_pen = tcolor, + input = input, + on_input = on_input, + on_cancel = on_cancel, + frame_width = min_width, + }:show() +end + + +return _ENV diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 19a4e6f6a..9fa473ed8 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -381,6 +381,19 @@ function getBuildingCenter(building) return xyz2pos(building.centerx, building.centery, building.z) end +function split_string(self, delimiter) + local result = { } + local from = 1 + local delim_from, delim_to = string.find( self, delimiter, from ) + while delim_from do + table.insert( result, string.sub( self, from , delim_from-1 ) ) + from = delim_to + 1 + delim_from, delim_to = string.find( self, delimiter, from ) + end + table.insert( result, string.sub( self, from ) ) + return result +end + -- Ask a yes-no question function prompt_yes_no(msg,default) local prompt = msg From 8d876cc7d92faf1616d914e03c890772256ebb83 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 5 Sep 2012 21:27:42 +0400 Subject: [PATCH 08/36] Support renaming some buildings, and arbitrary units, via gui script. --- dfhack.init-example | 4 + library/lua/gui/dialogs.lua | 9 +- library/modules/World.cpp | 4 +- plugins/CMakeLists.txt | 2 +- plugins/lua/rename.lua | 13 +++ plugins/proto/rename.proto | 7 ++ plugins/rename.cpp | 201 +++++++++++++++++++++++++++++++++++- scripts/gui/rename.lua | 63 +++++++++++ 8 files changed, 296 insertions(+), 7 deletions(-) create mode 100644 plugins/lua/rename.lua create mode 100644 scripts/gui/rename.lua diff --git a/dfhack.init-example b/dfhack.init-example index 39c0e61df..af8b17f0f 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -16,6 +16,10 @@ keybinding add Ctrl-K autodump-destroy-item # quicksave, only in main dwarfmode screen and menu page keybinding add Ctrl-Alt-S@dwarfmode/Default quicksave +# gui/rename script +keybinding add Ctrl-Shift-N gui/rename +keybinding add Ctrl-Shift-P "gui/rename unit-profession" + ############################## # Generic adv mode bindings # ############################## diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index e6d30c970..35720f871 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -9,6 +9,7 @@ local dscreen = dfhack.screen MessageBox = defclass(MessageBox, gui.FramedScreen) +MessageBox.focus_path = 'MessageBox' MessageBox.frame_style = gui.GREY_LINE_FRAME function MessageBox:init(info) @@ -31,7 +32,7 @@ end function MessageBox:getWantedFrameSize() local text = self.text - local w = #(self.frame_title or '') + 2 + local w = #(self.frame_title or '') + 4 w = math.max(w, 20) w = math.max(self.frame_width or w, w) for _, l in ipairs(text) do @@ -41,7 +42,7 @@ function MessageBox:getWantedFrameSize() if h > 1 then h = h+1 end - return w, #text+2 + return w+2, #text+2 end function MessageBox:onRenderBody(dc) @@ -102,6 +103,8 @@ end InputBox = defclass(InputBox, MessageBox) +InputBox.focus_path = 'InputBox' + function InputBox:init(info) info = info or {} self:init_fields{ @@ -127,7 +130,7 @@ function InputBox:onRenderBody(dc) dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) local cursor = '_' - if math.floor(dfhack.getTickCount()/500) % 2 == 0 then + if math.floor(dfhack.getTickCount()/300) % 2 == 0 then cursor = ' ' end local txt = self.input .. cursor diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 393e7cbfe..e14aa02a0 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -285,13 +285,13 @@ PersistentDataItem World::GetPersistentData(int entry_id) PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) { - *added = false; + if (added) *added = false; PersistentDataItem rv = GetPersistentData(key); if (!rv.isValid()) { - *added = true; + if (added) *added = true; rv = AddPersistentData(key); } diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a2e520178..04da3e6c8 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -92,7 +92,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(seedwatch seedwatch.cpp) DFHACK_PLUGIN(initflags initflags.cpp) DFHACK_PLUGIN(stockpiles stockpiles.cpp) - DFHACK_PLUGIN(rename rename.cpp PROTOBUFS rename) + DFHACK_PLUGIN(rename rename.cpp LINK_LIBRARIES lua PROTOBUFS rename) DFHACK_PLUGIN(jobutils jobutils.cpp) DFHACK_PLUGIN(workflow workflow.cpp) DFHACK_PLUGIN(showmood showmood.cpp) diff --git a/plugins/lua/rename.lua b/plugins/lua/rename.lua new file mode 100644 index 000000000..0e7128f57 --- /dev/null +++ b/plugins/lua/rename.lua @@ -0,0 +1,13 @@ +local _ENV = mkmodule('plugins.rename') + +--[[ + + Native functions: + + * canRenameBuilding(building) + * isRenamingBuilding(building) + * renameBuilding(building, name) + +--]] + +return _ENV \ No newline at end of file diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto index aa1e95f48..810091fc7 100644 --- a/plugins/proto/rename.proto +++ b/plugins/proto/rename.proto @@ -17,3 +17,10 @@ message RenameUnitIn { optional string nickname = 2; optional string profession = 3; } + +// RPC RenameBuilding : RenameBuildingIn -> EmptyMessage +message RenameBuildingIn { + required int32 building_id = 1; + + optional string name = 2; +} diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 1871d0f73..99dc6848a 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -3,11 +3,15 @@ #include "Export.h" #include "PluginManager.h" +#include +#include + #include "modules/Gui.h" #include "modules/Translation.h" #include "modules/Units.h" +#include "modules/World.h" -#include "DataDefs.h" +#include #include "df/ui.h" #include "df/world.h" #include "df/squad.h" @@ -18,6 +22,11 @@ #include "df/historical_figure_info.h" #include "df/assumed_identity.h" #include "df/language_name.h" +#include "df/building_stockpilest.h" +#include "df/building_workshopst.h" +#include "df/building_furnacest.h" +#include "df/building_trapst.h" +#include "df/building_siegeenginest.h" #include "RemoteServer.h" #include "rename.pb.h" @@ -36,6 +45,8 @@ using namespace dfproto; using df::global::ui; using df::global::world; +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event); + static command_result rename(color_ostream &out, vector & parameters); DFHACK_PLUGIN("rename"); @@ -51,8 +62,32 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector clear(); \ + *buf += name; \ + *buf += " ("; \ + if (tag) *buf += (const char*)tag; \ + else { std::string tmp; INTERPOSE_NEXT(getName)(&tmp); *buf += tmp; } \ + *buf += ")"; \ + return; \ + } \ + else \ + INTERPOSE_NEXT(getName)(buf); \ + } \ + }; \ + IMPLEMENT_VMETHOD_INTERPOSE(cname##_hook, getName); +KNOWN_BUILDINGS +#undef BUILDING + +static char getBuildingCode(df::building *bld) +{ + CHECK_NULL_POINTER(bld); + +#define BUILDING(code, cname, tag) \ + if (strict_virtual_cast(bld)) return code; +KNOWN_BUILDINGS +#undef BUILDING + + return 0; +} + +static bool enable_building_rename(char code, bool enable) +{ + switch (code) { +#define BUILDING(code, cname, tag) \ + case code: return INTERPOSE_HOOK(cname##_hook, getName).apply(enable); +KNOWN_BUILDINGS +#undef BUILDING + default: + return false; + } +} + +static void disable_building_rename() +{ +#define BUILDING(code, cname, tag) \ + INTERPOSE_HOOK(cname##_hook, getName).remove(); +KNOWN_BUILDINGS +#undef BUILDING +} + +static bool is_enabled_building(char code) +{ + switch (code) { +#define BUILDING(code, cname, tag) \ + case code: return INTERPOSE_HOOK(cname##_hook, getName).is_applied(); +KNOWN_BUILDINGS +#undef BUILDING + default: + return false; + } +} + +static void init_buildings(bool enable) +{ + disable_building_rename(); + + if (enable) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("rename/building_types"); + + if (entry.isValid()) + { + std::string val = entry.val(); + for (size_t i = 0; i < val.size(); i++) + enable_building_rename(val[i], true); + } + } +} + +static bool canRenameBuilding(df::building *bld) +{ + return getBuildingCode(bld) != 0; +} + +static bool isRenamingBuilding(df::building *bld) +{ + return is_enabled_building(getBuildingCode(bld)); +} + +static bool renameBuilding(df::building *bld, std::string name) +{ + char code = getBuildingCode(bld); + if (code == 0 && !name.empty()) + return false; + + if (!name.empty() && !is_enabled_building(code)) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("rename/building_types", NULL); + if (!entry.isValid()) + return false; + + if (!enable_building_rename(code, true)) + return false; + + entry.val().push_back(code); + } + + bld->name = name; + return true; +} + static df::squad *getSquadByIndex(unsigned idx) { auto entity = df::historical_entity::find(ui->group_id); @@ -101,14 +263,37 @@ static command_result RenameUnit(color_ostream &stream, const RenameUnitIn *in) return CR_OK; } +static command_result RenameBuilding(color_ostream &stream, const RenameBuildingIn *in) +{ + auto building = df::building::find(in->building_id()); + if (!building) + return CR_NOT_FOUND; + + if (in->has_name()) + { + if (!renameBuilding(building, in->name())) + return CR_FAILURE; + } + + return CR_OK; +} + DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) { RPCService *svc = new RPCService(); svc->addFunction("RenameSquad", RenameSquad); svc->addFunction("RenameUnit", RenameUnit); + svc->addFunction("RenameBuilding", RenameBuilding); return svc; } +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(canRenameBuilding), + DFHACK_LUA_FUNCTION(isRenamingBuilding), + DFHACK_LUA_FUNCTION(renameBuilding), + DFHACK_LUA_END +}; + static command_result rename(color_ostream &out, vector ¶meters) { CoreSuspender suspend; @@ -167,6 +352,20 @@ static command_result rename(color_ostream &out, vector ¶meters) unit->custom_profession = parameters[1]; } + else if (cmd == "building") + { + if (parameters.size() != 2) + return CR_WRONG_USAGE; + + if (ui->main.mode != ui_sidebar_mode::QueryBuilding) + return CR_WRONG_USAGE; + + if (!renameBuilding(world->selected_building, parameters[1])) + { + out.printerr("This type of building is not supported.\n"); + return CR_FAILURE; + } + } else { if (!parameters.empty() && cmd != "?") diff --git a/scripts/gui/rename.lua b/scripts/gui/rename.lua new file mode 100644 index 000000000..a457a0bfd --- /dev/null +++ b/scripts/gui/rename.lua @@ -0,0 +1,63 @@ +-- Rename various objects via gui. + +local gui = require 'gui' +local dlg = require 'gui.dialogs' +local plugin = require 'plugins.rename' + +local mode = ... +local focus = dfhack.gui.getCurFocus() + +local function verify_mode(expected) + if mode ~= nil and mode ~= expected then + qerror('Invalid UI state for mode '..mode) + end +end + +if string.match(focus, '^dwarfmode/QueryBuilding/Some') then + verify_mode('building') + + local building = df.global.world.selected_building + if plugin.canRenameBuilding(building) then + dlg.showInputPrompt( + 'Rename Building', + 'Enter a new name for the building:', COLOR_GREEN, + building.name, + curry(plugin.renameBuilding, building) + ) + else + dlg.showMessage( + 'Rename Building', + 'Cannot rename this type of building.', COLOR_LIGHTRED + ) + end +elseif dfhack.gui.getSelectedUnit(true) then + local unit = dfhack.gui.getSelectedUnit(true) + + if mode == 'unit-profession' then + dlg.showInputPrompt( + 'Rename Unit', + 'Enter a new profession for the unit:', COLOR_GREEN, + unit.custom_profession, + function(newval) + unit.custom_profession = newval + end + ) + else + verify_mode('unit') + + local vname = dfhack.units.getVisibleName(unit) + local vnick = '' + if vname and vname.has_name then + vnick = vname.nickname + end + + dlg.showInputPrompt( + 'Rename Unit', + 'Enter a new nickname for the unit:', COLOR_GREEN, + vnick, + curry(dfhack.units.setNickname, unit) + ) + end +elseif mode then + verify_mode(nil) +end From d5ea05ebb88b40483b62aaf5dfe20e1b24e8a28a Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Sep 2012 12:37:29 +0400 Subject: [PATCH 09/36] Implement a pressure plate sensitive to machine power. When built next to a gearbox, it will monitor its powered state. --- dfhack.init-example | 3 + library/lua/dfhack.lua | 2 +- library/lua/gui.lua | 8 +- library/lua/gui/dialogs.lua | 6 +- library/modules/Gui.cpp | 9 +- plugins/CMakeLists.txt | 1 + plugins/lua/power-meter.lua | 11 ++ plugins/power-meter.cpp | 237 ++++++++++++++++++++++++++++++++++++ scripts/gui/power-meter.lua | 116 ++++++++++++++++++ 9 files changed, 383 insertions(+), 10 deletions(-) create mode 100644 plugins/lua/power-meter.lua create mode 100644 plugins/power-meter.cpp create mode 100644 scripts/gui/power-meter.lua diff --git a/dfhack.init-example b/dfhack.init-example index af8b17f0f..5af527099 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -56,6 +56,9 @@ keybinding add Alt-R@dwarfmode/QueryBuilding/Some gui/room-list.work # interface for the liquids plugin 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 # ################### diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index a1e899761..e96bb0f4b 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -84,7 +84,7 @@ function mkmodule(module,env) error("Not a table in package.loaded["..module.."]") end end - local plugname = string.match(module,'^plugins%.(%w+)$') + local plugname = string.match(module,'^plugins%.([%w%-]+)$') if plugname then dfhack.open_plugin(pkg,plugname) end diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 23904c14f..f9b6ab6d2 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -162,10 +162,10 @@ function Painter:fill(x1,y1,x2,y2,pen,bg,bold) if type(x1) == 'table' then x1, y1, x2, y2, pen, bg, bold = x1.x1, x1.y1, x1.x2, x1.y2, y1, x2, y2 end - x1 = math.max(x1,self.clip_x1) - y1 = math.max(y1,self.clip_y1) - x2 = math.min(x2,self.clip_x2) - y2 = math.min(y2,self.clip_y2) + x1 = math.max(x1+self.x1,self.clip_x1) + y1 = math.max(y1+self.y1,self.clip_y1) + x2 = math.min(x2+self.x1,self.clip_x2) + y2 = math.min(y2+self.y1,self.clip_y2) dscreen.fillRect(to_pen(self.cur_pen,pen,bg,bold),x1,y1,x2,y2) return self end diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 35720f871..c4f15c9ac 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -127,7 +127,7 @@ function InputBox:onRenderBody(dc) dc:newline(1) dc:pen(self.input_pen or COLOR_LIGHTCYAN) - dc:fill(dc.x1+1,dc.y,dc.x2-1,dc.y) + dc:fill(1,dc:localY(),dc.width-2,dc:localY()) local cursor = '_' if math.floor(dfhack.getTickCount()/300) % 2 == 0 then @@ -135,9 +135,7 @@ function InputBox:onRenderBody(dc) end local txt = self.input .. cursor if #txt > dc.width-2 then - txt = string.sub(txt, #txt-dc.width+3) - -- Add prefix arrow - dc:advance(-1):char(27) + txt = string.char(27)..string.sub(txt, #txt-dc.width+4) end dc:string(txt) end diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 1ea4bf687..8de908734 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -211,7 +211,14 @@ DEFINE_GET_FOCUS_STRING_HANDLER(dwarfmode) if (ui_build_selector->building_type < 0) focus += "/Type"; else if (ui_build_selector->stage != 2) - focus += "/Position"; + { + if (ui_build_selector->stage != 1) + focus += "/NoMaterials"; + else + focus += "/Position"; + + focus += "/" + enum_item_key(ui_build_selector->building_type); + } else { focus += "/Material"; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 04da3e6c8..9093a493f 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -114,6 +114,7 @@ if (BUILD_SUPPORTED) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(power-meter power-meter.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/lua/power-meter.lua b/plugins/lua/power-meter.lua new file mode 100644 index 000000000..310e51c4e --- /dev/null +++ b/plugins/lua/power-meter.lua @@ -0,0 +1,11 @@ +local _ENV = mkmodule('plugins.power-meter') + +--[[ + + Native functions: + + * makePowerMeter(plate_info,min_power,max_power,invert) + +--]] + +return _ENV \ No newline at end of file diff --git a/plugins/power-meter.cpp b/plugins/power-meter.cpp new file mode 100644 index 000000000..0466b68e4 --- /dev/null +++ b/plugins/power-meter.cpp @@ -0,0 +1,237 @@ +#include "Core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "df/graphic.h" +#include "df/building_trapst.h" +#include "df/builtin_mats.h" +#include "df/world.h" +#include "df/buildings_other_id.h" +#include "df/machine.h" +#include "df/machine_info.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; + +DFHACK_PLUGIN("power-meter"); + +static const uint32_t METER_BIT = 0x80000000U; + +static void init_plate_info(df::pressure_plate_info &plate_info) +{ + plate_info.water_min = 1; + plate_info.water_max = 7; + plate_info.flags.whole = METER_BIT; + plate_info.flags.bits.water = true; + plate_info.flags.bits.resets = true; +} + +/* + * Hook for the pressure plate itself. Implements core logic. + */ + +struct trap_hook : df::building_trapst { + typedef df::building_trapst interpose_base; + + // Engine detection + + bool is_power_meter() + { + return trap_type == trap_type::PressurePlate && + (plate_info.flags.whole & METER_BIT) != 0; + } + + inline bool is_fully_built() + { + return getBuildStage() >= getMaxBuildStage(); + } + + DEFINE_VMETHOD_INTERPOSE(void, getName, (std::string *buf)) + { + if (is_power_meter()) + { + buf->clear(); + *buf += "Power Meter"; + return; + } + + INTERPOSE_NEXT(getName)(buf); + } + + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + if (is_power_meter()) + { + auto pdsgn = Maps::getTileDesignation(centerx,centery,z); + + if (pdsgn) + { + bool active = false; + auto &gears = world->buildings.other[buildings_other_id::GEAR_ASSEMBLY]; + + for (size_t i = 0; i < gears.size(); i++) + { + // Adjacent + auto gear = gears[i]; + int deltaxy = abs(centerx - gear->centerx) + abs(centery - gear->centery); + if (gear->z != z || deltaxy != 1) + continue; + // Linked to machine + auto info = gears[i]->getMachineInfo(); + if (!info || info->machine_id < 0) + continue; + // an active machine + auto machine = df::machine::find(info->machine_id); + if (!machine || !machine->flags.bits.active) + continue; + // with adequate power? + int power = machine->cur_power - machine->min_power; + if (power < 0 || machine->cur_power <= 0) + continue; + if (power < plate_info.track_min) + continue; + if (power > plate_info.track_max && plate_info.track_max >= 0) + continue; + + active = true; + break; + } + + if (plate_info.flags.bits.citizens) + active = !active; + + // Temporarily set the tile water amount based on power state + auto old_dsgn = *pdsgn; + pdsgn->bits.liquid_type = tile_liquid::Water; + pdsgn->bits.flow_size = (active ? 7 : 0); + + INTERPOSE_NEXT(updateAction)(); + + *pdsgn = old_dsgn; + return; + } + } + + INTERPOSE_NEXT(updateAction)(); + } + + DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, void *unk)) + { + INTERPOSE_NEXT(drawBuilding)(db, unk); + + if (is_power_meter() && is_fully_built()) + { + db->fore[0][0] = 3; + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, getName); +IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, updateAction); +IMPLEMENT_VMETHOD_INTERPOSE(trap_hook, drawBuilding); + +static bool enabled = false; + +static void enable_hooks(bool enable) +{ + enabled = enable; + + INTERPOSE_HOOK(trap_hook, getName).apply(enable); + INTERPOSE_HOOK(trap_hook, updateAction).apply(enable); + INTERPOSE_HOOK(trap_hook, drawBuilding).apply(enable); +} + +static bool makePowerMeter(df::pressure_plate_info *info, int min_power, int max_power, bool invert) +{ + CHECK_NULL_POINTER(info); + + if (!enabled) + { + auto pworld = Core::getInstance().getWorld(); + auto entry = pworld->GetPersistentData("power-meter/enabled", NULL); + if (!entry.isValid()) + return false; + + enable_hooks(true); + } + + init_plate_info(*info); + info->track_min = min_power; + info->track_max = max_power; + info->flags.bits.citizens = invert; + return true; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS { + DFHACK_LUA_FUNCTION(makePowerMeter), + DFHACK_LUA_END +}; + +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("power-meter/enabled").isValid(); + + if (enable) + { + out.print("Enabling the power meter plugin.\n"); + enable_hooks(true); + } + } + 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/scripts/gui/power-meter.lua b/scripts/gui/power-meter.lua new file mode 100644 index 000000000..8baf43e7e --- /dev/null +++ b/scripts/gui/power-meter.lua @@ -0,0 +1,116 @@ +-- Interface front-end for power-meter plugin. + +local utils = require 'utils' +local gui = require 'gui' +local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' + +local plugin = require('plugins.power-meter') +local bselector = df.global.ui_build_selector + +PowerMeter = defclass(PowerMeter, guidm.MenuOverlay) + +PowerMeter.focus_path = 'power-meter' + +function PowerMeter:init() + self:init_fields{ + min_power = 0, max_power = -1, invert = false, + } + guidm.MenuOverlay.init(self) + return self +end + +function PowerMeter:onShow() + guidm.MenuOverlay.onShow(self) + + -- Send an event to update the errors + bselector.plate_info.flags.whole = 0 + self:sendInputToParent('BUILDING_TRIGGER_ENABLE_WATER') +end + +function PowerMeter:onRenderBody(dc) + dc:fill(0,0,dc.width-1,13,gui.CLEAR_PEN) + dc:seek(1,1):pen(COLOR_WHITE) + dc:string("Power Meter"):newline():newline(1) + dc:string("Placement"):newline():newline(1) + + dc:string("Excess power range:") + + dc:newline(3):string("as", COLOR_LIGHTGREEN) + dc:string(": Min ") + if self.min_power <= 0 then + dc:string("(any)") + else + dc:string(''..self.min_power) + end + + dc:newline(3):string("zx", COLOR_LIGHTGREEN) + dc:string(": Max ") + if self.max_power < 0 then + dc:string("(any)") + else + dc:string(''..self.max_power) + end + dc:newline():newline(1) + + dc:string("i",COLOR_LIGHTGREEN):string(": ") + if self.invert then + dc:string("Inverted") + else + dc:string("Not inverted") + end +end + +function PowerMeter:onInput(keys) + if keys.CUSTOM_I then + self.invert = not self.invert + elseif keys.BUILDING_TRIGGER_MIN_WATER_UP then + self.min_power = self.min_power + 10 + elseif keys.BUILDING_TRIGGER_MIN_WATER_DOWN then + self.min_power = math.max(0, self.min_power - 10) + elseif keys.BUILDING_TRIGGER_MAX_WATER_UP then + if self.max_power < 0 then + self.max_power = 0 + else + self.max_power = self.max_power + 10 + end + elseif keys.BUILDING_TRIGGER_MAX_WATER_DOWN then + self.max_power = math.max(-1, self.max_power - 10) + elseif keys.LEAVESCREEN then + self:dismiss() + self:sendInputToParent('LEAVESCREEN') + elseif keys.SELECT then + if #bselector.errors == 0 then + if not plugin.makePowerMeter( + bselector.plate_info, + self.min_power, self.max_power, self.invert + ) + then + dlg.showMessage( + 'Power Meter', + 'Could not initialize.', COLOR_LIGHTRED + ) + + self:dismiss() + self:sendInputToParent('LEAVESCREEN') + return + end + + self:sendInputToParent('SELECT') + if bselector.stage ~= 1 then + self:dismiss() + end + end + elseif self:propagateMoveKeys(keys) then + return + end +end + +if dfhack.gui.getCurFocus() ~= 'dwarfmode/Build/Position/Trap' +or bselector.building_subtype ~= df.trap_type.PressurePlate +then + qerror("This script requires the main dwarfmode view in build pressure plate mode") +end + +local list = mkinstance(PowerMeter):init() +list:show() From d0e630d4c35717bad682894e33e7dd57f86ac126 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Sep 2012 17:10:58 +0400 Subject: [PATCH 10/36] Move steam engine out of devel, since it should be fully functional. --- plugins/CMakeLists.txt | 4 ++++ plugins/devel/CMakeLists.txt | 1 - plugins/{devel => raw}/building_steam_engine.txt | 0 plugins/{devel => raw}/item_trapcomp_steam_engine.txt | 0 plugins/{devel => raw}/reaction_steam_engine.txt | 0 plugins/{devel => }/steam-engine.cpp | 0 6 files changed, 4 insertions(+), 1 deletion(-) rename plugins/{devel => raw}/building_steam_engine.txt (100%) rename plugins/{devel => raw}/item_trapcomp_steam_engine.txt (100%) rename plugins/{devel => raw}/reaction_steam_engine.txt (100%) rename plugins/{devel => }/steam-engine.cpp (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 9093a493f..6e207385e 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -44,6 +44,9 @@ endif() install(DIRECTORY lua/ DESTINATION ${DFHACK_LUA_DESTINATION}/plugins FILES_MATCHING PATTERN "*.lua") +install(DIRECTORY raw/ + DESTINATION ${DFHACK_DATA_DESTINATION}/raw + FILES_MATCHING PATTERN "*.txt") # Protobuf FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto) @@ -114,6 +117,7 @@ if (BUILD_SUPPORTED) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) 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) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index f126ae53b..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(steam-engine steam-engine.cpp) IF(UNIX) DFHACK_PLUGIN(ref-index ref-index.cpp) ENDIF() diff --git a/plugins/devel/building_steam_engine.txt b/plugins/raw/building_steam_engine.txt similarity index 100% rename from plugins/devel/building_steam_engine.txt rename to plugins/raw/building_steam_engine.txt diff --git a/plugins/devel/item_trapcomp_steam_engine.txt b/plugins/raw/item_trapcomp_steam_engine.txt similarity index 100% rename from plugins/devel/item_trapcomp_steam_engine.txt rename to plugins/raw/item_trapcomp_steam_engine.txt diff --git a/plugins/devel/reaction_steam_engine.txt b/plugins/raw/reaction_steam_engine.txt similarity index 100% rename from plugins/devel/reaction_steam_engine.txt rename to plugins/raw/reaction_steam_engine.txt diff --git a/plugins/devel/steam-engine.cpp b/plugins/steam-engine.cpp similarity index 100% rename from plugins/devel/steam-engine.cpp rename to plugins/steam-engine.cpp From c971a819def1c5cc29dc926f62456c336a1dfa17 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 6 Sep 2012 22:45:19 +0400 Subject: [PATCH 11/36] Experimental creation of map blocks in gui/liquids script. --- LUA_API.rst | 4 +++ Lua API.html | 3 ++ library/LuaApi.cpp | 8 +++++ library/include/modules/MapCache.h | 10 ++++++ library/include/modules/Maps.h | 2 ++ library/modules/Maps.cpp | 58 +++++++++++++++++++++++++++++- library/xml | 2 +- scripts/gui/liquids.lua | 41 ++++++++++++++++++++- 8 files changed, 125 insertions(+), 3 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index 799f623eb..d13c1e52e 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -995,6 +995,10 @@ Maps module Returns a map block object for given df::coord or x,y,z in local tile coordinates. +* ``dfhack.maps.ensureTileBlock(coords)``, or ``ensureTileBlock(x,y,z)`` + + Like ``getTileBlock``, but if the block is not allocated, try creating it. + * ``dfhack.maps.getRegionBiome(region_coord2d)``, or ``getRegionBiome(x,y)`` Returns the biome info struct for the given global map region. diff --git a/Lua API.html b/Lua API.html index f05ee5511..546d27403 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1207,6 +1207,9 @@ Returns false in case of error.

    • dfhack.maps.getTileBlock(coords), or getTileBlock(x,y,z)

      Returns a map block object for given df::coord or x,y,z in local tile coordinates.

    • +
    • dfhack.maps.ensureTileBlock(coords), or ensureTileBlock(x,y,z)

      +

      Like getTileBlock, but if the block is not allocated, try creating it.

      +
    • dfhack.maps.getRegionBiome(region_coord2d), or getRegionBiome(x,y)

      Returns the biome info struct for the given global map region.

    • diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 1dcb001f1..63838d356 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -935,6 +935,13 @@ static int maps_getTileBlock(lua_State *L) return 1; } +static int maps_ensureTileBlock(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + Lua::PushDFObject(L, Maps::ensureTileBlock(pos)); + return 1; +} + static int maps_getRegionBiome(lua_State *L) { auto pos = CheckCoordXY(L, 1, true); @@ -951,6 +958,7 @@ static int maps_getTileBiomeRgn(lua_State *L) static const luaL_Reg dfhack_maps_funcs[] = { { "isValidTilePos", maps_isValidTilePos }, { "getTileBlock", maps_getTileBlock }, + { "ensureTileBlock", maps_ensureTileBlock }, { "getRegionBiome", maps_getRegionBiome }, { "getTileBiomeRgn", maps_getTileBiomeRgn }, { NULL, NULL } diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 109a20a41..262e70bbf 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -253,6 +253,8 @@ public: bool is_valid() { return valid; } df::map_block *getRaw() { return block; } + bool Allocate(); + MapCache *getParent() { return parent; } private: @@ -262,6 +264,8 @@ private: MapCache *parent; df::map_block *block; + void init(); + int biomeIndexAt(df::coord2d p); bool valid; @@ -347,6 +351,12 @@ class DFHACK_EXPORT MapCache return BlockAt(df::coord(coord.x>>4,coord.y>>4,coord.z)); } + bool ensureBlockAt(df::coord coord) + { + Block *b = BlockAtTile(coord); + return b ? b->Allocate() : false; + } + df::tiletype baseTiletypeAt (DFCoord tilecoord) { Block *b = BlockAtTile(tilecoord); diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 984cf16cf..869b21580 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -241,9 +241,11 @@ inline bool isValidTilePos(df::coord pos) { return isValidTilePos(pos.x, pos.y, */ extern DFHACK_EXPORT df::map_block * getBlock (int32_t blockx, int32_t blocky, int32_t blockz); extern DFHACK_EXPORT df::map_block * getTileBlock (int32_t x, int32_t y, int32_t z); +extern DFHACK_EXPORT df::map_block * ensureTileBlock (int32_t x, int32_t y, int32_t z); inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); } inline df::map_block * getTileBlock (df::coord pos) { return getTileBlock(pos.x, pos.y, pos.z); } +inline df::map_block * ensureTileBlock (df::coord pos) { return ensureTileBlock(pos.x, pos.y, pos.z); } extern DFHACK_EXPORT df::tiletype *getTileType(int32_t x, int32_t y, int32_t z); extern DFHACK_EXPORT df::tile_designation *getTileDesignation(int32_t x, int32_t y, int32_t z); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 305f1296d..d0401164a 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -157,6 +157,39 @@ df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) return world->map.block_index[x >> 4][y >> 4][z]; } +df::map_block *Maps::ensureTileBlock (int32_t x, int32_t y, int32_t z) +{ + if (!isValidTilePos(x,y,z)) + return NULL; + + auto column = world->map.block_index[x >> 4][y >> 4]; + auto &slot = column[z]; + if (slot) + return slot; + + // Find another block below + int z2 = z; + while (z2 >= 0 && !column[z2]) z2--; + if (z2 < 0) + return NULL; + + slot = new df::map_block(); + slot->region_pos = column[z2]->region_pos; + slot->map_pos = column[z2]->map_pos; + slot->map_pos.z = z; + + // Assume sky + df::tile_designation dsgn(0); + dsgn.bits.light = true; + dsgn.bits.outside = true; + + for (int tx = 0; tx < 16; tx++) + for (int ty = 0; ty < 16; ty++) + slot->designation[tx][ty] = dsgn; + + return slot; +} + df::tiletype *Maps::getTileType(int32_t x, int32_t y, int32_t z) { df::map_block *block = getTileBlock(x,y,z); @@ -513,8 +546,14 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) valid = false; bcoord = _bcoord; block = Maps::getBlock(bcoord); - item_counts = NULL; tags = NULL; + + init(); +} + +void MapExtras::Block::init() +{ + item_counts = NULL; tiles = NULL; basemats = NULL; @@ -537,6 +576,23 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) } } +bool MapExtras::Block::Allocate() +{ + if (block) + return true; + + block = Maps::ensureTileBlock(bcoord.x*16, bcoord.y*16, bcoord.z); + if (!block) + return false; + + delete item_counts; + delete tiles; + delete basemats; + init(); + + return true; +} + MapExtras::Block::~Block() { delete[] item_counts; diff --git a/library/xml b/library/xml index df8178a98..a914f3b75 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit df8178a989373ec7868d9195d82ae5f85145ef81 +Subproject commit a914f3b7558335d53c0ac93f6e7267906a33cd29 diff --git a/scripts/gui/liquids.lua b/scripts/gui/liquids.lua index 869cac908..89f08b7cf 100644 --- a/scripts/gui/liquids.lua +++ b/scripts/gui/liquids.lua @@ -3,6 +3,7 @@ local utils = require 'utils' local gui = require 'gui' local guidm = require 'gui.dwarfmode' +local dlg = require 'gui.dialogs' local liquids = require('plugins.liquids') @@ -199,6 +200,42 @@ function LiquidsUI:onRenderBody(dc) dc:string("Enter", COLOR_LIGHTGREEN):string(": Paint") end +function ensure_blocks(cursor, size, cb) + local cx,cy,cz = pos2xyz(cursor) + local all = true + for x=1,size.x or 1,16 do + for y=1,size.y or 1,16 do + for z=1,size.z do + if not dfhack.maps.getTileBlock(cx+x-1, cy+y-1, cz+z-1) then + all = false + end + end + end + end + if all then + cb() + return + end + dlg.showYesNoPrompt( + 'Instantiate Blocks', + 'Not all map blocks are allocated - instantiate?\n\nWarning: new untested feature.', + COLOR_YELLOW, + function() + for x=1,size.x or 1,16 do + for y=1,size.y or 1,16 do + for z=1,size.z do + dfhack.maps.ensureTileBlock(cx+x-1, cy+y-1, cz+z-1) + end + end + end + cb() + end, + function() + cb() + end + ) +end + function LiquidsUI:onInput(keys) local paint = self.paint:get() local liquid = paint.liquid @@ -239,13 +276,15 @@ function LiquidsUI:onInput(keys) else guidm.clearSelection() end - liquids.paint( + local cb = curry( + liquids.paint, cursor, self.brush:get().tag, self.paint:get().tag, self.amount, size, self.set:get().tag, self.flow:get().tag, self.permaflow:get().tag ) + ensure_blocks(cursor, size, cb) elseif self:propagateMoveKeys(keys) then return elseif keys.D_LOOK_ARENA_WATER then From e925d8f4d999817d3ccf7f3180e7abee382e03b4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 7 Sep 2012 11:36:45 +0400 Subject: [PATCH 12/36] Add an API function for reading tiles from the screen buffers. --- LUA_API.rst | 5 ++++ Lua API.html | 4 ++++ library/LuaApi.cpp | 40 ++++++++++++++++++++++++++++++++ library/include/modules/Screen.h | 6 +++++ library/modules/Screen.cpp | 39 +++++++++++++++++++++++++++++-- 5 files changed, 92 insertions(+), 2 deletions(-) diff --git a/LUA_API.rst b/LUA_API.rst index d13c1e52e..4d9170d6e 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -1308,6 +1308,11 @@ Basic painting functions: Returns *false* if coordinates out of bounds, or other error. +* ``dfhack.screen.readTile(x,y)`` + + Retrieves the contents of the specified tile from the screen buffers. + Returns a pen, or *nil* if invalid or TrueType. + * ``dfhack.screen.paintString(pen,x,y,text)`` Paints the string starting at *x,y*. Uses the string characters diff --git a/Lua API.html b/Lua API.html index 546d27403..dc9c8d73e 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1480,6 +1480,10 @@ Otherwise should be true/false.

      Returns false if coordinates out of bounds, or other error.

      +
    • dfhack.screen.readTile(x,y)

      +

      Retrieves the contents of the specified tile from the screen buffers. +Returns a pen, or nil if invalid or TrueType.

      +
    • dfhack.screen.paintString(pen,x,y,text)

      Paints the string starting at x,y. Uses the string characters in sequence to override the ch field of pen.

      diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 63838d356..807cbf539 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1154,6 +1154,45 @@ static int screen_paintTile(lua_State *L) return 1; } +static int screen_readTile(lua_State *L) +{ + int x = luaL_checkint(L, 1); + int y = luaL_checkint(L, 2); + Pen pen = Screen::readTile(x, y); + + if (!pen.valid()) + { + lua_pushnil(L); + } + else + { + lua_newtable(L); + lua_pushinteger(L, pen.ch); lua_setfield(L, -2, "ch"); + lua_pushinteger(L, pen.fg); lua_setfield(L, -2, "fg"); + lua_pushinteger(L, pen.bg); lua_setfield(L, -2, "bg"); + lua_pushboolean(L, pen.bold); lua_setfield(L, -2, "bold"); + + if (pen.tile) + { + lua_pushinteger(L, pen.tile); lua_setfield(L, -2, "tile"); + + switch (pen.tile_mode) { + case Pen::CharColor: + lua_pushboolean(L, true); lua_setfield(L, -2, "tile_color"); + break; + case Pen::TileColor: + lua_pushinteger(L, pen.tile_fg); lua_setfield(L, -2, "tile_fg"); + lua_pushinteger(L, pen.tile_bg); lua_setfield(L, -2, "tile_bg"); + break; + default: + break; + } + } + } + + return 1; +} + static int screen_paintString(lua_State *L) { Pen pen; @@ -1258,6 +1297,7 @@ static const luaL_Reg dfhack_screen_funcs[] = { { "getMousePos", screen_getMousePos }, { "getWindowSize", screen_getWindowSize }, { "paintTile", screen_paintTile }, + { "readTile", screen_readTile }, { "paintString", screen_paintString }, { "fillRect", screen_fillRect }, { "findGraphicsTile", screen_findGraphicsTile }, diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 492e1eecc..4f47205f2 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -65,6 +65,9 @@ namespace DFHack } tile_mode; int8_t tile_fg, tile_bg; + bool valid() const { return tile >= 0; } + bool empty() const { return ch == 0 && tile == 0; } + Pen(char ch = 0, int8_t fg = 7, int8_t bg = 0, int tile = 0, bool color_tile = false) : ch(ch), fg(fg&7), bg(bg), bold(!!(fg&8)), tile(tile), tile_mode(color_tile ? CharColor : AsIs), tile_fg(0), tile_bg(0) @@ -92,6 +95,9 @@ namespace DFHack /// Paint one screen tile with the given pen DFHACK_EXPORT bool paintTile(const Pen &pen, int x, int y); + /// Retrieves one screen tile from the buffer + DFHACK_EXPORT Pen readTile(int x, int y); + /// Paint a string onto the screen. Ignores ch and tile of pen. DFHACK_EXPORT bool paintString(const Pen &pen, int x, int y, const std::string &text); diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index c2377f2ca..9f258fe02 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -100,7 +100,7 @@ static void doSetTile(const Pen &pen, int index) bool Screen::paintTile(const Pen &pen, int x, int y) { - if (!gps) return false; + if (!gps || !pen.valid()) return false; int dimx = gps->dimx, dimy = gps->dimy; if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; @@ -109,6 +109,41 @@ bool Screen::paintTile(const Pen &pen, int x, int y) return true; } +Pen Screen::readTile(int x, int y) +{ + if (!gps) return Pen(0,0,0,-1); + + int dimx = gps->dimx, dimy = gps->dimy; + if (x < 0 || x >= dimx || y < 0 || y >= dimy) + return Pen(0,0,0,-1); + + int index = x*dimy + y; + auto screen = gps->screen + index*4; + if (screen[3] & 0x80) + return Pen(0,0,0,-1); + + Pen pen( + screen[0], screen[1], screen[2], screen[3]?true:false, + gps->screentexpos[index] + ); + + if (pen.tile) + { + if (gps->screentexpos_grayscale[index]) + { + pen.tile_mode = Screen::Pen::TileColor; + pen.tile_fg = gps->screentexpos_cf[index]; + pen.tile_bg = gps->screentexpos_cbr[index]; + } + else if (gps->screentexpos_addcolor[index]) + { + pen.tile_mode = Screen::Pen::CharColor; + } + } + + return pen; +} + bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) { if (!gps || y < 0 || y >= gps->dimy) return false; @@ -132,7 +167,7 @@ bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) bool Screen::fillRect(const Pen &pen, int x1, int y1, int x2, int y2) { - if (!gps) return false; + if (!gps || !pen.valid()) return false; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; From 325e294af2bbd2ba0381a476756eddbbfceb1b36 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 7 Sep 2012 19:54:32 +0400 Subject: [PATCH 13/36] 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 14/36] 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 15/36] 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 16/36] 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 17/36] 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 18/36] 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 19/36] 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 20/36] 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 21/36] 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 22/36] 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 23/36] 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 24/36] 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 25/36] 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 26/36] 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 27/36] 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 28/36] 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 29/36] 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 30/36] 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 31/36] 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 32/36] 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 33/36] 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 34/36] 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 35/36] 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 36/36] 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;