From dfa3a520fd2e7243413d5dc38d253fd17e072c1e Mon Sep 17 00:00:00 2001 From: Kelly Martin Date: Sun, 21 Oct 2012 16:34:13 -0500 Subject: [PATCH 01/55] sync structures --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index b9b2e8c6d..e06438924 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit b9b2e8c6d2141f13966ed965b3f3ffe924e527db +Subproject commit e06438924929a8ecab751c0c233dad5767e91f7e From dd89baf6f88b5e111dbe21b24a2f951f0799a5e2 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 12 Nov 2012 19:18:03 +0100 Subject: [PATCH 02/55] add raw mmap/mprotect access --- library/Process-darwin.cpp | 27 ++++++++++++++++++++++++- library/Process-linux.cpp | 27 ++++++++++++++++++++++++- library/Process-windows.cpp | 39 +++++++++++++++++++++++++++++++++++++ library/include/MemAccess.h | 21 ++++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index d081c8c5c..72311e83a 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -304,4 +304,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); return result==0; -} \ No newline at end of file +} + +// returns -1 on error +void* Process::memAlloc(const int length) +{ + return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); +} + +int Process::memDealloc(const void *ptr, const int length) +{ + return munmap(ptr, length); +} + +int Process::memProtect(const void *ptr, const int length, const int prot) +{ + int prot_native = 0; + + if (prot & Process::MemProt::READ) + prot_native |= PROT_READ; + if (prot & Process::MemProt::WRITE) + prot_native |= PROT_WRITE; + if (prot & Process::MemProt::EXEC) + prot_native |= PROT_EXEC; + + return mprotect(ptr, length, prot_native); +} diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 046b7696d..c3995a2aa 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -235,4 +235,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); return result==0; -} \ No newline at end of file +} + +// returns -1 on error +void* Process::memAlloc(const int length) +{ + return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); +} + +int Process::memDealloc(void *ptr, const int length) +{ + return munmap(ptr, length); +} + +int Process::memProtect(void *ptr, const int length, const int prot) +{ + int prot_native = 0; + + if (prot & Process::MemProt::READ) + prot_native |= PROT_READ; + if (prot & Process::MemProt::WRITE) + prot_native |= PROT_WRITE; + if (prot & Process::MemProt::EXEC) + prot_native |= PROT_EXEC; + + return mprotect(ptr, length, prot_native); +} diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 6f79236f9..cfa0b688d 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -473,3 +473,42 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) return result; } + +void* Process::memAlloc(const int length) +{ + void *ret; + // returns 0 on error + ret = VirtualAlloc(0, length, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + if (!ret) + ret = (void*)-1; + return ret; +} + +int Process::memDealloc(const void *ptr, const int length) +{ + // can only free the whole region at once + // vfree returns 0 on error + return !VirtualFree(ptr, 0, MEM_RELEASE) +} + +int Process::memProtect(const void *ptr, const int length, const int prot) +{ + int prot_native = 0; + int old_prot = 0; + + // only support a few constant combinations + if (prot == 0) + prot_native = PAGE_NOACCESS; + else if (prot == Process::MemProt::READ) + prot_native = PAGE_READONLY; + else if (prot == Process::MemProt::READ | Process::MemProt::WRITE) + prot_native = PAGE_READWRITE; + else if (prot == Process::MemProt::READ | Process::MemProt::WRITE | Process::MemProt::EXECUTE) + prot_native = PAGE_EXECUTE_READWRITE; + else if (prot == Process::MemProt::READ | Process::MemProt::EXECUTE) + prot_native = PAGE_EXECUTE_READ; + else + return -1; + + return !VirtualProtect(ptr, length, prot_native, &old_prot); +} diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 22f15eecf..31647a25e 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -291,6 +291,27 @@ namespace DFHack /// write a possibly read-only memory area bool patchMemory(void *target, const void* src, size_t count); + + /// allocate new memory pages for code or stuff + /// returns -1 on error (0 is a valid address) + void* memAlloc(const int length); + + /// free memory pages from memAlloc + /// should have length = alloced length for portability + /// returns 0 on success + int memDealloc(void *ptr, const int length); + + /// change memory page permissions + /// prot is a bitwise OR of the MemProt enum + /// returns 0 on success + int memProtect(void *ptr, const int length, const int prot); + + enum MemProt { + READ = 1, + WRITE = 2, + EXEC = 4 + }; + private: VersionInfo * my_descriptor; PlatformSpecific *d; From 72912edf58ea562592f00c8bab308c13529427a2 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 16 Nov 2012 18:45:51 +0400 Subject: [PATCH 03/55] Ensure AddPersistentData won't create duplicate ids. If anything messes around with the histfig vector between calls. --- library/modules/World.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/library/modules/World.cpp b/library/modules/World.cpp index f3283c3a3..9ae4266b2 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -197,11 +197,15 @@ PersistentDataItem World::AddPersistentData(const std::string &key) std::vector &hfvec = df::historical_figure::get_vector(); df::historical_figure *hfig = new df::historical_figure(); - hfig->id = next_persistent_id--; + hfig->id = next_persistent_id; hfig->name.has_name = true; hfig->name.first_name = key; memset(hfig->name.words, 0xFF, sizeof(hfig->name.words)); + if (!hfvec.empty()) + hfig->id = std::min(hfig->id, hfvec[0]->id-1); + next_persistent_id = hfig->id-1; + hfvec.insert(hfvec.begin(), hfig); persistent_index.insert(T_persistent_item(key, -hfig->id)); From 423c1224248195fa22d72781b7b26fa84e9dd2c7 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 16 Nov 2012 17:39:08 +0100 Subject: [PATCH 04/55] ruby: fix unit_find for advmode --- plugins/ruby/unit.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 4fbf75d8d..4c638b1a9 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -21,7 +21,7 @@ module DFHack when :SelectTrainer v.trainer_unit[v.trainer_cursor] end - else + when :viewscreen_dwarfmodest case ui.main.mode when :ViewUnits # nobody selected => idx == 0 @@ -33,6 +33,15 @@ module DFHack else ui.follow_unit_tg if ui.follow_unit != -1 end + when :viewscreen_dungeonmodest + case ui_advmode.menu + when :Default + world.units.active[0] + else + unit_find(cursor) # XXX + end + when :viewscreen_dungeon_monsterstatusst + curview.unit end elsif what.kind_of?(Integer) # search by id From 342badac982d63b3c600c5307624e3a4402a93f8 Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 16 Nov 2012 17:46:41 +0100 Subject: [PATCH 05/55] scripts/superdwarf: advmode support --- NEWS | 1 + scripts/superdwarf.rb | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index 48aaf26a2..4ccb1d18d 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ DFHack future - fastdwarf: new mode using debug flags, and some internal consistency fixes. - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option + - superdwarf: work in adventure mode too New scripts: - binpatch: the same as the stand-alone binpatch.exe, but works at runtime. - region-pops: displays animal populations of the region and allows tweaking them. diff --git a/scripts/superdwarf.rb b/scripts/superdwarf.rb index 7f5296b74..6277db97f 100644 --- a/scripts/superdwarf.rb +++ b/scripts/superdwarf.rb @@ -8,7 +8,12 @@ when 'add' if u = df.unit_find $superdwarf_ids |= [u.id] - $superdwarf_onupdate ||= df.onupdate_register(1) { + if df.gamemode == :ADVENTURE + onupdate_delay = nil + else + onupdate_delay = 1 + end + $superdwarf_onupdate ||= df.onupdate_register(onupdate_delay) { if $superdwarf_ids.empty? df.onupdate_unregister($superdwarf_onupdate) $superdwarf_onupdate = nil From 2401be1b3b4ed423542e232deeafa8406ffc79b8 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Fri, 16 Nov 2012 22:48:49 +0400 Subject: [PATCH 06/55] Add an api function to retrieve unit skill experience. --- Lua API.html | 21 +++++++++++++++++++++ Lua API.rst | 4 ++++ library/LuaApi.cpp | 1 + library/include/modules/Units.h | 2 ++ library/modules/Units.cpp | 18 ++++++++++++++++++ 5 files changed, 46 insertions(+) diff --git a/Lua API.html b/Lua API.html index d2e0da1ef..04af5d672 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1109,6 +1109,12 @@ above operations accordingly. If enabled, pauses and zooms to position.

  • dfhack.job.printItemDetails(jobitem,idx)

    Prints info about the job item.

  • +
  • dfhack.job.getGeneralRef(job, type)

    +

    Searches for a general_ref with the given type.

    +
  • +
  • dfhack.job.getSpecificRef(job, type)

    +

    Searches for a specific_ref with the given type.

    +
  • dfhack.job.getHolder(job)

    Returns the building holding the job.

  • @@ -1147,6 +1153,12 @@ the flags in the job item.

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

  • +
  • dfhack.units.getGeneralRef(unit, type)

    +

    Searches for a general_ref with the given type.

    +
  • +
  • dfhack.units.getSpecificRef(unit, type)

    +

    Searches for a specific_ref with the given type.

    +
  • dfhack.units.getContainer(unit)

    Returns the container (cage) item or nil.

  • @@ -1209,6 +1221,9 @@ is true, subtracts the rust penalty.

  • dfhack.units.getEffectiveSkill(unit, skill)

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

  • +
  • dfhack.units.getExperience(unit, skill[, total])

    +

    Returns the experience value for the given skill. If total is true, adds experience implied by the current rating.

    +
  • dfhack.units.computeMovementSpeed(unit)

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

  • @@ -1403,6 +1418,12 @@ burrows, or the presence of invaders.

    Buildings module

    The building or unit options are automatically assumed when in relevant ui state.

    +

    The example config binds building/unit rename to Ctrl-Shift-N, and +unit profession change to Ctrl-Shift-T.

    gui/room-list

    -

    To use, bind to a key and activate in the 'q' mode, either immediately or after opening -the assign owner page.

    +

    To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, +either immediately or after opening the assign owner page.

    images/room-list.png

    The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them.

    gui/choose-weapons

    -

    Bind to a key, and activate in the Equip->View/Customize page of the military screen.

    +

    Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize +page of the military screen.

    Depending on the cursor location, it rewrites all 'individual choice weapon' entries in the selected squad or position to use a specific weapon type matching the assigned unit's top skill. If the cursor is in the rightmost list over a weapon entry, it rewrites @@ -2882,14 +2897,16 @@ and may lead to inappropriate weapons being selected.

    gui/guide-path

    -

    Bind to a key, and activate in the Hauling menu with the cursor over a Guide order.

    +

    Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with +the cursor over a Guide order.

    images/guide-path.png

    The script displays the cached path that will be used by the order; the game computes it when the order is executed for the first time.

    gui/workshop-job

    -

    Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

    +

    Bind to a key (the example config uses Alt-A), and activate with a job selected in +a workshop in the 'q' mode.

    images/workshop-job.png

    The script shows a list of the input reagents of the selected job, and allows changing them like the job item-type and job item-material commands.

    @@ -2924,7 +2941,8 @@ you have to unset the material first.

    gui/workflow

    -

    Bind to a key, and activate with a job selected in a workshop in the 'q' mode.

    +

    Bind to a key (the example config uses Alt-W), and activate with a job selected +in a workshop in the 'q' mode.

    images/workflow.png

    This script provides a simple interface to constraints managed by the workflow plugin. When active, it displays a list of all constraints applicable to the @@ -2953,7 +2971,8 @@ suit your need, and set the item count range.

    gui/assign-rack

    -

    Bind to a key, and activate when viewing a weapon rack in the 'q' mode.

    +

    Bind to a key (the example config uses P), and activate when viewing a weapon +rack in the 'q' mode.

    images/assign-rack.png

    This script is part of a group of related fixes to make the armory storage work again. The existing issues are:

    @@ -3002,7 +3021,8 @@ e.g. like making siegers bring their own, are something only Toady can do.

    Configuration UI

    The configuration front-end to the plugin is implemented by the gui/siege-engine -script. Bind it to a key and activate after selecting a siege engine in 'q' mode.

    +script. Bind it to a key (the example config uses Alt-A) and activate after selecting +a siege engine in 'q' mode.

    images/siege-engine.png

    The main mode displays the current target, selected ammo item type, linked stockpiles and the allowed operator skill range. The map tile color is changed to signify if it can be @@ -3026,7 +3046,8 @@ menu.

    The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

    The configuration front-end is implemented by the gui/power-meter script. Bind it to a -key and activate after selecting Pressure Plate in the build menu.

    +key (the example config uses Ctrl-Shift-M) and activate after selecting Pressure Plate +in the build menu.

    images/power-meter.png

    The script follows the general look and feel of the regular pressure plate build configuration page, but configures parameters relevant to the modded power meter building.

    diff --git a/Readme.rst b/Readme.rst index 4488490dd..1f5ac08fd 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1077,6 +1077,9 @@ Subcommands that persist until disabled or DF quit: :patrol-duty: Makes Train orders not count as patrol duty to stop unhappy thoughts. Does NOT fix the problem when soldiers go off-duty (i.e. civilian). :readable-build-plate: Fixes rendering of creature weight limits in pressure plate build menu. + + .. image:: images/tweak-plate.png + :stable-temp: Fixes performance bug 6012 by squashing jitter in temperature updates. In very item-heavy forts with big stockpiles this can improve FPS by 50-100% :fast-heat: Further improves temperature update performance by ensuring that 1 degree @@ -1095,9 +1098,16 @@ Subcommands that persist until disabled or DF quit: :military-stable-assign: Preserve list order and cursor position when assigning to squad, i.e. stop the rightmost list of the Positions page of the military screen from constantly resetting to the top. -:military-color-assigned: Color squad candidates already assigned to other squads in brown/green +:military-color-assigned: Color squad candidates already assigned to other squads in yellow/green to make them stand out more in the list. + .. image:: images/tweak-mil-color.png + +:military-training: Speeds up melee squad training by removing an almost certainly + unintended inverse dependency of training speed on unit count + (i.e. the more units you have, the slower it becomes), and making + the units spar more. + fix-armory ---------- @@ -2039,7 +2049,7 @@ key while search is active clears the search instead of executing the trade. gui/liquids =========== -To use, bind to a key and activate in the 'k' mode. +To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode. .. image:: images/liquids.png @@ -2050,7 +2060,7 @@ to select the target area and apply changes. gui/mechanisms ============== -To use, bind to a key and activate in the 'q' mode. +To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode. .. image:: images/mechanisms.png @@ -2088,12 +2098,15 @@ via a simple dialog in the game ui. The ``building`` or ``unit`` options are automatically assumed when in relevant ui state. +The example config binds building/unit rename to Ctrl-Shift-N, and +unit profession change to Ctrl-Shift-T. + gui/room-list ============= -To use, bind to a key and activate in the 'q' mode, either immediately or after opening -the assign owner page. +To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, +either immediately or after opening the assign owner page. .. image:: images/room-list.png @@ -2104,7 +2117,8 @@ list, and allows unassigning them. gui/choose-weapons ================== -Bind to a key, and activate in the Equip->View/Customize page of the military screen. +Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize +page of the military screen. Depending on the cursor location, it rewrites all 'individual choice weapon' entries in the selected squad or position to use a specific weapon type matching the assigned @@ -2118,7 +2132,8 @@ and may lead to inappropriate weapons being selected. gui/guide-path ============== -Bind to a key, and activate in the Hauling menu with the cursor over a Guide order. +Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with +the cursor over a Guide order. .. image:: images/guide-path.png @@ -2129,7 +2144,8 @@ computes it when the order is executed for the first time. gui/workshop-job ================ -Bind to a key, and activate with a job selected in a workshop in the 'q' mode. +Bind to a key (the example config uses Alt-A), and activate with a job selected in +a workshop in the 'q' mode. .. image:: images/workshop-job.png @@ -2176,7 +2192,8 @@ you have to unset the material first. gui/workflow ============ -Bind to a key, and activate with a job selected in a workshop in the 'q' mode. +Bind to a key (the example config uses Alt-W), and activate with a job selected +in a workshop in the 'q' mode. .. image:: images/workflow.png @@ -2217,7 +2234,8 @@ If you don't need advanced settings, you can just press 'y' to confirm creation. gui/assign-rack =============== -Bind to a key, and activate when viewing a weapon rack in the 'q' mode. +Bind to a key (the example config uses P), and activate when viewing a weapon +rack in the 'q' mode. .. image:: images/assign-rack.png @@ -2278,7 +2296,8 @@ Configuration UI ---------------- The configuration front-end to the plugin is implemented by the gui/siege-engine -script. Bind it to a key and activate after selecting a siege engine in 'q' mode. +script. Bind it to a key (the example config uses Alt-A) and activate after selecting +a siege engine in 'q' mode. .. image:: images/siege-engine.png @@ -2310,7 +2329,8 @@ The power-meter plugin implements a modified pressure plate that detects power b supplied to gear boxes built in the four adjacent N/S/W/E tiles. The configuration front-end is implemented by the gui/power-meter script. Bind it to a -key and activate after selecting Pressure Plate in the build menu. +key (the example config uses Ctrl-Shift-M) and activate after selecting Pressure Plate +in the build menu. .. image:: images/power-meter.png diff --git a/images/tweak-mil-color.png b/images/tweak-mil-color.png new file mode 100644 index 0000000000000000000000000000000000000000..b4a012cabf69fe7ab62d0941dcd018f946c86eab GIT binary patch literal 6967 zcmZ{Jby!qgwEhG`4KX04)F2_<(hMUxbP5b1(k0!^AZftRA)$mwhjd8^LwBbjA_5=M zDPF!`-g}?t{&DtN=Y7t*pMCaP>+G}N9jBwELQ4FA7ytl}s-hJ2001x?008phf$xw3 z@F3Bhq1VwcPznzZ2ZV=rc6R>#`}Yn5+%@5Vf8D=-3nJb&-XTCMbrnT01&PMd-3^gD z3he~|kY(Rpy+EI{NDKh*KwnkyiGly(e$KAPG55XxH5yS>2nnf;JGjevo9emWx-KMB z7rd=77%Jz!0DZ5{Yw9^FeEidc4Gj;qqP>0A`}ej{!snc$EoH&>YVCEA__o>kk7boP z$^Npdg54JXb=Li$>sb2jmP#*DI87%#wA`v({JF*j0Y~Tq?Mq^sPVo-l(XtW2Wb>#` z&aT<{+2PrffVL3tl_iz!ZVrw)AMBp2Q5JnQkDkcZ-12zHXOhBYq*tM)V|}B^$JUB>p?j3!rk7B?={=6&r27Ac`d= z!cVj#>+K!>%@T8>F{7rf<}QPsjFIyrjg+zGgA=49h>}wgvlzkRP!glAQgS*>nn!Lm%c0j6dNF`A6Z#$TX zCinqL*wgWz+cA=yYDQ>wXpmz!5&5joMoA?rkKWIH+DVc2=LXkLctXIvm9q~Lf_@AU z*UMdedpB6<_Xw|dw8CTOELZG*QxjW)<_B1F_)@iIRe9bJWb4DTn0`#FC$pSQcs$v1 zM7Uo1ONkre)P}fZVwME3)|$5zww1=tjUOP7{&YF9@E{|k8m-}EK}em%Fgw7)$B$KO<2iy8X6>*nCvYAK;L@F|p z_EC>Tqc*%>3RYS|xbkgXZNN`~Sw5TrFi4|!$imsa4{R=%#n#o8#O1>!XmXBqVT-eiavvu!wP0DC19nfdklgan;~w&Y>&Qk^zrEb%lY=P*GIAh^yxscg zDp~rZs51klV0I-o`wDXRsMZR#>$jipDcoiV(4dJcMUV#Q>TE<7uj(B`WW#JbtrP0|5#`Ej zy3NZFTQ;+tOkr-p$6Sx7@XVm~g!+h!G?v7GjeJxuUr}#PQoi;r;!Ov31yK)yqU!bC zL;8bLc&qF2P<*U-5h_#My3w-zWk8m%vEtybN9qAvrNv;?Arfk&dr6CqbuReB_DZrY zur~uACMVa3wyi;!d6UFia1B8!=EQ#y1=lz*%0Ad@CEs32P3#)l4lY9i?6$?23EzOw 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 Date: Thu, 22 Nov 2012 02:57:55 +0100 Subject: [PATCH 32/55] add scripts/lever, add binary patches section in NEWS file --- NEWS | 9 ++++ scripts/lever.rb | 109 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+) create mode 100644 scripts/lever.rb diff --git a/NEWS b/NEWS index f14bcb1b4..e12608d34 100644 --- a/NEWS +++ b/NEWS @@ -19,6 +19,7 @@ DFHack future - lua: lua interpreter front-end converted to a script from a native command. - dfusion: misc scripts with a text based menu. - embark: lets you embark anywhere. + - lever: list and pull fort levers from the dfhack console. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. @@ -26,6 +27,14 @@ DFHack future - gui/assign-rack: works together with a binary patch to fix weapon racks. - gui/gm-editor: an universal editor for lots of dfhack things. - gui/companion-order: a adventure mode command interface for your companions. + New binary patches: + - armorstand-capacity + - custom-reagent-size + - deconstruct-heapfall + - deconstruct-teleport + - hospital-overstocking + - training-ammo + - weaponrack-unassign Workflow plugin: - properly considers minecarts assigned to routes busy. - code for deducing job outputs rewritten in lua for flexibility. diff --git a/scripts/lever.rb b/scripts/lever.rb new file mode 100644 index 000000000..2012f7297 --- /dev/null +++ b/scripts/lever.rb @@ -0,0 +1,109 @@ +# control your levers from the dfhack console + +def lever_pull_job(bld) + ref = DFHack::GeneralRefBuildingHolderst.cpp_new + ref.building_id = bld.id + + job = DFHack::Job.cpp_new + job.job_type = :PullLever + job.pos = [bld.centerx, bld.centery, bld.z] + job.general_refs << ref + bld.jobs << job + df.job_link job +end + +def lever_pull_cheat(bld) + bld.state = (bld.state == 0 ? 1 : 0) + + bld.linked_mechanisms.each { |i| + i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| + tg = r.building_tg + next if tg.gate_flags.closing or tg.gate_flags.opening + r.building_tg.setTriggerState(tg.gate_flags.closed ? 0 : 1) + } + } + + puts lever_descr(bld) +end + +def lever_descr(bld, idx=nil) + ret = [] + + # lever description + descr = '' + descr << "#{idx}: " if idx + descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" + + bld.linked_mechanisms.map { |i| + i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) + }.flatten.each { |r| + # linked building description + tg = r.building_tg + state = tg.gate_flags.closed ? 'closed' : 'opened' + state << ', closing' if tg.gate_flags.closing + state << ', opening' if tg.gate_flags.opening + + ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") + + # indent other links + descr = descr.gsub(/./, ' ') + } + + ret << descr if ret.empty? + + ret +end + +def lever_list + @lever_list = [] + df.world.buildings.other[:TRAP].find_all { |bld| + bld.trap_type == :Lever + }.sort_by { |bld| bld.id }.each { |bld| + puts lever_descr(bld, @lever_list.length) + @lever_list << bld.id + } +end + +case $script_args[0] +when 'pull' + cheat = $script_args.delete('--cheat') || $script_args.delete('--now') + + id = $script_args[1].to_i + id = @lever_list[id] || id + bld = df.building_find(id) + raise 'invalid lever id' if not bld + + if cheat + lever_pull_cheat(bld) + else + lever_pull_job(bld) + end + +when 'list' + lever_list + +when /^\d+$/ + id = $script_args[0].to_i + id = @lever_list[id] || id + bld = df.building_find(id) + raise 'invalid lever id' if not bld + + puts lever_descr(bld) + +else + puts < Date: Thu, 22 Nov 2012 03:17:41 +0100 Subject: [PATCH 33/55] script/lever: synchronize linked buildings as the game does --- scripts/lever.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/lever.rb b/scripts/lever.rb index 2012f7297..2c7735146 100644 --- a/scripts/lever.rb +++ b/scripts/lever.rb @@ -13,16 +13,14 @@ def lever_pull_job(bld) end def lever_pull_cheat(bld) - bld.state = (bld.state == 0 ? 1 : 0) - bld.linked_mechanisms.each { |i| i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| - tg = r.building_tg - next if tg.gate_flags.closing or tg.gate_flags.opening - r.building_tg.setTriggerState(tg.gate_flags.closed ? 0 : 1) + r.building_tg.setTriggerState(bld.state) } } + bld.state = (bld.state == 0 ? 1 : 0) + puts lever_descr(bld) end From e7905a5cff2b6174d92527ab6bbdd8358b98b877 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 22 Nov 2012 19:38:45 +0400 Subject: [PATCH 34/55] Add docs for the automaterial plugin, and use the new Painter class. --- NEWS | 3 + Readme.html | 123 ++++++++++++++++++++----------- Readme.rst | 39 ++++++++++ images/automaterial-mat.png | Bin 0 -> 5187 bytes images/automaterial-pos.png | Bin 0 -> 3007 bytes library/include/modules/Screen.h | 4 + plugins/automaterial.cpp | 69 ++++++----------- 7 files changed, 147 insertions(+), 91 deletions(-) create mode 100644 images/automaterial-mat.png create mode 100644 images/automaterial-pos.png diff --git a/NEWS b/NEWS index e12608d34..6ba9a769f 100644 --- a/NEWS +++ b/NEWS @@ -47,6 +47,9 @@ DFHack future properly designated barracks be used again for storage of squad equipment. New Search plugin by falconne: Adds an incremental search function to the Stocks, Trading and Unit List screens. + New AutoMaterial plugin by falconne: + Makes building constructions (walls, floors, fortifications, etc) a little bit easier by + saving you from having to trawl through long lists of materials each time you place one. Dfusion plugin: Reworked to make use of lua modules, now all the scripts can be used from other scripts. diff --git a/Readme.html b/Readme.html index f520e1a82..5b231b429 100644 --- a/Readme.html +++ b/Readme.html @@ -508,33 +508,34 @@ access DF memory and allow for easier development of new tools.

  • In-game interface tools
  • -
  • Behavior Mods
  • @@ -2836,15 +2846,42 @@ are actually visible in the list; the same effect applies to the Trade Value numbers displayed by the screen. Because of this, pressing the 't' key while search is active clears the search instead of executing the trade.

    +
    +

    AutoMaterial

    +

    The automaterial plugin makes building constructions (walls, floors, fortifications, +etc) a little bit easier by saving you from having to trawl through long lists of +materials each time you place one.

    +

    Firstly, it moves the last used material for a given construction type to the top of +the list, if there are any left. So if you build a wall with chalk blocks, the next +time you place a wall the chalk blocks will be at the top of the list, regardless of +distance (it only does this in "grouped" mode, as individual item lists could be huge). +This should mean you can place most constructions without having to search for your +preferred material type.

    +images/automaterial-mat.png +

    Pressing 'a' while highlighting any material will enable that material for "auto select" +for this construction type. You can enable multiple materials as autoselect. Now the next +time you place this type of construction, the plugin will automatically choose materials +for you from the kinds you enabled. If there is enough to satisfy the whole placement, +you won't be prompted with the material screen - the construction will be placed and you +will be back in the construction menu as if you did it manually.

    +

    When choosing the construction placement, you will see a couple of options:

    +images/automaterial-pos.png +

    Use 'a' here to temporarily disable the material autoselection, e.g. if you need +to go to the material selection screen so you can toggle some materials on or off.

    +

    The other option (auto type selection, off by default) can be toggled on with 't'. If you +toggle this option on, instead of returning you to the main construction menu after selecting +materials, it returns you back to this screen. If you use this along with several autoselect +enabled materials, you should be able to place complex constructions more conveniently.

    +
    -

    gui/liquids

    +

    gui/liquids

    To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.

    images/liquids.png

    While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

    -

    gui/mechanisms

    +

    gui/mechanisms

    To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.

    images/mechanisms.png

    Lists mechanisms connected to the building, and their links. Navigating the list centers @@ -2854,7 +2891,7 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

    -

    gui/rename

    +

    gui/rename

    Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui.

      @@ -2877,7 +2914,7 @@ their species string.

      unit profession change to Ctrl-Shift-T.

    -

    gui/room-list

    +

    gui/room-list

    To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, either immediately or after opening the assign owner page.

    images/room-list.png @@ -2885,7 +2922,7 @@ either immediately or after opening the assign owner page.

    list, and allows unassigning them.

    -

    gui/choose-weapons

    +

    gui/choose-weapons

    Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize page of the military screen.

    Depending on the cursor location, it rewrites all 'individual choice weapon' entries @@ -2896,7 +2933,7 @@ only that entry, and does it even if it is not 'individual choice'.

    and may lead to inappropriate weapons being selected.

    -

    gui/guide-path

    +

    gui/guide-path

    Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with the cursor over a Guide order.

    images/guide-path.png @@ -2904,7 +2941,7 @@ the cursor over a Guide order.

    computes it when the order is executed for the first time.

    -

    gui/workshop-job

    +

    gui/workshop-job

    Bind to a key (the example config uses Alt-A), and activate with a job selected in a workshop in the 'q' mode.

    images/workshop-job.png @@ -2940,7 +2977,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

    -

    gui/workflow

    +

    gui/workflow

    Bind to a key (the example config uses Alt-W), and activate with a job selected in a workshop in the 'q' mode.

    images/workflow.png @@ -2970,7 +3007,7 @@ suit your need, and set the item count range.

    If you don't need advanced settings, you can just press 'y' to confirm creation.

    -

    gui/assign-rack

    +

    gui/assign-rack

    Bind to a key (the example config uses P), and activate when viewing a weapon rack in the 'q' mode.

    images/assign-rack.png @@ -2995,7 +3032,7 @@ of currently assigned racks for every valid squad.

    -

    Behavior Mods

    +

    Behavior Mods

    These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present.

    @@ -3006,20 +3043,20 @@ technical challenge, and do not represent any long-term plans to produce more similar modifications of the game.

    -

    Siege Engine

    +

    Siege Engine

    The siege-engine plugin enables siege engines to be linked to stockpiles, and aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones.

    -

    Rationale

    +

    Rationale

    Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

    -

    Configuration UI

    +

    Configuration UI

    The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key (the example config uses Alt-A) and activate after selecting a siege engine in 'q' mode.

    @@ -3042,7 +3079,7 @@ menu.

    -

    Power Meter

    +

    Power Meter

    The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

    The configuration front-end is implemented by the gui/power-meter script. Bind it to a @@ -3053,11 +3090,11 @@ in the build menu.

    configuration page, but configures parameters relevant to the modded power meter building.

    -

    Steam Engine

    +

    Steam Engine

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

    -

    Rationale

    +

    Rationale

    The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -3068,7 +3105,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

    -

    Construction

    +

    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.

    @@ -3092,7 +3129,7 @@ short axles that can be built later than both of the engines.

    -

    Operation

    +

    Operation

    In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -3123,7 +3160,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

    -

    Explosions

    +

    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 @@ -3132,7 +3169,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

    -

    Save files

    +

    Save files

    It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -3143,7 +3180,7 @@ being generated.

    -

    Add Spatter

    +

    Add Spatter

    This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index 1f5ac08fd..e6554cac0 100644 --- a/Readme.rst +++ b/Readme.rst @@ -1968,6 +1968,9 @@ are mostly implemented by lua scripts. In order to avoid user confusion, as a matter of policy all these tools display the word "DFHack" on the screen somewhere while active. + When that is not appropriate because they merely add keybinding hints to + existing DF screens, they deliberately use red instead of green for the key. + As an exception, the tweak plugin described above does not follow this guideline because it arguably just fixes small usability bugs in the game UI. @@ -2046,6 +2049,42 @@ Value numbers displayed by the screen. Because of this, pressing the 't' key while search is active clears the search instead of executing the trade. +AutoMaterial +============ + +The automaterial plugin makes building constructions (walls, floors, fortifications, +etc) a little bit easier by saving you from having to trawl through long lists of +materials each time you place one. + +Firstly, it moves the last used material for a given construction type to the top of +the list, if there are any left. So if you build a wall with chalk blocks, the next +time you place a wall the chalk blocks will be at the top of the list, regardless of +distance (it only does this in "grouped" mode, as individual item lists could be huge). +This should mean you can place most constructions without having to search for your +preferred material type. + +.. image:: images/automaterial-mat.png + +Pressing 'a' while highlighting any material will enable that material for "auto select" +for this construction type. You can enable multiple materials as autoselect. Now the next +time you place this type of construction, the plugin will automatically choose materials +for you from the kinds you enabled. If there is enough to satisfy the whole placement, +you won't be prompted with the material screen - the construction will be placed and you +will be back in the construction menu as if you did it manually. + +When choosing the construction placement, you will see a couple of options: + +.. image:: images/automaterial-pos.png + +Use 'a' here to temporarily disable the material autoselection, e.g. if you need +to go to the material selection screen so you can toggle some materials on or off. + +The other option (auto type selection, off by default) can be toggled on with 't'. If you +toggle this option on, instead of returning you to the main construction menu after selecting +materials, it returns you back to this screen. If you use this along with several autoselect +enabled materials, you should be able to place complex constructions more conveniently. + + gui/liquids =========== diff --git a/images/automaterial-mat.png b/images/automaterial-mat.png new file mode 100644 index 0000000000000000000000000000000000000000..5517bbc6d33636b9842da1b712cac161a74a4f39 GIT binary patch literal 5187 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 literal 0 HcmV?d00001 diff --git a/images/automaterial-pos.png b/images/automaterial-pos.png new file mode 100644 index 0000000000000000000000000000000000000000..959573643a8d8a0ba3e9c686dd80e1885f4ad318 GIT binary patch literal 3007 zcma)83pA8l8~(;H?lYt0n&~5)Fw{&JC#h!43?ov;tx?By5JHBCqA}w#-T0Ktp_|0y zc2pB8w;V|$Ni^i{BywDT@bn6B z@94%XxP$Pqv!TkWdGn{>f&5WAGXen6m*KYwaqLrK0sts<+EU5R$NOdm>q7f2RXN_M zO<*2#aaTpt|VB9KDWJ%e$eF^RMY$C z__mg`x092F7Pl+gf2-=Ues};a#Z>IDEDe60c7{cY+EEvG_e4tW z4lT}TB~ECxqJffO6kDF!Xdcx58C&d7+E-Rp8uI6LT&8E|krF}lo^^9R^IP*$`1ASU zuFDZ)EBY6z4MkIz`lAwR>vPnt!LyY}g4*QA9g2!01y6UYvDR?QrXP6P#x=Wj9%)36 zkTTk_P;IX)_4d8c-OyUmRJkHGS_LVIo2!xc!IvPv8mO^?$>%5o6fvjj7%yi6G?8V5 zqPQ74`IcQlk?vE^Rk(OAa;*P|w`ab`8}o|^(@|>F^Y*uD+;(tv5RSi4-<9nDu*i%{ zxrb{c--fzV6>Mhc_&bu-!ylcpdUN_DjB~st0j9wa&6Chf?@JuXtMT|FMa4|uN_W9 zhD2}7hw)un!alO(tBb2Pz2C)Eoj&D~Ro9?(EYo11CqlYu)?hR1LEdZf0MH$u=_i3U z$V`YRRb6XbOfgKE-wU%+iqxOZh%Bi+<= zKU$0IJycjj1lE4pz!_a#dN>Ud@}6n3diLog@C`*Sw}k$2y(dyNE~tqHc-0#67jm#8 z7gSb@&zf*go~*rCe6bkWsmy2_m(|yVu3cLJY?0I_(9+!{BwefUqNLGnX-Sq&1a~XR z5%kwU7*eNHjI6#9lostncL$A&A~G|3T-0LdK{0YUu|7E#U|_=jL-x})HB&K68e>j4 zt1~(p!_QJ^%4z zZ>Roo<@{HhS1u2GWhYoVW5N3+rMC;t06c0jzeW4 zh3n>jRb2~D&gj^hBn@^P)~VKbG5Ljd5EDl6b>Uf2t*;zR90wqhcQ}PRrldW&^uoRl14v=6*ucOEGv8sy=JLd;V8BNXRF_rZK=2Mf5(N)^`yZ`>k7DA>!_6!e%J&THxGdp@ zht=N@b^Ob2s-x{y_dY`Jy8g{O;txoA&P`!X-ld(HP zTjZMRmLH|yr~ummvQ|o;fByQ7HOz(`uV=G1m+y5>0!E2CZA!yh`MNjCg`Wy(fx@sB zN`bs$S4^~Ht%^9LtYet4P5ZU*b47RTl@lY5Br`{gboLiVQjuSqdC|KWL4av%6Z_)r z{jC-O`=c+#SK=}GjW$uKCGsGF{?I*ZcPIl(dX8cQO|LugN|+d#UAHYDd;GpwiprdZ z9cQeXZL1g2Bx~+;i1~zDYYnK=y{RN9uri(U4`wbd-|2YDiu(u^mkV*FKdr;0y4Huvh@`=) zt$XS@hh(Ykt*g35@psiTtE8}+VZa0W#AxfY$QL^c`p)hK!|%og6`GRFx+#|Y2}4f; zzdtu}P$rgSrb=nVjpW-JYJ>H8#~$E690D<|y44?dKd-y?$%|Qd26tT@EtH^I%ii0J1aa7k|;p7bO z7^ys?xaRWhUE5pRTP5l>oI|Qj;yrjiMmK4>G#xF)p1|eHmb*e;9$DDm_a<=;{gf;g zuof;UAhw5Tj=y(tT{5Ek=C7r67m`y)b>lr;HF^!eWt2Yg*q)y_1IGM?j_0E5%ST3a%{IBs3GI7A7n01Pip!cOf2CC@oD6ZuKGh=>zjM#XW{&EzCcmqaWiXR{ zRMQUk&?Mcx+Yt(zC)#eif6m1)X@(&dem&&Z70l$d9?)6i6~w$bu500hA)omoXnfgv zAb-!6r6gfq*4Eo8iEkVU73aptOKrQvLTeuXsL_;%Uzr8{{imB*TaA+|-1eqs-O<{a z=-v%NvEc-5_hw=Aq}uNg&9rpI?_I!p%!i~B7!cHsi53elh{QH#Ms|YuA%RWVktT(l zd&=y?S|*ic^#RlISdDV<2y> +#include #include "DataDefs.h" #include "df/graphic.h" @@ -51,6 +53,8 @@ namespace DFHack { class Core; + typedef std::set interface_key_set; + /** * The Screen module * \ingroup grp_modules diff --git a/plugins/automaterial.cpp b/plugins/automaterial.cpp index ac5a4ae22..9f383b935 100644 --- a/plugins/automaterial.cpp +++ b/plugins/automaterial.cpp @@ -74,28 +74,6 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } - -void OutputString(int8_t color, int &x, int &y, const std::string &text, bool newline = false, int left_margin = 0) -{ - Screen::paintString(Screen::Pen(' ', color, 0), x, y, text); - if (newline) - { - ++y; - x = left_margin; - } - else - x += text.length(); -} - -void OutputHotkeyString(int &x, int &y, const char *text, const char *hotkey, bool newline = false, int left_margin = 0, int8_t color = COLOR_WHITE) -{ - OutputString(10, x, y, hotkey); - string display(": "); - display.append(text); - OutputString(color, x, y, display, newline, left_margin); -} - - static inline bool in_material_choice_stage() { return Gui::build_selector_hotkey(Core::getTopViewscreen()) && @@ -137,7 +115,7 @@ static inline MaterialDescriptor &get_last_used_material() return last_used_material[ui_build_selector->building_subtype]; } -static void set_last_used_material(MaterialDescriptor &matetial) +static void set_last_used_material(const MaterialDescriptor &matetial) { last_used_material[ui_build_selector->building_subtype] = matetial; } @@ -150,7 +128,7 @@ static MaterialDescriptor &get_last_moved_material() return last_moved_material[ui_build_selector->building_subtype]; } -static void set_last_moved_material(MaterialDescriptor &matetial) +static void set_last_moved_material(const MaterialDescriptor &matetial) { last_moved_material[ui_build_selector->building_subtype] = matetial; } @@ -306,9 +284,9 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest !in_material_choice_stage() && hotkeys.find(last_used_constr_subtype) != hotkeys.end()) { - input->clear(); - input->insert(hotkeys[last_used_constr_subtype]); - this->feed(input); + interface_key_set keys; + keys.insert(hotkeys[last_used_constr_subtype]); + INTERPOSE_NEXT(feed)(&keys); } } @@ -349,47 +327,42 @@ struct jobutils_hook : public df::viewscreen_dwarfmodest MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); if (material.valid) { - int left_margin = gps->dimx - 30; - int x = left_margin; - int y = 25; - - string toggle_string = "Enable"; string title = "Disabled"; if (check_autoselect(material, false)) { - toggle_string = "Disable"; title = "Enabled"; } - OutputString(COLOR_BROWN, x, y, "DFHack Autoselect: " + title, true, left_margin); - OutputHotkeyString(x, y, toggle_string.c_str(), "a", true, left_margin); + auto dims = Gui::getDwarfmodeViewDims(); + Screen::Painter dc(dims.menu()); + + dc.seek(1,24).key_pen(COLOR_LIGHTRED).pen(COLOR_WHITE); + dc.key(interface_key::CUSTOM_A).string(": Autoselect "+title); } } - else if (in_placement_stage() && ui_build_selector->building_subtype != 7) + else if (in_placement_stage() && ui_build_selector->building_subtype < construction_type::TrackN) { - int left_margin = gps->dimx - 30; - int x = left_margin; - int y = 25; + string autoselect_toggle = (auto_choose_materials) ? "Disable" : "Enable"; + string revert_toggle = (revert_to_last_used_type) ? "Disable" : "Enable"; - string autoselect_toggle_string = (auto_choose_materials) ? "Disable Auto Mat-select" : "Enable Auto Mat-select"; - string revert_toggle_string = (revert_to_last_used_type) ? "Disable Auto Type-select" : "Enable Auto Type-select"; + auto dims = Gui::getDwarfmodeViewDims(); + Screen::Painter dc(dims.menu()); - OutputString(COLOR_BROWN, x, y, "DFHack Options", true, left_margin); - OutputHotkeyString(x, y, autoselect_toggle_string.c_str(), "a", true, left_margin); - OutputHotkeyString(x, y, revert_toggle_string.c_str(), "t", true, left_margin); + dc.seek(1,23).key_pen(COLOR_LIGHTRED).pen(COLOR_WHITE); + dc.key(interface_key::CUSTOM_A).string(": "+autoselect_toggle+" Auto Mat-Select").newline(1); + dc.key(interface_key::CUSTOM_T).string(": "+revert_toggle+" Auto Type-Select"); } } }; -color_ostream_proxy console_out(Core::getInstance().getConsole()); - - IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(jobutils_hook, render); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - if (!gps || !INTERPOSE_HOOK(jobutils_hook, feed).apply() || !INTERPOSE_HOOK(jobutils_hook, render).apply()) + if (!gps || !ui_build_selector || + !INTERPOSE_HOOK(jobutils_hook, feed).apply() || + !INTERPOSE_HOOK(jobutils_hook, render).apply()) out.printerr("Could not insert jobutils hooks!\n"); hotkeys[construction_type::Wall] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_WALL; From 2a0d04804068a3e63866fa128711a0d1c2add8b4 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 22 Nov 2012 20:08:47 +0400 Subject: [PATCH 35/55] Make tweak stable-cursor interact with the build menu properly. --- NEWS | 1 + plugins/tweak.cpp | 22 +++++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index 6ba9a769f..463ebb47a 100644 --- a/NEWS +++ b/NEWS @@ -11,6 +11,7 @@ DFHack future - added a small stand-alone utility for applying and removing binary patches. - removebadthoughts: add --dry-run option - superdwarf: work in adventure mode too + - tweak stable-cursor: carries cursor location from/to Build menu. New tweaks: - tweak military-training: speed up melee squad training up to 10x (normally 3-5x). New scripts: diff --git a/plugins/tweak.cpp b/plugins/tweak.cpp index 7143e715e..70d915ffc 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -212,15 +212,31 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest { typedef df::viewscreen_dwarfmodest interpose_base; + bool check_default() + { + switch (ui->main.mode) { + case ui_sidebar_mode::Default: + return true; + + case ui_sidebar_mode::Build: + return ui_build_selector && + (ui_build_selector->building_type < 0 || + ui_build_selector->stage < 1); + + default: + return false; + } + } + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) { - bool was_default = (ui->main.mode == df::ui_sidebar_mode::Default); + bool was_default = check_default(); df::coord view = Gui::getViewportPos(); df::coord cursor = Gui::getCursorPos(); INTERPOSE_NEXT(feed)(input); - bool is_default = (ui->main.mode == df::ui_sidebar_mode::Default); + bool is_default = check_default(); df::coord cur_cursor = Gui::getCursorPos(); if (is_default && !was_default) @@ -241,7 +257,7 @@ struct stable_cursor_hook : df::viewscreen_dwarfmodest tmp.insert(interface_key::CURSOR_UP_Z); INTERPOSE_NEXT(feed)(&tmp); } - else if (cur_cursor.isValid()) + else if (!is_default && cur_cursor.isValid()) { last_cursor = df::coord(); } From e3eb325d3680aa06b5fa0cde68da186345858e34 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 23 Nov 2012 19:18:56 -0600 Subject: [PATCH 36/55] Minimize references to gps->dimx/dimy --- library/modules/Screen.cpp | 45 ++++++++++++++------------ plugins/manipulator.cpp | 66 ++++++++++++++++++++------------------ plugins/search.cpp | 8 +++-- 3 files changed, 64 insertions(+), 55 deletions(-) diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index f2d1f2d5d..cd20bc25e 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -110,10 +110,10 @@ bool Screen::paintTile(const Pen &pen, int x, int y) { if (!gps || !pen.valid()) return false; - int dimx = gps->dimx, dimy = gps->dimy; - if (x < 0 || x >= dimx || y < 0 || y >= dimy) return false; + auto dim = getWindowSize(); + if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return false; - doSetTile(pen, x*dimy + y); + doSetTile(pen, x*dim.y + y); return true; } @@ -121,11 +121,11 @@ 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) + auto dim = getWindowSize(); + if (x < 0 || x >= dim.x || y < 0 || y >= dim.y) return Pen(0,0,0,-1); - int index = x*dimy + y; + int index = x*dim.y + y; auto screen = gps->screen + index*4; if (screen[3] & 0x80) return Pen(0,0,0,-1); @@ -154,14 +154,15 @@ Pen Screen::readTile(int x, int y) bool Screen::paintString(const Pen &pen, int x, int y, const std::string &text) { - if (!gps || y < 0 || y >= gps->dimy) return false; + auto dim = getWindowSize(); + if (!gps || y < 0 || y >= dim.y) return false; Pen tmp(pen); bool ok = false; for (size_t i = -std::min(0,x); i < text.size(); i++) { - if (x + i >= size_t(gps->dimx)) + if (x + i >= size_t(dim.x)) break; tmp.ch = text[i]; @@ -175,17 +176,18 @@ 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) { + auto dim = getWindowSize(); if (!gps || !pen.valid()) return false; if (x1 < 0) x1 = 0; if (y1 < 0) y1 = 0; - if (x2 >= gps->dimx) x2 = gps->dimx-1; - if (y2 >= gps->dimy) y2 = gps->dimy-1; + if (x2 >= dim.x) x2 = dim.x-1; + if (y2 >= dim.y) y2 = dim.y-1; if (x1 > x2 || y1 > y2) return false; for (int x = x1; x <= x2; x++) { - int index = x*gps->dimy; + int index = x*dim.y; for (int y = y1; y <= y2; y++) doSetTile(pen, index+y); @@ -198,32 +200,33 @@ bool Screen::drawBorder(const std::string &title) { if (!gps) return false; - int dimx = gps->dimx, dimy = gps->dimy; + auto dim = getWindowSize(); Pen border('\xDB', 8); Pen text(0, 0, 7); Pen signature(0, 0, 8); - for (int x = 0; x < dimx; x++) + for (int x = 0; x < dim.x; x++) { - doSetTile(border, x * dimy + 0); - doSetTile(border, x * dimy + dimy - 1); + doSetTile(border, x * dim.y + 0); + doSetTile(border, x * dim.y + dim.y - 1); } - for (int y = 0; y < dimy; y++) + for (int y = 0; y < dim.y; y++) { - doSetTile(border, 0 * dimy + y); - doSetTile(border, (dimx - 1) * dimy + y); + doSetTile(border, 0 * dim.y + y); + doSetTile(border, (dim.x - 1) * dim.y + y); } - paintString(signature, dimx-8, dimy-1, "DFHack"); + paintString(signature, dim.x-8, dim.y-1, "DFHack"); - return paintString(text, (dimx - title.length()) / 2, 0, title); + return paintString(text, (dim.x - title.length()) / 2, 0, title); } bool Screen::clear() { if (!gps) return false; - return fillRect(Pen(' ',0,0,false), 0, 0, gps->dimx-1, gps->dimy-1); + auto dim = getWindowSize(); + return fillRect(Pen(' ',0,0,false), 0, 0, dim.x-1, dim.y-1); } bool Screen::invalidate() diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 79999d468..59b257979 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -456,11 +456,13 @@ void viewscreen_unitlaborsst::refreshNames() void viewscreen_unitlaborsst::calcSize() { - num_rows = gps->dimy - 10; + auto dim = Screen::getWindowSize(); + + num_rows = dim.y - 10; if (num_rows > units.size()) num_rows = units.size(); - int num_columns = gps->dimx - DISP_COLUMN_MAX - 1; + int num_columns = dim.x - DISP_COLUMN_MAX - 1; // min/max width of columns int col_minwidth[DISP_COLUMN_MAX]; @@ -940,10 +942,11 @@ void viewscreen_unitlaborsst::render() dfhack_viewscreen::render(); + auto dim = Screen::getWindowSize(); + Screen::clear(); Screen::drawBorder(" Dwarf Manipulator - Manage Labors "); - Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_HAPPINESS], 2, "Hap."); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_NAME], 2, "Name"); Screen::paintString(Screen::Pen(' ', 7, 0), col_offsets[DISP_COLUMN_PROFESSION], 2, "Profession"); @@ -1116,48 +1119,48 @@ void viewscreen_unitlaborsst::render() canToggle = (cur->allowEdit) && (columns[sel_column].labor != unit_labor::NONE); } - int x = 2; - OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT)); - OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle labor, "); + int x = 2, y = dim.y - 3; + OutputString(10, x, dim.y - 3, Screen::getKeyDisplay(interface_key::SELECT)); + OutputString(canToggle ? 15 : 8, x, y, ": Toggle labor, "); - OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL)); - OutputString(canToggle ? 15 : 8, x, gps->dimy - 3, ": Toggle Group, "); + OutputString(10, x, dim.y - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL)); + OutputString(canToggle ? 15 : 8, x, y, ": Toggle Group, "); - OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); - OutputString(15, x, gps->dimy - 3, ": ViewCre, "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); + OutputString(15, x, y, ": ViewCre, "); - OutputString(10, x, gps->dimy - 3, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); - OutputString(15, x, gps->dimy - 3, ": Zoom-Cre"); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_ZOOM_CRE)); + OutputString(15, x, y, ": Zoom-Cre"); - x = 2; - OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::LEAVESCREEN)); - OutputString(15, x, gps->dimy - 2, ": Done, "); + x = 2; y = dim.y - 2; + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::LEAVESCREEN)); + OutputString(15, x, y, ": Done, "); - OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN)); - OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP)); - OutputString(15, x, gps->dimy - 2, ": Sort by Skill, "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_DOWN)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_UP)); + OutputString(15, x, y, ": Sort by Skill, "); - OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN)); - OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP)); - OutputString(15, x, gps->dimy - 2, ": Sort by ("); - OutputString(10, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::CHANGETAB)); - OutputString(15, x, gps->dimy - 2, ") "); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEDOWN)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SECONDSCROLL_PAGEUP)); + OutputString(15, x, y, ": Sort by ("); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::CHANGETAB)); + OutputString(15, x, y, ") "); switch (altsort) { case ALTSORT_NAME: - OutputString(15, x, gps->dimy - 2, "Name"); + OutputString(15, x, y, "Name"); break; case ALTSORT_PROFESSION: - OutputString(15, x, gps->dimy - 2, "Profession"); + OutputString(15, x, y, "Profession"); break; case ALTSORT_HAPPINESS: - OutputString(15, x, gps->dimy - 2, "Happiness"); + OutputString(15, x, y, "Happiness"); break; case ALTSORT_ARRIVAL: - OutputString(15, x, gps->dimy - 2, "Arrival"); + OutputString(15, x, y, "Arrival"); break; default: - OutputString(15, x, gps->dimy - 2, "Unknown"); + OutputString(15, x, y, "Unknown"); break; } } @@ -1193,9 +1196,10 @@ struct unitlist_hook : df::viewscreen_unitlistst if (units[page].size()) { - int x = 2; - OutputString(12, x, gps->dimy - 2, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF)); - OutputString(15, x, gps->dimy - 2, ": Manage labors (DFHack)"); + auto dim = Screen::getWindowSize(); + int x = 2, y = dim.y - 2; + OutputString(12, x, y, Screen::getKeyDisplay(interface_key::UNITVIEW_PRF_PROF)); + OutputString(15, x, y, ": Manage labors (DFHack)"); } } }; diff --git a/plugins/search.cpp b/plugins/search.cpp index cc3f29c12..742fa9277 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -329,8 +329,9 @@ protected: // Display hotkey message void print_search_option(int x, int y = -1) const { + auto dim = Screen::getWindowSize(); if (y == -1) - y = gps->dimy - 2; + y = dim.y - 2; OutputString((entry_mode) ? 4 : 12, x, y, string(1, select_key)); OutputString((entry_mode) ? 10 : 15, x, y, ": Search"); @@ -413,8 +414,9 @@ public: print_search_option(2); else { - int x = 2; - OutputString(15, x, gps->dimy - 2, "Tab to enable Search"); + auto dim = Screen::getWindowSize(); + int x = 2, y = dim.y - 2; + OutputString(15, x, y, "Tab to enable Search"); } } From 139fd07df3e738fec33842d645fb1ac947679e61 Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 23 Nov 2012 19:23:06 -0600 Subject: [PATCH 37/55] missed a spot --- plugins/manipulator.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 59b257979..e8a91fdb6 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1120,10 +1120,10 @@ void viewscreen_unitlaborsst::render() } int x = 2, y = dim.y - 3; - OutputString(10, x, dim.y - 3, Screen::getKeyDisplay(interface_key::SELECT)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT)); OutputString(canToggle ? 15 : 8, x, y, ": Toggle labor, "); - OutputString(10, x, dim.y - 3, Screen::getKeyDisplay(interface_key::SELECT_ALL)); + OutputString(10, x, y, Screen::getKeyDisplay(interface_key::SELECT_ALL)); OutputString(canToggle ? 15 : 8, x, y, ": Toggle Group, "); OutputString(10, x, y, Screen::getKeyDisplay(interface_key::UNITJOB_VIEW)); From 8429f651766566f697f36347faba23a98d987f72 Mon Sep 17 00:00:00 2001 From: jj Date: Thu, 22 Nov 2012 16:56:22 +0100 Subject: [PATCH 38/55] add scripts/stripcaged.rb and documentation --- NEWS | 1 + Readme.rst | 45 ++++++++++ scripts/stripcaged.rb | 194 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 240 insertions(+) create mode 100644 scripts/stripcaged.rb diff --git a/NEWS b/NEWS index 463ebb47a..65c647337 100644 --- a/NEWS +++ b/NEWS @@ -21,6 +21,7 @@ DFHack future - dfusion: misc scripts with a text based menu. - embark: lets you embark anywhere. - lever: list and pull fort levers from the dfhack console. + - stripcaged: mark items inside cages for dumping, eg caged goblin weapons. New GUI scripts: - gui/guide-path: displays the cached path for minecart Guide orders. - gui/workshop-job: displays inputs of a workshop job and allows tweaking them. diff --git a/Readme.rst b/Readme.rst index e6554cac0..3434a240d 100644 --- a/Readme.rst +++ b/Readme.rst @@ -144,6 +144,16 @@ system console: The patches are expected to be encoded in text format used by IDA. + +Live patching +------------- + +As an alternative, you can use the ``binpatch`` dfhack command to apply/remove +patches live in memory during a DF session. + +In this case, updating symbols.xml is not necessary. + + ============================= Something doesn't work, help! ============================= @@ -1956,6 +1966,41 @@ embark ====== Allows to embark anywhere. Currently windows only. +lever +===== +Allow manipulation of in-game levers from the dfhack console. + +Can list levers, including state and links, with:: + + lever list + +To queue a job so that a dwarf will pull the lever 42, use ``lever pull 42``. +This is the same as 'q'uerying the building and queue a 'P'ull request. + +To magically toggle the lever immediately, use:: + + lever pull 42 --now + +stripcaged +========== +For dumping items inside cages. Will mark selected items for dumping, then +a dwarf may come and actually dump it. See also ``autodump``. + +With the ``items`` argument, only dumps items laying in the cage, excluding +stuff worn by caged creatures. ``weapons`` will dump worn weapons, ``armor`` +will dump everything worn by caged creatures (including armor and clothing), +and ``all`` will dump everything, on a creature or not. + +``stripcaged list`` will display on the dfhack console the list of all cages +and their item content. + +Without further arguments, all commands work on all cages and animal traps on +the map. With the ``here`` argument, considers only the in-game selected cage +(or the cage under the game cursor). To target only specific cages, you can +alternatively pass cage IDs as arguments:: + + stripcaged weapons 25321 34228 + ======================= In-game interface tools ======================= diff --git a/scripts/stripcaged.rb b/scripts/stripcaged.rb new file mode 100644 index 000000000..fa9c49552 --- /dev/null +++ b/scripts/stripcaged.rb @@ -0,0 +1,194 @@ +# mark stuff inside of cages for dumping. + +def plural(nr, name) + # '1 cage' / '4 cages' + "#{nr} #{name}#{'s' if nr > 1}" +end + +def cage_dump_items(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsItemst) + next if ref.item_tg.flags.dump + count += 1 + ref.item_tg.flags.dump = true + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_armor(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) + ref.unit_tg.inventory.each { |it| + next if it.mode != :Worn + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_weapons(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) + ref.unit_tg.inventory.each { |it| + next if it.mode != :Weapon + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_all(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + case ref + when DFHack::GeneralRefContainsItemst + next if ref.item_tg.flags.dump + count += 1 + ref.item_tg.flags.dump = true + when DFHack::GeneralRefContainsUnitst + ref.unit_tg.inventory.each { |it| + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + end + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" +end + + +def cage_dump_list(list) + count_total = Hash.new(0) + list.each { |cage| + count = Hash.new(0) + + cage.general_refs.each { |ref| + case ref + when DFHack::GeneralRefContainsItemst + count[ref.item_tg._rtti_classname] += 1 + when DFHack::GeneralRefContainsUnitst + ref.unit_tg.inventory.each { |it| + count[it.item._rtti_classname] += 1 + } + # TODO vermin ? + else + puts "unhandled ref #{ref.inspect}" if $DEBUG + end + } + + type = case cage + when DFHack::ItemCagest; 'Cage' + when DFHack::ItemAnimaltrapst; 'Animal trap' + else cage._rtti_classname + end + + puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + + count.each { |k, v| count_total[k] += v } + } + + if list.length > 2 + puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + end +end + + +# handle magic script arguments +here_only = $script_args.delete 'here' +if here_only + it = df.item_find + list = [it] + if not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) + list = df.world.items.other[:ANY_CAGE_OR_TRAP].find_all { |i| df.at_cursor?(i) } + end + puts 'Please select a cage' if list.empty? + +elsif ids = $script_args.find_all { |arg| arg =~ /^\d+$/ } and ids.first + list = [] + ids.each { |id| + $script_args.delete id + if not it = df.item_find(id.to_i) + puts "Invalid item id #{id}" + elsif not it.kind_of?(DFHack::ItemCagest) and not it.kind_of?(DFHack::ItemAnimaltrapst) + puts "Item ##{id} is not a cage" + list << it + else + list << it + end + } + puts 'Please use a valid cage id' if list.empty? + +else + list = df.world.items.other[:ANY_CAGE_OR_TRAP] +end + + +# act +case $script_args[0] +when 'items' + cage_dump_items(list) if not list.empty? +when 'armor' + cage_dump_armor(list) if not list.empty? +when 'weapons' + cage_dump_weapons(list) if not list.empty? +when 'all' + cage_dump_all(list) if not list.empty? + +when 'list' + cage_dump_list(list) if not list.empty? + +else + puts < Date: Thu, 22 Nov 2012 17:42:10 +0100 Subject: [PATCH 39/55] scripts/lever: show pending jobs --- scripts/lever.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/scripts/lever.rb b/scripts/lever.rb index 2c7735146..59196f7d2 100644 --- a/scripts/lever.rb +++ b/scripts/lever.rb @@ -10,6 +10,8 @@ def lever_pull_job(bld) job.general_refs << ref bld.jobs << job df.job_link job + + puts lever_descr(bld) end def lever_pull_cheat(bld) @@ -31,6 +33,14 @@ def lever_descr(bld, idx=nil) descr = '' descr << "#{idx}: " if idx descr << "lever ##{bld.id} @[#{bld.centerx}, #{bld.centery}, #{bld.z}] #{bld.state == 0 ? '\\' : '/'}" + bld.jobs.each { |j| + if j.job_type == :PullLever + flags = '' + flags << ', repeat' if j.flags.repeat + flags << ', suspended' if j.flags.suspend + descr << " (pull order#{flags})" + end + } bld.linked_mechanisms.map { |i| i.general_refs.grep(DFHack::GeneralRefBuildingHolderst) From cb06c896984735c59a72570dd50f5dfc62bc40ab Mon Sep 17 00:00:00 2001 From: jj Date: Fri, 23 Nov 2012 17:20:16 +0100 Subject: [PATCH 40/55] stripcaged: dont list empty cages individually --- scripts/stripcaged.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/scripts/stripcaged.rb b/scripts/stripcaged.rb index fa9c49552..07694f711 100644 --- a/scripts/stripcaged.rb +++ b/scripts/stripcaged.rb @@ -90,6 +90,7 @@ end def cage_dump_list(list) count_total = Hash.new(0) + empty_cages = 0 list.each { |cage| count = Hash.new(0) @@ -113,13 +114,18 @@ def cage_dump_list(list) else cage._rtti_classname end - puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + if count.empty? + empty_cages += 1 + else + puts "#{type} ##{cage.id}: ", count.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + end count.each { |k, v| count_total[k] += v } } if list.length > 2 puts '', "Total: ", count_total.sort_by { |k, v| v }.map { |k, v| " #{v} #{k}" } + puts "with #{plural(empty_cages, 'empty cage')}" end end From e73274d281e4f7f8b476a64a4854400dbb40c791 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 24 Nov 2012 16:05:03 +0100 Subject: [PATCH 41/55] ruby: add description field to onupdate_register --- plugins/ruby/README | 4 ++-- plugins/ruby/ruby.rb | 38 ++++++++++++++++++++------------------ scripts/autofarm.rb | 16 ++++++++-------- scripts/autounsuspend.rb | 6 +++--- scripts/magmasource.rb | 2 +- scripts/slayrace.rb | 2 +- scripts/superdwarf.rb | 4 ++-- 7 files changed, 37 insertions(+), 35 deletions(-) diff --git a/plugins/ruby/README b/plugins/ruby/README index 9246fec88..d35c34bbe 100644 --- a/plugins/ruby/README +++ b/plugins/ruby/README @@ -125,9 +125,9 @@ DFHack callbacks The plugin interfaces with dfhack 'onupdate' hook. To register ruby code to be run every graphic frame, use: - handle = df.onupdate_register { puts 'i love flooding the console' } + handle = df.onupdate_register('log') { puts 'i love flooding the console' } You can also rate-limit when your callback is called to a number of game ticks: - handle = df.onupdate_register(10) { puts '10 more in-game ticks elapsed' } + handle = df.onupdate_register('myname', 10) { puts '10 more in-game ticks elapsed' } In this case, the callback is called immediately, and then every X in-game ticks (advances only when the game is unpaused). To stop being called, use: diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index ab095e8d8..4fcb5543a 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -24,8 +24,9 @@ end module DFHack class OnupdateCallback - attr_accessor :callback, :timelimit, :minyear, :minyeartick - def initialize(cb, tl, initdelay=0) + attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description + def initialize(descr, cb, tl, initdelay=0) + @description = descr @callback = cb @ticklimit = tl @minyear = (tl ? df.cur_year : 0) @@ -34,22 +35,21 @@ module DFHack # run callback if timedout def check_run(year, yeartick, yearlen) - if !@ticklimit - @callback.call - else - if year > @minyear or (year == @minyear and yeartick >= @minyeartick) - @minyear = year - @minyeartick = yeartick + @ticklimit - if @minyeartick > yearlen - @minyear += 1 - @minyeartick -= yearlen - end - @callback.call + if @ticklimit + return unless year > @minyear or (year == @minyear and yeartick >= @minyeartick) + @minyear = year + @minyeartick = yeartick + @ticklimit + if @minyeartick > yearlen + @minyear += 1 + @minyeartick -= yearlen end end + # t0 = Time.now + @callback.call + # dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1 rescue df.onupdate_unregister self - puts_err "onupdate cb #$!", $!.backtrace + puts_err "onupdate #@description unregistered: #$!", $!.backtrace end def <=>(o) @@ -61,10 +61,11 @@ module DFHack attr_accessor :onupdate_list, :onstatechange_list # register a callback to be called every gframe or more - # ex: DFHack.onupdate_register { DFHack.world.units[0].counters.job_counter = 0 } - def onupdate_register(ticklimit=nil, initialtickdelay=0, &b) + # ex: DFHack.onupdate_register('fastdwarf') { DFHack.world.units[0].counters.job_counter = 0 } + def onupdate_register(descr, ticklimit=nil, initialtickdelay=0, &b) + raise ArgumentError, 'need a description as 1st arg' unless descr.kind_of?(::String) @onupdate_list ||= [] - @onupdate_list << OnupdateCallback.new(b, ticklimit, initialtickdelay) + @onupdate_list << OnupdateCallback.new(descr, b, ticklimit, initialtickdelay) DFHack.onupdate_active = true if onext = @onupdate_list.sort.first DFHack.onupdate_minyear = onext.minyear @@ -73,8 +74,9 @@ module DFHack @onupdate_list.last end - # delete the callback for onupdate ; use the value returned by onupdate_register + # delete the callback for onupdate ; use the value returned by onupdate_register or the description def onupdate_unregister(b) + b = @onupdate_list.find { |bb| bb.description == b } if b.kind_of?(String) @onupdate_list.delete b if @onupdate_list.empty? DFHack.onupdate_active = false diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index 098466745..c89cb9ff4 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -5,7 +5,7 @@ class AutoFarm @lastcounts = Hash.new(0) end - def setthreshold (id, v) + def setthreshold(id, v) if df.world.raws.plants.all.find { |r| r.id == id } @thresholds[id] = v.to_i else @@ -13,11 +13,11 @@ class AutoFarm end end - def setdefault (v) + def setdefault(v) @thresholds.default = v.to_i end - def is_plantable (plant) + def is_plantable(plant) season = df.cur_season harvest = df.cur_season_tick + plant.growdur * 10 will_finish = harvest < 10080 @@ -40,7 +40,7 @@ class AutoFarm return plantable end - def set_farms ( plants, farms) + def set_farms( plants, farms) return if farms.length == 0 if plants.length == 0 plants = [-1] @@ -66,7 +66,7 @@ class AutoFarm if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact1 && plantable.has_key? (i.mat_index)) + !i.flags.artifact1 && plantable.has_key?(i.mat_index)) counts[i.mat_index] = counts[i.mat_index] + i.stack_size end } @@ -95,13 +95,13 @@ class AutoFarm end } - set_farms (plants_s, farms_s) - set_farms (plants_u, farms_u) + set_farms(plants_s, farms_s) + set_farms(plants_u, farms_u) end def start - @onupdate = df.onupdate_register (100) { process } + @onupdate = df.onupdate_register('autofarm', 100) { process } @running = true end diff --git a/scripts/autounsuspend.rb b/scripts/autounsuspend.rb index 45dd8df4d..c7fe20748 100644 --- a/scripts/autounsuspend.rb +++ b/scripts/autounsuspend.rb @@ -26,7 +26,7 @@ class AutoUnsuspend end def start - @onupdate = df.onupdate_register (5) { process } + @onupdate = df.onupdate_register('autounsuspend', 5) { process } @running = true end @@ -36,7 +36,7 @@ class AutoUnsuspend end def status - stat = @running ? "Running." : "Loaded." + @running ? 'Running.' : 'Stopped.' end end @@ -53,6 +53,6 @@ else if $AutoUnsuspend puts $AutoUnsuspend.status else - puts "AI not started" + puts 'Not loaded.' end end diff --git a/scripts/magmasource.rb b/scripts/magmasource.rb index e97080834..c20199c2a 100644 --- a/scripts/magmasource.rb +++ b/scripts/magmasource.rb @@ -4,7 +4,7 @@ $magma_sources ||= [] case $script_args[0] when 'here' - $magma_onupdate ||= df.onupdate_register(12) { + $magma_onupdate ||= df.onupdate_register('magmasource', 12) { # called every 12 game ticks (100x a dwarf day) if $magma_sources.empty? df.onupdate_unregister($magma_onupdate) diff --git a/scripts/slayrace.rb b/scripts/slayrace.rb index 749d0189b..ca50020f7 100644 --- a/scripts/slayrace.rb +++ b/scripts/slayrace.rb @@ -21,7 +21,7 @@ slayit = lambda { |u| else # it's getting hot around here # !!WARNING!! do not call on a magma-safe creature - ouh = df.onupdate_register(1) { + ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) { if u.flags1.dead df.onupdate_unregister(ouh) else diff --git a/scripts/superdwarf.rb b/scripts/superdwarf.rb index 6277db97f..eac9802fa 100644 --- a/scripts/superdwarf.rb +++ b/scripts/superdwarf.rb @@ -8,12 +8,12 @@ when 'add' if u = df.unit_find $superdwarf_ids |= [u.id] - if df.gamemode == :ADVENTURE + if df.gamemode == :ADVENTURE and not df.respond_to?(:cur_year_tick_advmode) onupdate_delay = nil else onupdate_delay = 1 end - $superdwarf_onupdate ||= df.onupdate_register(onupdate_delay) { + $superdwarf_onupdate ||= df.onupdate_register('superdwarf', onupdate_delay) { if $superdwarf_ids.empty? df.onupdate_unregister($superdwarf_onupdate) $superdwarf_onupdate = nil From 4dfe46e26f139167cc51ec689c19b3ba08747c0d Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 24 Nov 2012 16:52:21 +0100 Subject: [PATCH 42/55] manipulator: fix column width calculations for 80x25 window --- plugins/manipulator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index e8a91fdb6..57c9390bb 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -469,10 +469,10 @@ void viewscreen_unitlaborsst::calcSize() int col_maxwidth[DISP_COLUMN_MAX]; col_minwidth[DISP_COLUMN_HAPPINESS] = 4; col_maxwidth[DISP_COLUMN_HAPPINESS] = 4; - col_minwidth[DISP_COLUMN_NAME] = 0; - col_maxwidth[DISP_COLUMN_NAME] = 0; - col_minwidth[DISP_COLUMN_PROFESSION] = 0; - col_maxwidth[DISP_COLUMN_PROFESSION] = 0; + col_minwidth[DISP_COLUMN_NAME] = 15; + col_maxwidth[DISP_COLUMN_NAME] = 15; // adjusted in the loop below + col_minwidth[DISP_COLUMN_PROFESSION] = 15; + col_maxwidth[DISP_COLUMN_PROFESSION] = 15; // adjusted in the loop below col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60% col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS; From cdc44b74f2d8d353d0beab7e73bfa8bbf916151c Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 24 Nov 2012 10:36:32 -0600 Subject: [PATCH 43/55] Fix possible crash when using shift+enter on cells that don't have labors --- plugins/manipulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 57c9390bb..d8b44f657 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -842,7 +842,7 @@ void viewscreen_unitlaborsst::feed(set *events) { df::unit *unit = cur->unit; const SkillColumn &col = columns[input_column]; - bool newstatus = !unit->status.labors[col.labor]; + bool newstatus = (col.labor == unit_labor::NONE) ? true : !unit->status.labors[col.labor]; for (int i = 0; i < NUM_COLUMNS; i++) { if (columns[i].group != col.group) From c58f30ba0095248512c3b1320de6869620794377 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 24 Nov 2012 10:37:22 -0600 Subject: [PATCH 44/55] Use teal background instead of red for no-labor cells --- plugins/manipulator.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index d8b44f657..3e1a414ff 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -1060,7 +1060,7 @@ void viewscreen_unitlaborsst::render() } } else - bg = 4; + bg = 3; Screen::paintTile(Screen::Pen(c, fg, bg), col_offsets[DISP_COLUMN_LABORS] + col, 4 + row); } } From e9141f34f6dd9354a4eed0498aa72ff944cc1189 Mon Sep 17 00:00:00 2001 From: Quietust Date: Sat, 24 Nov 2012 11:13:54 -0600 Subject: [PATCH 45/55] Adjust minimum widths so they actually work at 80x25 without glitching out --- plugins/manipulator.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 3e1a414ff..88dc61726 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -469,10 +469,10 @@ void viewscreen_unitlaborsst::calcSize() int col_maxwidth[DISP_COLUMN_MAX]; col_minwidth[DISP_COLUMN_HAPPINESS] = 4; col_maxwidth[DISP_COLUMN_HAPPINESS] = 4; - col_minwidth[DISP_COLUMN_NAME] = 15; - col_maxwidth[DISP_COLUMN_NAME] = 15; // adjusted in the loop below - col_minwidth[DISP_COLUMN_PROFESSION] = 15; - col_maxwidth[DISP_COLUMN_PROFESSION] = 15; // adjusted in the loop below + col_minwidth[DISP_COLUMN_NAME] = 16; + col_maxwidth[DISP_COLUMN_NAME] = 16; // adjusted in the loop below + col_minwidth[DISP_COLUMN_PROFESSION] = 10; + col_maxwidth[DISP_COLUMN_PROFESSION] = 10; // adjusted in the loop below col_minwidth[DISP_COLUMN_LABORS] = num_columns*3/5; // 60% col_maxwidth[DISP_COLUMN_LABORS] = NUM_COLUMNS; From f091284a75b52b84dbe602d85345bae9bcfc2422 Mon Sep 17 00:00:00 2001 From: jj Date: Sun, 25 Nov 2012 17:29:03 +0100 Subject: [PATCH 46/55] ruby: avoid crash on ArgumentError in onupdate --- plugins/ruby/ruby.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index 4fcb5543a..b7f7590e9 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -47,7 +47,7 @@ module DFHack # t0 = Time.now @callback.call # dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1 - rescue + rescue Exception df.onupdate_unregister self puts_err "onupdate #@description unregistered: #$!", $!.backtrace end From 76bb5f0196e5f57be0e7c3bdd4947c8bd6416fc3 Mon Sep 17 00:00:00 2001 From: jj Date: Mon, 26 Nov 2012 20:09:56 +0100 Subject: [PATCH 47/55] ruby: items in containers are free --- plugins/ruby/item.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/ruby/item.rb b/plugins/ruby/item.rb index 469ec7449..0d65a707b 100644 --- a/plugins/ruby/item.rb +++ b/plugins/ruby/item.rb @@ -56,7 +56,7 @@ module DFHack def item_isfree(i) !i.flags.trader and !i.flags.in_job and - !i.flags.in_inventory and + (!i.flags.in_inventory or i.general_refs.grep(GeneralRefContainedInItemst).first) and !i.flags.removed and !i.flags.in_building and !i.flags.owned and From 536fd5546a8420340d0f6ca2fc8e5133bbda447b Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Tue, 27 Nov 2012 13:56:02 +0400 Subject: [PATCH 48/55] Update manipulator screenshots. --- Readme.html | 639 +++++++++++++++++++++------------------- Readme.rst | 8 +- images/manipulator.png | Bin 7373 -> 9024 bytes images/manipulator2.png | Bin 0 -> 8840 bytes 4 files changed, 348 insertions(+), 299 deletions(-) create mode 100644 images/manipulator2.png diff --git a/Readme.html b/Readme.html index 5b231b429..cdc4dd631 100644 --- a/Readme.html +++ b/Readme.html @@ -342,200 +342,205 @@ access DF memory and allow for easier development of new tools.

  • Using DFHack
  • -

    Something doesn't work, help!

    +

    Something doesn't work, help!

    First, don't panic :) Second, dfhack keeps a few log files in DF's folder - stderr.log and stdout.log. You can look at those and possibly find out what's happening. @@ -661,13 +672,13 @@ the issues tracker on github, contact me ( -

    The init file

    +

    The init file

    If your DF folder contains a file named dfhack.init, its contents will be run every time you start DF. This allows setting up keybindings. An example file is provided as dfhack.init-example - you can tweak it and rename to dfhack.init if you want to use this functionality.

    -

    Setting keybindings

    +

    Setting keybindings

    To set keybindings, use the built-in keybinding command. Like any other command it can be used at any time from the console, but it is also meaningful in the DFHack init file.

    @@ -712,7 +723,7 @@ for context foo/bar/baz, possible matches are
    -

    Commands

    +

    Commands

    DFHack command syntax consists of a command name, followed by arguments separated by whitespace. To include whitespace in an argument, quote it in double quotes. To include a double quote character, use \" inside double quotes.

    @@ -734,13 +745,13 @@ The following two command lines are exactly equivalent:

    to retrieve further help without having to look at this document. Alternatively, some accept a 'help'/'?' option on their command line.

    -

    Game progress

    +

    Game progress

    -

    die

    +

    die

    Instantly kills DF without saving.

    -

    forcepause

    +

    forcepause

    Forces DF to pause. This is useful when your FPS drops below 1 and you lose control of the game.

    @@ -751,12 +762,12 @@ control of the game.

    -

    nopause

    +

    nopause

    Disables pausing (both manual and automatic) with the exception of pause forced by 'reveal hell'. This is nice for digging under rivers.

    -

    fastdwarf

    +

    fastdwarf

    Controls speedydwarf and teledwarf. Speedydwarf makes dwarves move quickly and perform tasks quickly. Teledwarf makes dwarves move instantaneously, but do jobs at the same speed.

      @@ -773,29 +784,29 @@ that implements an even more aggressive version of speedydwarf.
    -

    Game interface

    +

    Game interface

    -

    follow

    +

    follow

    Makes the game view follow the currently highlighted unit after you exit from current menu/cursor mode. Handy for watching dwarves running around. Deactivated by moving the view manually.

    -

    tidlers

    +

    tidlers

    Toggle between all possible positions where the idlers count can be placed.

    -

    twaterlvl

    +

    twaterlvl

    Toggle between displaying/not displaying liquid depth as numbers.

    -

    copystock

    +

    copystock

    Copies the parameters of the currently highlighted stockpile to the custom stockpile settings and switches to custom stockpile placement mode, effectively allowing you to copy/paste stockpiles easily.

    -

    rename

    +

    rename

    Allows renaming various things.

    Options:

    @@ -829,9 +840,9 @@ siege engine or an activity zone.
    -

    Adventure mode

    +

    Adventure mode

    -

    adv-bodyswap

    +

    adv-bodyswap

    This allows taking control over your followers and other creatures in adventure mode. For example, you can make them pick up new arms and armor and equip them properly.

    @@ -844,7 +855,7 @@ properly.

    -

    advtools

    +

    advtools

    A package of different adventure mode tools (currently just one)

    Usage:

    @@ -867,9 +878,9 @@ on item type and being in shop.
    -

    Map modification

    +

    Map modification

    -

    changelayer

    +

    changelayer

    Changes material of the geology layer under cursor to the specified inorganic RAW material. Can have impact on all surrounding regions, not only your embark! By default changing stone to soil and vice versa is not allowed. By default @@ -944,7 +955,7 @@ You did save your game, right?

    -

    changevein

    +

    changevein

    Changes material of the vein under cursor to the specified inorganic RAW material. Only affects tiles within the current 16x16 block - for veins and large clusters, you will need to use this command multiple times.

    @@ -957,7 +968,7 @@ large clusters, you will need to use this command multiple times.

    -

    changeitem

    +

    changeitem

    Allows changing item material and base quality. By default the item currently selected in the UI will be changed (you can select items in the 'k' list or inside containers/inventory). By default change is only allowed if materials @@ -997,7 +1008,7 @@ crafters/haulers.

    -

    colonies

    +

    colonies

    Allows listing all the vermin colonies on the map and optionally turning them into honey bee colonies.

    Options:

    @@ -1012,12 +1023,12 @@ crafters/haulers.

    -

    deramp (by zilpin)

    +

    deramp (by zilpin)

    Removes all ramps designated for removal from the map. This is useful for replicating the old channel digging designation. It also removes any and all 'down ramps' that can remain after a cave-in (you don't have to designate anything for that to happen).

    -

    feature

    +

    feature

    Enables management of map features.

    • Discovering a magma feature (magma pool, volcano, magma sea, or curious @@ -1042,7 +1053,7 @@ that cavern to grow within your fortress.
    -

    liquids

    +

    liquids

    Allows adding magma, water and obsidian to the game. It replaces the normal dfhack command line and can't be used from a hotkey. Settings will be remembered as long as dfhack runs. Intended for use in combination with the command @@ -1055,13 +1066,13 @@ temperatures (creating heat traps). You've been warned.

    -

    liquids-here

    +

    liquids-here

    Run the liquid spawner with the current/last settings made in liquids (if no settings in liquids were made it paints a point of 7/7 magma by default).

    Intended to be used as keybinding. Requires an active in-game cursor.

    -

    tiletypes

    +

    tiletypes

    Can be used for painting map tiles and is an interactive command, much like liquids.

    The tool works with two set of options and a brush. The brush determines which @@ -1122,27 +1133,27 @@ up.

    For more details, see the 'help' command while using this.

    -

    tiletypes-commands

    +

    tiletypes-commands

    Runs tiletypes commands, separated by ;. This makes it possible to change tiletypes modes from a hotkey.

    -

    tiletypes-here

    +

    tiletypes-here

    Apply the current tiletypes options at the in-game cursor position, including the brush. Can be used from a hotkey.

    -

    tiletypes-here-point

    +

    tiletypes-here-point

    Apply the current tiletypes options at the in-game cursor position to a single tile. Can be used from a hotkey.

    -

    tubefill

    +

    tubefill

    Fills all the adamantine veins again. Veins that were empty will be filled in too, but might still trigger a demon invasion (this is a known bug).

    -

    extirpate

    +

    extirpate

    A tool for getting rid of trees and shrubs. By default, it only kills a tree/shrub under the cursor. The plants are turned into ashes instantly.

    Options:

    @@ -1162,20 +1173,20 @@ a tree/shrub under the cursor. The plants are turned into ashes instantly.

    -

    grow

    +

    grow

    Makes all saplings present on the map grow into trees (almost) instantly.

    -

    immolate

    +

    immolate

    Very similar to extirpate, but additionally sets the plants on fire. The fires can and will spread ;)

    -

    regrass

    +

    regrass

    Regrows grass. Not much to it ;)

    -

    weather

    +

    weather

    Prints the current weather map by default.

    Also lets you change the current weather to 'clear sky', 'rainy' or 'snowing'.

    Options:

    @@ -1196,9 +1207,9 @@ can and will spread ;)

    -

    Map inspection

    +

    Map inspection

    -

    cursecheck

    +

    cursecheck

    Checks a single map tile or the whole map/world for cursed creatures (ghosts, vampires, necromancers, werebeasts, zombies).

    With an active in-game cursor only the selected tile will be observed. @@ -1253,17 +1264,17 @@ of curses, for example.

    -

    flows

    +

    flows

    A tool for checking how many tiles contain flowing liquids. If you suspect that your magma sea leaks into HFS, you can use this tool to be sure without revealing the map.

    -

    probe

    +

    probe

    Can be used to determine tile properties like temperature.

    -

    prospect

    +

    prospect

    Prints a big list of all the present minerals and plants. By default, only the visible part of the map is scanned.

    Options:

    @@ -1282,7 +1293,7 @@ the visible part of the map is scanned.

    -

    Pre-embark estimate

    +

    Pre-embark estimate

    If prospect is called during the embark selection screen, it displays an estimate of layer stone availability.

    @@ -1307,7 +1318,7 @@ that is actually present.

    -

    reveal

    +

    reveal

    This reveals the map. By default, HFS will remain hidden so that the demons don't spawn. You can use 'reveal hell' to reveal everything. With hell revealed, you won't be able to unpause until you hide the map again. If you really want @@ -1316,34 +1327,34 @@ to unpause with hell revealed, use 'reveal demons'.

    you move. When you use it this way, you don't need to run 'unreveal'.

    -

    unreveal

    +

    unreveal

    Reverts the effects of 'reveal'.

    -

    revtoggle

    +

    revtoggle

    Switches between 'reveal' and 'unreveal'.

    -

    revflood

    +

    revflood

    This command will hide the whole map and then reveal all the tiles that have a path to the in-game cursor.

    -

    revforget

    +

    revforget

    When you use reveal, it saves information about what was/wasn't visible before revealing everything. Unreveal uses this information to hide things again. This command throws away the information. For example, use in cases where you abandoned with the fort revealed and no longer want the data.

    -

    showmood

    +

    showmood

    Shows all items needed for the currently active strange mood.

    -

    Designations

    +

    Designations

    -

    burrow

    +

    burrow

    Miscellaneous burrow control. Allows manipulating burrows and automated burrow expansion while digging.

    Options:

    @@ -1391,17 +1402,17 @@ Digging 1-wide corridors with the miner inside the burrow is SLOW.
    -

    digv

    +

    digv

    Designates a whole vein for digging. Requires an active in-game cursor placed over a vein tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles).

    -

    digvx

    +

    digvx

    A permanent alias for 'digv x'.

    -

    digl

    +

    digl

    Designates layer stone for digging. Requires an active in-game cursor placed over a layer stone tile. With the 'x' option, it will traverse z-levels (putting stairs between the same-material tiles). With the 'undo' option it @@ -1409,11 +1420,11 @@ will remove the dig designation instead (if you realize that digging out a 50 z-level deep layer was not such a good idea after all).

    -

    diglx

    +

    diglx

    A permanent alias for 'digl x'.

    -

    digexp

    +

    digexp

    This command can be used for exploratory mining.

    See: http://df.magmawiki.com/index.php/DF2010:Exploratory_mining

    There are two variables that can be set: pattern and filter.

    @@ -1476,7 +1487,7 @@ z-level deep layer was not such a good idea after all).

    -

    digcircle

    +

    digcircle

    A command for easy designation of filled and hollow circles. It has several types of options.

    Shape:

    @@ -1539,7 +1550,7 @@ repeats with the last selected parameters.

    -

    digtype

    +

    digtype

    For every tile on the map of the same vein type as the selected tile, this command designates it to have the same designation as the selected tile. If the selected tile has no designation, they will be dig designated. If an argument is given, the designation of the selected tile is ignored, and all appropriate tiles are set to the specified designation.

    Options:

    @@ -1567,7 +1578,7 @@ If an argument is given, the designation of the selected tile is ignored, and al
    -

    filltraffic

    +

    filltraffic

    Set traffic designations using flood-fill starting at the cursor.

    Traffic Type Codes:

    @@ -1606,7 +1617,7 @@ If an argument is given, the designation of the selected tile is ignored, and al 'filltraffic H' - When used in a room with doors, it will set traffic to HIGH in just that room.
    -

    alltraffic

    +

    alltraffic

    Set traffic designations for every single tile of the map (useful for resetting traffic designations).

    Traffic Type Codes:

    @@ -1630,7 +1641,7 @@ If an argument is given, the designation of the selected tile is ignored, and al 'alltraffic N' - Set traffic to 'normal' for all tiles.
    -

    getplants

    +

    getplants

    This tool allows plant gathering and tree cutting by RAW ID. Specify the types of trees to cut down and/or shrubs to gather by their plant names, separated by spaces.

    @@ -1657,9 +1668,9 @@ all valid plant IDs will be listed.

    -

    Cleanup and garbage disposal

    +

    Cleanup and garbage disposal

    -

    clean

    +

    clean

    Cleans all the splatter that get scattered all over the map, items and creatures. In an old fortress, this can significantly reduce FPS lag. It can also spoil your !!FUN!!, so think before you use it.

    @@ -1693,12 +1704,12 @@ also spoil your !!FUN!!, so think before you use it.

    -

    spotclean

    +

    spotclean

    Works like 'clean map snow mud', but only for the tile under the cursor. Ideal if you want to keep that bloody entrance 'clean map' would clean up.

    -

    autodump

    +

    autodump

    This utility lets you quickly move all items designated to be dumped. Items are instantly moved to the cursor position, the dump flag is unset, and the forbid flag is set, as if it had been dumped normally. @@ -1725,17 +1736,17 @@ Be aware that any active dump item tasks still point at the item.

    -

    autodump-destroy-here

    +

    autodump-destroy-here

    Destroy items marked for dumping under cursor. Identical to autodump destroy-here, but intended for use as keybinding.

    -

    autodump-destroy-item

    +

    autodump-destroy-item

    Destroy the selected item. The item may be selected in the 'k' list, or inside a container. If called again before the game is resumed, cancels destroy.

    -

    cleanowned

    +

    cleanowned

    Confiscates items owned by dwarfs. By default, owned food on the floor and rotten items are confistacted and dumped.

    Options:

    @@ -1769,13 +1780,13 @@ worn items with 'X' damage and above.
    -

    Bugfixes

    +

    Bugfixes

    -

    drybuckets

    +

    drybuckets

    This utility removes water from all buckets in your fortress, allowing them to be safely used for making lye.

    -

    fixdiplomats

    +

    fixdiplomats

    Up to version 0.31.12, Elves only sent Diplomats to your fortress to propose tree cutting quotas due to a bug; once that bug was fixed, Elves stopped caring about excess tree cutting. This command adds a Diplomat position to all Elven @@ -1784,19 +1795,19 @@ to violate them and potentially start wars) in case you haven't already modified your raws accordingly.

    -

    fixmerchants

    +

    fixmerchants

    This command adds the Guild Representative position to all Human civilizations, allowing them to make trade agreements (just as they did back in 0.28.181.40d and earlier) in case you haven't already modified your raws accordingly.

    -

    fixveins

    +

    fixveins

    Removes invalid references to mineral inclusions and restores missing ones. Use this if you broke your embark with tools like tiletypes, or if you accidentally placed a construction on top of a valuable mineral floor.

    -

    tweak

    +

    tweak

    Contains various tweaks for minor bugs.

    One-shot subcommands:

    @@ -1902,7 +1913,7 @@ the units spar more.

    -

    fix-armory

    +

    fix-armory

    Enables a fix for storage of squad equipment in barracks.

    Specifically, it prevents your haulers from moving squad equipment to stockpiles, and instead queues jobs to store it on weapon racks, @@ -1956,9 +1967,9 @@ these rules is intended by Toady; the rest are invented by this plugin.

    -

    Mode switch and reclaim

    +

    Mode switch and reclaim

    -

    lair

    +

    lair

    This command allows you to mark the map as 'monster lair', preventing item scatter on abandon. When invoked as 'lair reset', it does the opposite.

    Unlike reveal, this command doesn't save the information about tiles - you @@ -1978,7 +1989,7 @@ won't be able to restore state of real monster lairs using 'lair reset'.

    -

    mode

    +

    mode

    This command lets you see and change the game mode directly. Not all combinations are good for every situation and most of them will produce undesirable results. There are a few good ones though.

    @@ -1998,9 +2009,9 @@ You just created a returnable mountain home and gained an adventurer.

    -

    Visualizer and data export

    +

    Visualizer and data export

    -

    ssense / stonesense

    +

    ssense / stonesense

    An isometric visualizer that runs in a second window. This requires working graphics acceleration and at least a dual core CPU (otherwise it will slow down DF).

    @@ -2013,19 +2024,19 @@ thread: http://df.magmawiki.com/index.php/Utility:Stonesense/Content_repository

    -

    mapexport

    +

    mapexport

    Export the current loaded map as a file. This will be eventually usable with visualizers.

    -

    dwarfexport

    +

    dwarfexport

    Export dwarves to RuneSmith-compatible XML.

    -

    Job management

    +

    Job management

    -

    job

    +

    job

    Command for general job query and manipulation.

    Options:
    @@ -2044,7 +2055,7 @@ in a workshop, or the unit/jobs screen.
    -

    job-material

    +

    job-material

    Alter the material of the selected job.

    Invoked as:

    @@ -2062,7 +2073,7 @@ over the first available choice with the matching material.
     
     
    -

    job-duplicate

    +

    job-duplicate

    Duplicate the selected job in a workshop:
      @@ -2073,7 +2084,7 @@ instantly duplicates the job.
    -

    workflow

    +

    workflow

    Manage control of repeat jobs.

    Usage:

    @@ -2105,7 +2116,7 @@ this list can be copied to a file, and then reloaded using the
    -

    Function

    +

    Function

    When the plugin is enabled, it protects all repeat jobs from removal. If they do disappear due to any cause, they are immediately re-added to their workshop and suspended.

    @@ -2118,7 +2129,7 @@ the frequency of jobs being toggled.

    in the game UI.

    -

    Constraint format

    +

    Constraint format

    The contstraint spec consists of 4 parts, separated with '/' characters:

     ITEM[:SUBTYPE]/[GENERIC_MAT,...]/[SPECIFIC_MAT:...]/[LOCAL,<quality>]
    @@ -2147,7 +2158,7 @@ be used to ignore imported items or items below a certain quality.

    -

    Constraint examples

    +

    Constraint examples

    Keep metal bolts within 900-1000, and wood/bone within 150-200.

     workflow amount AMMO:ITEM_AMMO_BOLTS/METAL 1000 100
    @@ -2196,15 +2207,15 @@ workflow count CRAFTS///LOCAL,EXCEPTIONAL 100 90
     
    -

    Fortress activity management

    +

    Fortress activity management

    -

    seedwatch

    +

    seedwatch

    Tool for turning cooking of seeds and plants on/off depending on how much you have of them.

    See 'seedwatch help' for detailed description.

    -

    zone

    +

    zone

    Helps a bit with managing activity zones (pens, pastures and pits) and cages.

    Options:

    @@ -2303,7 +2314,7 @@ for war/hunt). Negatable.
    -

    Usage with single units

    +

    Usage with single units

    One convenient way to use the zone tool is to bind the command 'zone assign' to a hotkey, maybe also the command 'zone set'. Place the in-game cursor over a pen/pasture or pit, use 'zone set' to mark it. Then you can select units @@ -2312,7 +2323,7 @@ and use 'zone assign' to assign them to their new home. Allows pitting your own dwarves, by the way.

    -

    Usage with filters

    +

    Usage with filters

    All filters can be used together with the 'assign' command.

    Restrictions: It's not possible to assign units who are inside built cages or chained because in most cases that won't be desirable anyways. @@ -2330,14 +2341,14 @@ are not properly added to your own stocks; slaughtering them should work).

    Most filters can be negated (e.g. 'not grazer' -> race is not a grazer).

    -

    Mass-renaming

    +

    Mass-renaming

    Using the 'nick' command you can set the same nickname for multiple units. If used without 'assign', 'all' or 'count' it will rename all units in the current default target zone. Combined with 'assign', 'all' or 'count' (and further optional filters) it will rename units matching the filter conditions.

    -

    Cage zones

    +

    Cage zones

    Using the 'tocages' command you can assign units to a set of cages, for example a room next to your butcher shop(s). They will be spread evenly among available cages to optimize hauling to and butchering from them. For this to work you need @@ -2348,7 +2359,7 @@ would make no sense, but can be used together with 'nick' or 'remnick' and all the usual filters.

    -

    Examples

    +

    Examples

    zone assign all own ALPACA minage 3 maxage 10
    Assign all own alpacas who are between 3 and 10 years old to the selected @@ -2374,7 +2385,7 @@ on the current default zone.
    -

    autonestbox

    +

    autonestbox

    Assigns unpastured female egg-layers to nestbox zones. Requires that you create pen/pasture zones above nestboxes. If the pen is bigger than 1x1 the nestbox must be in the top left corner. Only 1 unit will be assigned per pen, regardless @@ -2403,7 +2414,7 @@ frames between runs.

    -

    autobutcher

    +

    autobutcher

    Assigns lifestock for slaughter once it reaches a specific count. Requires that you add the target race(s) to a watch list. Only tame units will be processed.

    Named units will be completely ignored (to protect specific animals from @@ -2511,7 +2522,7 @@ autobutcher.bat

    -

    autolabor

    +

    autolabor

    Automatically manage dwarf labors.

    When enabled, autolabor periodically checks your dwarves and enables or disables labors. It tries to keep as many dwarves as possible busy but @@ -2525,14 +2536,14 @@ while it is enabled.

    -

    Other

    +

    Other

    -

    catsplosion

    +

    catsplosion

    Makes cats just multiply. It is not a good idea to run this more than once or twice.

    -

    dfusion

    +

    dfusion

    This is the DFusion lua plugin system by Warmist, running as a DFHack plugin. There are two parts to this plugin: an interactive script that shows a text based menu and lua modules. Some of the functionality of is intentionaly left out of the menu:
    @@ -2557,7 +2568,7 @@ twice.

    -

    misery

    +

    misery

    When enabled, every new negative dwarven thought will be multiplied by a factor (2 by default).

    Usage:

    @@ -2581,7 +2592,7 @@ twice.

    -

    Scripts

    +

    Scripts

    Lua or ruby scripts placed in the hack/scripts/ directory are considered for execution as if they were native DFHack commands. They are listed at the end of the 'ls' command output.

    @@ -2590,7 +2601,7 @@ only be listed by ls if called as 'ls -a'. This is intended as a way to hide scripts that are obscure, developer-oriented, or should be used as keybindings.

    Some notable scripts:

    -

    fix/*

    +

    fix/*

    Scripts in this subdirectory fix various bugs and issues, some of them obscure.

    • fix/dead-units

      @@ -2616,12 +2627,12 @@ caused by autodump bugs or other hacking mishaps.

    -

    gui/*

    +

    gui/*

    Scripts that implement dialogs inserted into the main game window are put in this directory.

    -

    binpatch

    +

    binpatch

    Checks, applies or removes binary patches directly in memory at runtime:

     binpatch check/apply/remove <patchname>
    @@ -2631,17 +2642,17 @@ script uses hack/patches/<df-v
     the version appropriate for the currently loaded executable.

    -

    quicksave

    +

    quicksave

    If called in dwarf mode, makes DF immediately auto-save the game by setting a flag normally used in seasonal auto-save.

    -

    setfps

    +

    setfps

    Run setfps <number> to set the FPS cap at runtime, in case you want to watch combat in slow motion or something :)

    -

    siren

    +

    siren

    Wakes up sleeping units, cancels breaks and stops parties either everywhere, or in the burrows given as arguments. In return, adds bad thoughts about noise, tiredness and lack of protection. Also, the units with interrupted @@ -2649,7 +2660,7 @@ breaks will go on break again a lot sooner. The script is intended for emergencies, e.g. when a siege appears, and all your military is partying.

    -

    growcrops

    +

    growcrops

    Instantly grow seeds inside farming plots.

    With no argument, this command list the various seed types currently in use in your farming plots. @@ -2661,7 +2672,7 @@ growcrops plump 40

    -

    removebadthoughts

    +

    removebadthoughts

    This script remove negative thoughts from your dwarves. Very useful against tantrum spirals.

    The script can target a single creature, when used with the him argument, @@ -2675,7 +2686,7 @@ but in the short term your dwarves will get much more joyful.

    quickly after you unpause.

    -

    slayrace

    +

    slayrace

    Kills any unit of a given race.

    With no argument, lists the available races.

    With the special argument him, targets only the selected creature.

    @@ -2701,7 +2712,7 @@ slayrace elve magma
    -

    magmasource

    +

    magmasource

    Create an infinite magma source on a tile.

    This script registers a map tile as a magma source, and every 12 game ticks that tile receives 1 new unit of flowing magma.

    @@ -2716,7 +2727,7 @@ To remove all placed sources, call magmasource stop

    With no argument, this command shows an help message and list existing sources.

    -

    digfort

    +

    digfort

    A script to designate an area for digging according to a plan in csv format.

    This script, inspired from quickfort, can designate an area for digging. Your plan should be stored in a .csv file like this:

    @@ -2734,7 +2745,7 @@ To skip a row in your design, use a single ;.<

    The script takes the plan filename, starting from the root df folder.

    -

    superdwarf

    +

    superdwarf

    Similar to fastdwarf, per-creature.

    To make any creature superfast, target it ingame using 'v' and:

    @@ -2744,16 +2755,16 @@ superdwarf add
     

    This plugin also shortens the 'sleeping' and 'on break' periods of targets.

    -

    drainaquifer

    +

    drainaquifer

    Remove all 'aquifer' tag from the map blocks. Irreversible.

    -

    deathcause

    +

    deathcause

    Focus a body part ingame, and this script will display the cause of death of the creature.

    -

    lua

    +

    lua

    There are the following ways to invoke this command:

    1. lua (without any parameters)

      @@ -2772,12 +2783,44 @@ directory. If the filename is not supplied, it loads "dfhack.lua".

    -

    embark

    +

    embark

    Allows to embark anywhere. Currently windows only.

    +
    +

    lever

    +

    Allow manipulation of in-game levers from the dfhack console.

    +

    Can list levers, including state and links, with:

    +
    +lever list
    +
    +

    To queue a job so that a dwarf will pull the lever 42, use lever pull 42. +This is the same as 'q'uerying the building and queue a 'P'ull request.

    +

    To magically toggle the lever immediately, use:

    +
    +lever pull 42 --now
    +
    +
    +
    +

    stripcaged

    +

    For dumping items inside cages. Will mark selected items for dumping, then +a dwarf may come and actually dump it. See also autodump.

    +

    With the items argument, only dumps items laying in the cage, excluding +stuff worn by caged creatures. weapons will dump worn weapons, armor +will dump everything worn by caged creatures (including armor and clothing), +and all will dump everything, on a creature or not.

    +

    stripcaged list will display on the dfhack console the list of all cages +and their item content.

    +

    Without further arguments, all commands work on all cages and animal traps on +the map. With the here argument, considers only the in-game selected cage +(or the cage under the game cursor). To target only specific cages, you can +alternatively pass cage IDs as arguments:

    +
    +stripcaged weapons 25321 34228
    +
    +
    -

    In-game interface tools

    +

    In-game interface tools

    These tools work by displaying dialogs or overlays in the game window, and are mostly implemented by lua scripts.

    @@ -2790,7 +2833,7 @@ existing DF screens, they deliberately use red instead of green for the key.

    guideline because it arguably just fixes small usability bugs in the game UI.

    -

    Dwarf Manipulator

    +

    Dwarf Manipulator

    Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'.

    images/manipulator.png @@ -2798,8 +2841,10 @@ press 'l'.

    far left column displays the unit's Happiness (color-coded based on its value), and the right half of the screen displays each dwarf's labor settings and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand -Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds -denote skills not controlled by labors.

    +Master, and U-Z for Legendary thru Legendary+5).

    +

    Cells with teal backgrounds denote skills not controlled by labors, e.g. +military and social skills.

    +images/manipulator2.png

    Use the arrow keys or number pad to move the cursor around, holding Shift to move 10 tiles at a time.

    Press the Z-Up (<) and Z-Down (>) keys to move quickly between labor/skill @@ -2827,7 +2872,7 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

    -

    AutoMaterial

    +

    AutoMaterial

    The automaterial plugin makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time you place one.

    @@ -2874,14 +2919,14 @@ materials, it returns you back to this screen. If you use this along with severa enabled materials, you should be able to place complex constructions more conveniently.

    -

    gui/liquids

    +

    gui/liquids

    To use, bind to a key (the example config uses Alt-L) and activate in the 'k' mode.

    images/liquids.png

    While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes.

    -

    gui/mechanisms

    +

    gui/mechanisms

    To use, bind to a key (the example config uses Ctrl-M) and activate in the 'q' mode.

    images/mechanisms.png

    Lists mechanisms connected to the building, and their links. Navigating the list centers @@ -2891,7 +2936,7 @@ focus on the current one. Shift-Enter has an effect equivalent to pressing Enter re-entering the mechanisms ui.

    -

    gui/rename

    +

    gui/rename

    Backed by the rename plugin, this script allows entering the desired name via a simple dialog in the game ui.

      @@ -2914,7 +2959,7 @@ their species string.

      unit profession change to Ctrl-Shift-T.

    -

    gui/room-list

    +

    gui/room-list

    To use, bind to a key (the example config uses Alt-R) and activate in the 'q' mode, either immediately or after opening the assign owner page.

    images/room-list.png @@ -2922,7 +2967,7 @@ either immediately or after opening the assign owner page.

    list, and allows unassigning them.

    -

    gui/choose-weapons

    +

    gui/choose-weapons

    Bind to a key (the example config uses Ctrl-W), and activate in the Equip->View/Customize page of the military screen.

    Depending on the cursor location, it rewrites all 'individual choice weapon' entries @@ -2933,7 +2978,7 @@ only that entry, and does it even if it is not 'individual choice'.

    and may lead to inappropriate weapons being selected.

    -

    gui/guide-path

    +

    gui/guide-path

    Bind to a key (the example config uses Alt-P), and activate in the Hauling menu with the cursor over a Guide order.

    images/guide-path.png @@ -2941,7 +2986,7 @@ the cursor over a Guide order.

    computes it when the order is executed for the first time.

    -

    gui/workshop-job

    +

    gui/workshop-job

    Bind to a key (the example config uses Alt-A), and activate with a job selected in a workshop in the 'q' mode.

    images/workshop-job.png @@ -2977,7 +3022,7 @@ and then try to change the input item type, now it won't let you select plan you have to unset the material first.

    -

    gui/workflow

    +

    gui/workflow

    Bind to a key (the example config uses Alt-W), and activate with a job selected in a workshop in the 'q' mode.

    images/workflow.png @@ -3007,7 +3052,7 @@ suit your need, and set the item count range.

    If you don't need advanced settings, you can just press 'y' to confirm creation.

    -

    gui/assign-rack

    +

    gui/assign-rack

    Bind to a key (the example config uses P), and activate when viewing a weapon rack in the 'q' mode.

    images/assign-rack.png @@ -3032,7 +3077,7 @@ of currently assigned racks for every valid squad.

    -

    Behavior Mods

    +

    Behavior Mods

    These plugins, when activated via configuration UI or by detecting certain structures in RAWs, modify the game engine behavior concerning the target objects to add features not otherwise present.

    @@ -3043,20 +3088,20 @@ technical challenge, and do not represent any long-term plans to produce more similar modifications of the game.

    -

    Siege Engine

    +

    Siege Engine

    The siege-engine plugin enables siege engines to be linked to stockpiles, and aimed at an arbitrary rectangular area across Z levels, instead of the original four directions. Also, catapults can be ordered to load arbitrary objects, not just stones.

    -

    Rationale

    +

    Rationale

    Siege engines are a very interesting feature, but sadly almost useless in the current state because they haven't been updated since 2D and can only aim in four directions. This is an attempt to bring them more up to date until Toady has time to work on it. Actual improvements, e.g. like making siegers bring their own, are something only Toady can do.

    -

    Configuration UI

    +

    Configuration UI

    The configuration front-end to the plugin is implemented by the gui/siege-engine script. Bind it to a key (the example config uses Alt-A) and activate after selecting a siege engine in 'q' mode.

    @@ -3079,7 +3124,7 @@ menu.

    -

    Power Meter

    +

    Power Meter

    The power-meter plugin implements a modified pressure plate that detects power being supplied to gear boxes built in the four adjacent N/S/W/E tiles.

    The configuration front-end is implemented by the gui/power-meter script. Bind it to a @@ -3090,11 +3135,11 @@ in the build menu.

    configuration page, but configures parameters relevant to the modded power meter building.

    -

    Steam Engine

    +

    Steam Engine

    The steam-engine plugin detects custom workshops with STEAM_ENGINE in their token, and turns them into real steam engines.

    -

    Rationale

    +

    Rationale

    The vanilla game contains only water wheels and windmills as sources of power, but windmills give relatively little power, and water wheels require flowing water, which must either be a real river and thus immovable and @@ -3105,7 +3150,7 @@ it can be done just by combining existing features of the game engine in a new way with some glue code and a bit of custom logic.

    -

    Construction

    +

    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.

    @@ -3129,7 +3174,7 @@ short axles that can be built later than both of the engines.

    -

    Operation

    +

    Operation

    In order to operate the engine, queue the Stoke Boiler job (optionally on repeat). A furnace operator will come, possibly bringing a bar of fuel, and perform it. As a result, a "boiling water" item will appear @@ -3160,7 +3205,7 @@ decrease it by further 4%, and also decrease the whole steam use rate by 10%.

    -

    Explosions

    +

    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 @@ -3169,7 +3214,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

    -

    Save files

    +

    Save files

    It should be safe to load and view engine-using fortresses from a DF version without DFHack installed, except that in such case the engines won't work. However actually making modifications @@ -3180,7 +3225,7 @@ being generated.

    -

    Add Spatter

    +

    Add Spatter

    This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index 3434a240d..b9844debd 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2032,8 +2032,12 @@ This tool implements a Dwarf Therapist-like interface within the game UI. The far left column displays the unit's Happiness (color-coded based on its value), and the right half of the screen displays each dwarf's labor settings and skill levels (0-9 for Dabbling thru Professional, A-E for Great thru Grand -Master, and U-Z for Legendary thru Legendary+5). Cells with red backgrounds -denote skills not controlled by labors. +Master, and U-Z for Legendary thru Legendary+5). + +Cells with teal backgrounds denote skills not controlled by labors, e.g. +military and social skills. + +.. image:: images/manipulator2.png Use the arrow keys or number pad to move the cursor around, holding Shift to move 10 tiles at a time. diff --git a/images/manipulator.png b/images/manipulator.png index 0a546034557fceb446aa0ccb8ddb51883fd71a84..44b603600b36d2954163982c2ad4aa37a36b12e6 100644 GIT binary patch literal 9024 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 diff --git a/images/manipulator2.png b/images/manipulator2.png new file mode 100644 index 0000000000000000000000000000000000000000..250b2f2ab7492a51efaefcac761811b3acef4089 GIT binary patch literal 8840 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 Date: Tue, 27 Nov 2012 11:55:53 -0600 Subject: [PATCH 49/55] Pack manipulator screenshots --- images/manipulator.png | Bin 9024 -> 7724 bytes images/manipulator2.png | Bin 8840 -> 7558 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/images/manipulator.png b/images/manipulator.png index 44b603600b36d2954163982c2ad4aa37a36b12e6..1f452549f9e0abc9998f298eb19a9a3650ee1ed5 100644 GIT binary patch literal 7724 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 POaWs<3w)LSmFWKgF`t0@ literal 8840 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 Date: Wed, 28 Nov 2012 13:33:07 +0100 Subject: [PATCH 50/55] ruby: add DFHack::VERSION --- plugins/ruby/ruby.cpp | 7 +++++++ plugins/ruby/ruby.rb | 2 ++ 2 files changed, 9 insertions(+) diff --git a/plugins/ruby/ruby.cpp b/plugins/ruby/ruby.cpp index 13190f70d..db94ad650 100644 --- a/plugins/ruby/ruby.cpp +++ b/plugins/ruby/ruby.cpp @@ -438,6 +438,12 @@ static VALUE rb_cDFHack; // DFHack module ruby methods, binds specific dfhack methods +// df-dfhack version (eg "0.34.11-r2") +static VALUE rb_dfversion(VALUE self) +{ + return rb_str_new(DFHACK_VERSION, strlen(DFHACK_VERSION)); +} + // enable/disable calls to DFHack.onupdate() static VALUE rb_dfonupdate_active(VALUE self) { @@ -955,6 +961,7 @@ static void ruby_bind_dfhack(void) { rb_define_singleton_method(rb_cDFHack, "malloc", RUBY_METHOD_FUNC(rb_dfmalloc), 1); rb_define_singleton_method(rb_cDFHack, "free", RUBY_METHOD_FUNC(rb_dffree), 1); rb_define_singleton_method(rb_cDFHack, "vmethod_do_call", RUBY_METHOD_FUNC(rb_dfvcall), 8); + rb_define_singleton_method(rb_cDFHack, "version", RUBY_METHOD_FUNC(rb_dfversion), 0); rb_define_singleton_method(rb_cDFHack, "memory_read", RUBY_METHOD_FUNC(rb_dfmemory_read), 2); rb_define_singleton_method(rb_cDFHack, "memory_read_int8", RUBY_METHOD_FUNC(rb_dfmemory_read_int8), 1); diff --git a/plugins/ruby/ruby.rb b/plugins/ruby/ruby.rb index b7f7590e9..27cde675a 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -23,6 +23,8 @@ module Kernel end module DFHack + VERSION = version + class OnupdateCallback attr_accessor :callback, :timelimit, :minyear, :minyeartick, :description def initialize(descr, cb, tl, initdelay=0) From 593dc4f55486b02a258953558db2054cd3607123 Mon Sep 17 00:00:00 2001 From: Anuradha Dissanayake Date: Sun, 25 Nov 2012 19:01:58 +1300 Subject: [PATCH 51/55] Fix handling of manipulator hotkey in unit search screen --- plugins/search.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/plugins/search.cpp b/plugins/search.cpp index 742fa9277..a14397fba 100644 --- a/plugins/search.cpp +++ b/plugins/search.cpp @@ -521,7 +521,8 @@ private: virtual bool should_check_input(set *input) { - if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || input->count(interface_key::CUSTOM_L)) + if (input->count(interface_key::CURSOR_LEFT) || input->count(interface_key::CURSOR_RIGHT) || + (!is_entry_mode() && input->count(interface_key::UNITVIEW_PRF_PROF))) { if (!is_entry_mode()) { From bfc11cf94636856a55d0a26ddcd139d41c3d11e1 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 28 Nov 2012 19:25:01 +0400 Subject: [PATCH 52/55] Add persistent history of per-constraint item counts in workflow. This will be needed for properly merging or integrating the status screen by falconne. The history is maintained as a circular buffer of up to 28 entries, and persists in save files. --- library/include/modules/World.h | 49 ++++++++++++++++ plugins/workflow.cpp | 101 +++++++++++++++++++++++++++++--- 2 files changed, 143 insertions(+), 7 deletions(-) diff --git a/library/include/modules/World.h b/library/include/modules/World.h index a945c4e72..f1fea52a1 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -81,6 +81,55 @@ namespace DFHack int &ival(int i) { return int_values[i]; } int ival(int i) const { return int_values[i]; } + // Pack binary data into string field. + // Since DF serialization chokes on NUL bytes, + // use bit magic to ensure none of the bytes is 0. + // Choose the lowest bit for padding so that + // sign-extend can be used normally. + + size_t data_size() const { return str_value->size(); } + + bool check_data(size_t off, size_t sz = 1) { + return (str_value->size() >= off+sz); + } + void ensure_data(size_t off, size_t sz = 0) { + if (str_value->size() < off+sz) str_value->resize(off+sz, '\x01'); + } + uint8_t *pdata(size_t off) { return (uint8_t*)&(*str_value)[off]; } + + static const size_t int7_size = 1; + uint8_t get_uint7(size_t off) { + uint8_t *p = pdata(off); + return p[0]>>1; + } + int8_t get_int7(size_t off) { + uint8_t *p = pdata(off); + return int8_t(p[0])>>1; + } + void set_uint7(size_t off, uint8_t val) { + uint8_t *p = pdata(off); + p[0] = uint8_t((val<<1) | 1); + } + void set_int7(size_t off, int8_t val) { set_uint7(off, val); } + + static const size_t int28_size = 4; + uint32_t get_uint28(size_t off) { + uint8_t *p = pdata(off); + return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((p[3]&~1U)<<20); + } + int32_t get_int28(size_t off) { + uint8_t *p = pdata(off); + return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((int8_t(p[3])&~1)<<20); + } + void set_uint28(size_t off, uint32_t val) { + uint8_t *p = pdata(off); + p[0] = uint8_t((val<<1) | 1); + p[1] = uint8_t((val>>6) | 1); + p[2] = uint8_t((val>>13) | 1); + p[3] = uint8_t((val>>20) | 1); + } + void set_int28(size_t off, int32_t val) { set_uint28(off, val); } + PersistentDataItem() : id(0), str_value(0), int_values(0) {} PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv) : id(id), key_value(key), str_value(sv), int_values(iv) {} diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 6e15a4537..813326175 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -293,6 +293,7 @@ typedef std::map, bool> TMaterialCache; struct ItemConstraint { PersistentDataItem config; + PersistentDataItem history; // Fixed key parsed into fields bool is_craft; @@ -308,7 +309,7 @@ struct ItemConstraint { int weight; std::vector jobs; - int item_amount, item_count, item_inuse; + int item_amount, item_count, item_inuse_amount, item_inuse_count; bool request_suspend, request_resume; bool is_active, cant_resume_reported; @@ -318,7 +319,7 @@ struct ItemConstraint { public: ItemConstraint() : is_craft(false), min_quality(item_quality::Ordinary), is_local(false), - weight(0), item_amount(0), item_count(0), item_inuse(0), + weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0), is_active(false), cant_resume_reported(false) {} @@ -352,6 +353,44 @@ public: request_resume = (size <= goalCount()-goalGap()); request_suspend = (size >= goalCount()); } + + static const size_t int28_size = PersistentDataItem::int28_size; + static const size_t hist_entry_size = PersistentDataItem::int28_size * 4; + + size_t history_size() { + return history.data_size() / hist_entry_size; + } + size_t history_base(int idx) { + size_t hsize = history_size(); + return ((history.ival(0)+hsize-idx) % hsize) * hist_entry_size; + } + int history_count(int idx) { + return history.get_int28(history_base(idx) + 0*int28_size); + } + int history_amount(int idx) { + return history.get_int28(history_base(idx) + 1*int28_size); + } + int history_inuse_count(int idx) { + return history.get_int28(history_base(idx) + 2*int28_size); + } + int history_inuse_amount(int idx) { + return history.get_int28(history_base(idx) + 3*int28_size); + } + + void updateHistory() + { + size_t buffer_size = history_size(); + if (buffer_size < 28) + history.ensure_data(hist_entry_size*buffer_size++, hist_entry_size); + history.ival(0) = (history.ival(0)+1) % buffer_size; + + size_t base = history.ival(0) * hist_entry_size; + + history.set_int28(base + 0*int28_size, item_count); + history.set_int28(base + 1*int28_size, item_amount); + history.set_int28(base + 2*int28_size, item_inuse_count); + history.set_int28(base + 3*int28_size, item_inuse_amount); + } }; /****************************** @@ -649,6 +688,9 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) update_job_data(out); process_constraints(out); + + for (size_t i = 0; i < constraints.size(); i++) + constraints[i]->updateHistory(); } } @@ -659,6 +701,10 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) * ITEM COUNT CONSTRAINT * ******************************/ +static std::string history_key(PersistentDataItem &config) { + return stl_sprintf("workflow/history/%d", config.entry_id()); +} + static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg, bool create) { std::vector tokens; @@ -776,6 +822,8 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str nct->init(str); } + nct->history = World::GetPersistentData(history_key(nct->config), NULL); + constraints.push_back(nct); return nct; } @@ -787,6 +835,7 @@ static void delete_constraint(ItemConstraint *cv) vector_erase_at(constraints, idx); World::DeletePersistentData(cv->config); + World::DeletePersistentData(cv->history); delete cv; } @@ -1064,7 +1113,8 @@ static void map_job_items(color_ostream &out) { constraints[i]->item_amount = 0; constraints[i]->item_count = 0; - constraints[i]->item_inuse = 0; + constraints[i]->item_inuse_amount = 0; + constraints[i]->item_inuse_count = 0; } meltable_count = 0; @@ -1177,7 +1227,8 @@ static void map_job_items(color_ostream &out) isAssignedSquad(item)) { is_invalid = true; - cv->item_inuse++; + cv->item_inuse_count++; + cv->item_inuse_amount += item->getStackSize(); } else { @@ -1367,7 +1418,8 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) Lua::SetField(L, cv->item_amount, ctable, "cur_amount"); Lua::SetField(L, cv->item_count, ctable, "cur_count"); - Lua::SetField(L, cv->item_inuse, ctable, "cur_in_use"); + Lua::SetField(L, cv->item_inuse_amount, ctable, "cur_in_use_amount"); + Lua::SetField(L, cv->item_inuse_count, ctable, "cur_in_use_count"); // Current state value @@ -1463,6 +1515,40 @@ static int setConstraint(lua_State *L) return 1; } +static int getCountHistory(lua_State *L) +{ + auto token = luaL_checkstring(L, 1); + + color_ostream &out = *Lua::GetOutput(L); + update_data_structures(out); + + ItemConstraint *icv = get_constraint(out, token, NULL, false); + + if (icv) + { + size_t hsize = icv->history_size(); + + lua_createtable(L, hsize, 0); + + for (int i = hsize-1; i >= 0; i--) + { + lua_createtable(L, 0, 4); + + Lua::SetField(L, icv->history_amount(i), -1, "cur_amount"); + Lua::SetField(L, icv->history_count(i), -1, "cur_count"); + Lua::SetField(L, icv->history_inuse_amount(i), -1, "cur_in_use_amount"); + Lua::SetField(L, icv->history_inuse_count(i), -1, "cur_in_use_count"); + + lua_rawseti(L, -2, hsize-i); // reverse order + } + } + else + lua_pushnil(L); + + return 1; +} + + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(isEnabled), DFHACK_LUA_FUNCTION(setEnabled), @@ -1474,6 +1560,7 @@ DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(listConstraints), DFHACK_LUA_COMMAND(findConstraint), DFHACK_LUA_COMMAND(setConstraint), + DFHACK_LUA_COMMAND(getCountHistory), DFHACK_LUA_END }; @@ -1521,10 +1608,10 @@ static void print_constraint(color_ostream &out, ItemConstraint *cv, bool no_job << cv->goalCount() << " (gap " << cv->goalGap() << ")" << endl; out.reset_color(); - if (cv->item_count || cv->item_inuse) + if (cv->item_count || cv->item_inuse_count) out << prefix << " items: amount " << cv->item_amount << "; " << cv->item_count << " stacks available, " - << cv->item_inuse << " in use." << endl; + << cv->item_inuse_count << " in use." << endl; if (no_job) return; From 614225cc5f62ea6ccb6743ae38a32cec5e6270f8 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 28 Nov 2012 19:46:56 +0100 Subject: [PATCH 53/55] follow rename itemst.flags.artifact1 -> artifact --- plugins/autodump.cpp | 4 ++-- plugins/autolabor.cpp | 2 +- plugins/devel/stockcheck.cpp | 2 +- plugins/workflow.cpp | 2 +- scripts/autofarm.rb | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/autodump.cpp b/plugins/autodump.cpp index 5eb25964e..5b4804647 100644 --- a/plugins/autodump.cpp +++ b/plugins/autodump.cpp @@ -161,7 +161,7 @@ static command_result autodump_main(color_ostream &out, vector & parame || itm->flags.bits.in_building || itm->flags.bits.in_chest // || itm->flags.bits.in_inventory - || itm->flags.bits.artifact1 + || itm->flags.bits.artifact ) continue; @@ -271,7 +271,7 @@ command_result df_autodump_destroy_item(color_ostream &out, vector & pa if (item->flags.bits.construction || item->flags.bits.in_building || - item->flags.bits.artifact1) + item->flags.bits.artifact) { out.printerr("Choosing not to destroy buildings, constructions and artifacts.\n"); return CR_FAILURE; diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 83718bd09..e5047b434 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -1556,7 +1556,7 @@ static int stockcheck(color_ostream &out, vector & parameters) #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); F(spider_web); F(owned); F(in_job); #undef F diff --git a/plugins/devel/stockcheck.cpp b/plugins/devel/stockcheck.cpp index 666db0d79..679411b0e 100644 --- a/plugins/devel/stockcheck.cpp +++ b/plugins/devel/stockcheck.cpp @@ -144,7 +144,7 @@ static command_result stockcheck(color_ostream &out, vector & parameter #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); F(spider_web); F(owned); F(in_job); #undef F diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 813326175..05fdca55b 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -1126,7 +1126,7 @@ static void map_job_items(color_ostream &out) #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); #undef F bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS); diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index c89cb9ff4..cd381089e 100644 --- a/scripts/autofarm.rb +++ b/scripts/autofarm.rb @@ -66,7 +66,7 @@ class AutoFarm if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && !i.flags.hostile && !i.flags.on_fire && !i.flags.rotten && !i.flags.trader && !i.flags.in_building && !i.flags.construction && - !i.flags.artifact1 && plantable.has_key?(i.mat_index)) + !i.flags.artifact && plantable.has_key?(i.mat_index)) counts[i.mat_index] = counts[i.mat_index] + i.stack_size end } From 771a5ac50bfae154773a4b083277ec81ffa38148 Mon Sep 17 00:00:00 2001 From: jj Date: Wed, 28 Nov 2012 20:08:34 +0100 Subject: [PATCH 54/55] ruby: tweak flagarray#inspect --- plugins/ruby/ruby-autogen-defs.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index c3203bd52..4148659a6 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -138,7 +138,6 @@ module DFHack @@inspecting = {} # avoid infinite recursion on mutually-referenced objects def inspect cn = self.class.name.sub(/^DFHack::/, '') - cn << ' @' << ('0x%X' % _memaddr) if cn != '' out = "#<#{cn}" return out << ' ...>' if @@inspecting[_memaddr] @@inspecting[_memaddr] = true @@ -655,6 +654,13 @@ module DFHack DFHack.memory_bitarray_set(@_memaddr, idx, v) end end + def inspect + out = "#' + end include Enumerable end From 94e669058604b8a129ff431164a987b951f15f0d Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 29 Nov 2012 13:37:16 +0400 Subject: [PATCH 55/55] Don't complain about fake input tokens in simulateInput. --- library/lua/gui.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/lua/gui.lua b/library/lua/gui.lua index cfb058f9d..2145cfad1 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -10,13 +10,19 @@ local to_pen = dfhack.pen.parse CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} +local FAKE_INPUT_KEYS = { + _MOUSE_L = true, + _MOUSE_R = true, + _STRING = true, +} + function simulateInput(screen,...) local keys = {} local function push_key(arg) local kv = arg if type(arg) == 'string' then kv = df.interface_key[arg] - if kv == nil then + if kv == nil and not FAKE_INPUT_KEYS[arg] then error('Invalid keycode: '..arg) end end