diff --git a/Lua API.html b/Lua API.html index 04af5d672..f42905d01 100644 --- a/Lua API.html +++ b/Lua API.html @@ -2853,6 +2853,9 @@ this may be extended with mouse click support.

on_submit:Enter key callback; if specified, the list reacts to the key and calls it as on_submit(index,choice). +on_submit2:Shift-Enter key callback; if specified, the list reacts to the key +and calls it as on_submit2(index,choice). + row_height:Height of every row in text lines. icon_width:If not nil, the specified number of character columns @@ -2908,6 +2911,9 @@ with the following fields:

  • list:submit()

    Call the on_submit callback, as if the Enter key was handled.

  • +
  • list:submit2()

    +

    Call the on_submit2 callback, as if the Shift-Enter key was handled.

    +
  • diff --git a/Lua API.rst b/Lua API.rst index 4087ff0aa..714a41bfb 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -2710,6 +2710,16 @@ containing newlines, or a table with the following possible fields: Specifies a pen to paint as one tile before the main part of the token. +* ``token.width = ...`` + + If specified either as a value or a callback, the text field is padded + or truncated to the specified number. + +* ``token.pad_char = '?'`` + + If specified together with ``width``, the padding area is filled with + this character instead of just being skipped over. + * ``token.key = '...'`` Specifies the keycode associated with the token. The string description @@ -2777,6 +2787,8 @@ It has the following attributes: :on_select: Selection change callback; called as ``on_select(index,choice)``. :on_submit: Enter key callback; if specified, the list reacts to the key and calls it as ``on_submit(index,choice)``. +:on_submit2: Shift-Enter key callback; if specified, the list reacts to the key + and calls it as ``on_submit2(index,choice)``. :row_height: Height of every row in text lines. :icon_width: If not *nil*, the specified number of character columns are reserved to the left of the list item for the icons. @@ -2826,6 +2838,10 @@ The list supports the following methods: Call the ``on_submit`` callback, as if the Enter key was handled. +* ``list:submit2()`` + + Call the ``on_submit2`` callback, as if the Shift-Enter key was handled. + FilteredList class ------------------ @@ -2836,6 +2852,7 @@ In addition to passing through all attributes supported by List, it supports: :edit_pen: If specified, used instead of ``cursor_pen`` for the edit field. +:edit_below: If true, the edit field is placed below the list instead of above. :not_found_label: Specifies the text of the label shown when no items match the filter. The list choices may include the following attributes: diff --git a/NEWS b/NEWS index 2cde6ba68..65c647337 100644 --- a/NEWS +++ b/NEWS @@ -10,15 +10,18 @@ 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 - New tweaks: - - tweak military-training: speed up melee squad training up to 10x (depends on unit count). - 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: - 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. - - lua: lua interpreter. + - 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. + - 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. @@ -26,6 +29,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. @@ -38,6 +49,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 137971abd..deea72bef 100644 --- a/Readme.html +++ b/Readme.html @@ -342,199 +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. @@ -660,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.

    @@ -711,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.

    @@ -733,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.

    @@ -750,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.

      @@ -772,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:

    @@ -828,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.

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

    -

    advtools

    +

    advtools

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

    Usage:

    @@ -866,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 @@ -943,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.

    @@ -956,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 @@ -996,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:

    @@ -1011,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.

    -

    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 @@ -1054,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 @@ -1121,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:

    @@ -1161,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:

    @@ -1195,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. @@ -1252,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:

    @@ -1281,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.

    @@ -1306,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 @@ -1315,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:

    @@ -1390,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 @@ -1408,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.

    @@ -1475,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:

    @@ -1538,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:

    @@ -1566,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:

    @@ -1605,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:

    @@ -1629,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.

    @@ -1656,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.

    @@ -1692,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. @@ -1724,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:

    @@ -1768,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 @@ -1783,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:

    @@ -1840,49 +1852,68 @@ they are scuttled. - + - + - + - + - +takes for stable-temp to stop updates again when equilibrium is disturbed.

    + - +This is a necessary addition to the binary patch in bug 808.

    + - +reagents.

    + - + - +screen from constantly resetting to the top.

    + - + + + +
    stable-cursor:Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.
    stable-cursor:

    Saves the exact cursor position between t/q/k/d/etc menus of dwarfmode.

    +
    patrol-duty:Makes Train orders not count as patrol duty to stop unhappy thoughts. -Does NOT fix the problem when soldiers go off-duty (i.e. civilian).
    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.
     

    Fixes rendering of creature weight limits in pressure plate build menu.

    +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%
    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 +
    fast-heat:

    Further improves temperature update performance by ensuring that 1 degree of item temperature is crossed in no more than specified number of frames when updating from the environment temperature. This reduces the time it -takes for stable-temp to stop updates again when equilibrium is disturbed.

    fix-dimensions:Fixes subtracting small amount of thread/cloth/liquid from a stack +
    fix-dimensions:

    Fixes subtracting small amount of thread/cloth/liquid from a stack by splitting the stack and subtracting from the remaining single item. -This is a necessary addition to the binary patch in bug 808.

    advmode-contained:
     Works around bug 6202, i.e. custom reactions with container inputs +
     

    Works around bug 6202, i.e. custom reactions with container inputs in advmode. The issue is that the screen tries to force you to select the contents separately from the container. This forcefully skips child -reagents.

    fast-trade:Makes Shift-Enter in the Move Goods to Depot and Trade screens select -the current item (fully, in case of a stack), and scroll down one line.
    fast-trade:

    Makes Shift-Enter in the Move Goods to Depot and Trade screens select +the current item (fully, in case of a stack), and scroll down one line.

    +
    military-stable-assign:
     Preserve list order and cursor position when assigning to squad, +
     

    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 -to make them stand out more in the list.
     

    Color squad candidates already assigned to other squads in yellow/green +to make them stand out more in the list.

    +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

    +

    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, @@ -1936,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 @@ -1958,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.

    @@ -1978,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).

    @@ -1993,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:
    @@ -2024,7 +2055,7 @@ in a workshop, or the unit/jobs screen.
    -

    job-material

    +

    job-material

    Alter the material of the selected job.

    Invoked as:

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

    job-duplicate

    +

    job-duplicate

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

    workflow

    +

    workflow

    Manage control of repeat jobs.

    Usage:

    @@ -2085,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.

    @@ -2098,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>]
    @@ -2127,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
    @@ -2176,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:

    @@ -2283,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 @@ -2292,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. @@ -2310,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 @@ -2328,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 @@ -2354,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 @@ -2383,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 @@ -2491,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 @@ -2505,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:
    @@ -2537,7 +2568,7 @@ twice.

    -

    misery

    +

    misery

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

    Usage:

    @@ -2561,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.

    @@ -2570,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

      @@ -2596,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>
    @@ -2611,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 @@ -2629,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. @@ -2641,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, @@ -2655,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.

    @@ -2681,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.

    @@ -2696,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:

    @@ -2714,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:

    @@ -2724,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)

      @@ -2752,23 +2783,57 @@ 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.

    Note

    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.

    -

    Dwarf Manipulator

    +

    Dwarf Manipulator

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

    images/manipulator.png @@ -2776,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 @@ -2805,7 +2872,7 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

    +
    +

    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

    -

    To use, bind to a key and activate in the 'k' mode.

    +

    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

    -

    To use, bind to a key and activate in the 'q' mode.

    +

    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 the view on the relevant linked buildings.

    @@ -2842,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.

      @@ -2861,18 +2955,21 @@ their species string.

    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.

    +

    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

    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.

    +

    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 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 @@ -2881,15 +2978,17 @@ only that entry, and does it even if it is not 'individual choice'.

    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.

    +

    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

    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.

    +

    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

    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.

    @@ -2923,8 +3022,9 @@ 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

    -

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

    +

    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

    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 @@ -2945,15 +3045,16 @@ the job material first using job as described in workflow documentation above. In this manner, this feature can be used for troubleshooting jobs that don't match the right constraints.

    images/workflow-new1.png -

    After selecting one of the presented outputs, the interface proceeds to the +

    If you select one of the outputs with Enter, the matching constraint is simply +added to the list. If you use Shift-Enter, the interface proceeds to the next dialog, which allows you to edit the suggested constraint parameters to suit your need, and set the item count range.

    images/workflow-new2.png -

    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.

    +

    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

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

    @@ -2976,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.

    @@ -2987,22 +3088,23 @@ 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 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 @@ -3022,21 +3124,22 @@ 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 -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.

    -

    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 @@ -3047,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.

    @@ -3071,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 @@ -3102,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 @@ -3111,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 @@ -3122,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 4488490dd..a214a6ecb 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! ============================= @@ -1077,6 +1087,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 +1108,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 ---------- @@ -1946,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 ======================= @@ -1958,6 +2013,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. @@ -1974,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. @@ -2036,10 +2098,46 @@ 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 =========== -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 +2148,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 +2186,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 +2205,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 +2220,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 +2232,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 +2280,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 @@ -2204,20 +2309,19 @@ can be used for troubleshooting jobs that don't match the right constraints. .. image:: images/workflow-new1.png -After selecting one of the presented outputs, the interface proceeds to the +If you select one of the outputs with Enter, the matching constraint is simply +added to the list. If you use Shift-Enter, the interface proceeds to the next dialog, which allows you to edit the suggested constraint parameters to suit your need, and set the item count range. .. image:: images/workflow-new2.png -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 +2382,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 +2415,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/assign-rack.png b/images/assign-rack.png index c0acb5802..4ebcbf590 100644 Binary files a/images/assign-rack.png and b/images/assign-rack.png differ diff --git a/images/automaterial-mat.png b/images/automaterial-mat.png new file mode 100644 index 000000000..5517bbc6d Binary files /dev/null and b/images/automaterial-mat.png differ diff --git a/images/automaterial-pos.png b/images/automaterial-pos.png new file mode 100644 index 000000000..959573643 Binary files /dev/null and b/images/automaterial-pos.png differ diff --git a/images/guide-path.png b/images/guide-path.png index 0a3e967a3..afb6511fa 100644 Binary files a/images/guide-path.png and b/images/guide-path.png differ diff --git a/images/liquids.png b/images/liquids.png index f2058695d..da70a058b 100644 Binary files a/images/liquids.png and b/images/liquids.png differ diff --git a/images/manipulator.png b/images/manipulator.png index de61e30fc..1f452549f 100644 Binary files a/images/manipulator.png and b/images/manipulator.png differ diff --git a/images/manipulator2.png b/images/manipulator2.png new file mode 100644 index 000000000..38c6c3cfb Binary files /dev/null and b/images/manipulator2.png differ diff --git a/images/mechanisms.png b/images/mechanisms.png index c46868449..062d126cd 100644 Binary files a/images/mechanisms.png and b/images/mechanisms.png differ diff --git a/images/power-meter.png b/images/power-meter.png index 4d84f3761..b1a4b6229 100644 Binary files a/images/power-meter.png and b/images/power-meter.png differ diff --git a/images/rename-bld.png b/images/rename-bld.png index e1fa8ef31..0028a87a5 100644 Binary files a/images/rename-bld.png and b/images/rename-bld.png differ diff --git a/images/rename-prof.png b/images/rename-prof.png index 789f6fe6f..5f2cfc453 100644 Binary files a/images/rename-prof.png and b/images/rename-prof.png differ diff --git a/images/room-list.png b/images/room-list.png index 34c93591a..5a60398f8 100644 Binary files a/images/room-list.png and b/images/room-list.png differ diff --git a/images/search.png b/images/search.png index f299622fc..384c3c533 100644 Binary files a/images/search.png and b/images/search.png differ diff --git a/images/siege-engine.png b/images/siege-engine.png index 509b26046..032e265dc 100644 Binary files a/images/siege-engine.png and b/images/siege-engine.png differ diff --git a/images/tweak-mil-color.png b/images/tweak-mil-color.png new file mode 100644 index 000000000..b4a012cab Binary files /dev/null and b/images/tweak-mil-color.png differ diff --git a/images/tweak-plate.png b/images/tweak-plate.png new file mode 100644 index 000000000..b7ba2c88d Binary files /dev/null and b/images/tweak-plate.png differ diff --git a/images/workflow-new1.png b/images/workflow-new1.png index ccc0e5fcc..50d0e1f42 100644 Binary files a/images/workflow-new1.png and b/images/workflow-new1.png differ diff --git a/images/workflow-new2.png b/images/workflow-new2.png index 944563316..74a4922be 100644 Binary files a/images/workflow-new2.png and b/images/workflow-new2.png differ diff --git a/images/workflow.png b/images/workflow.png index 70b41f29b..a0a0d4216 100644 Binary files a/images/workflow.png and b/images/workflow.png differ diff --git a/images/workshop-job-item.png b/images/workshop-job-item.png index 952f19ae0..cef691918 100644 Binary files a/images/workshop-job-item.png and b/images/workshop-job-item.png differ diff --git a/images/workshop-job-material.png b/images/workshop-job-material.png index 2ad69eaf4..d75797766 100644 Binary files a/images/workshop-job-material.png and b/images/workshop-job-material.png differ diff --git a/images/workshop-job.png b/images/workshop-job.png index 25ecff0b2..938b84374 100644 Binary files a/images/workshop-job.png and b/images/workshop-job.png differ diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index 2c245ae4c..bfc393734 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -28,7 +28,9 @@ distribution. #include "BitArray.h" #include "ColorText.h" #include "Types.h" + #include +#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/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/library/lua/gui.lua b/library/lua/gui.lua index cfb058f9d..99bf9263c 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 @@ -106,10 +112,14 @@ function inset_frame(rect, inset, gap) return mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) end -function compute_frame_body(wavail, havail, spec, inset, gap) +function compute_frame_body(wavail, havail, spec, inset, gap, inner_frame) gap = gap or 0 local l,t,r,b = parse_inset(inset) - local rect = compute_frame_rect(wavail, havail, spec, gap*2+l+r, gap*2+t+b) + local xgap,ygap = 0,0 + if inner_frame then + xgap,ygap = gap*2+l+r, gap*2+t+b + end + local rect = compute_frame_rect(wavail, havail, spec, xgap, ygap) local body = mkdims_xy(rect.x1+l+gap, rect.y1+t+gap, rect.x2-r-gap, rect.y2-b-gap) return rect, body end @@ -617,7 +627,7 @@ end function FramedScreen:computeFrame(parent_rect) local sw, sh = parent_rect.width, parent_rect.height local fw, fh = self:getWantedFrameSize(parent_rect) - return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1) + return compute_frame_body(sw, sh, { w = fw, h = fh }, self.frame_inset, 1, true) end function FramedScreen:onRenderFrame(dc, rect) diff --git a/library/lua/gui/dialogs.lua b/library/lua/gui/dialogs.lua index 0a79b4c3e..fb9b8fd63 100644 --- a/library/lua/gui/dialogs.lua +++ b/library/lua/gui/dialogs.lua @@ -152,7 +152,9 @@ ListBox.ATTRS{ with_filter = false, cursor_pen = DEFAULT_NIL, select_pen = DEFAULT_NIL, - on_select = DEFAULT_NIL + on_select = DEFAULT_NIL, + on_select2 = DEFAULT_NIL, + select2_hint = DEFAULT_NIL, } function ListBox:preinit(info) @@ -168,6 +170,16 @@ function ListBox:init(info) list_widget = widgets.FilteredList end + local on_submit2 + if self.select2_hint or self.on_select2 then + on_submit2 = function(sel, obj) + self:dismiss() + if self.on_select2 then self.on_select2(sel, obj) end + local cb = obj.on_select2 + if cb then cb(obj, sel) end + end + end + self:addviews{ list_widget{ view_id = 'list', @@ -182,11 +194,19 @@ function ListBox:init(info) local cb = obj.on_select or obj[2] if cb then cb(obj, sel) end end, + on_submit2 = on_submit2, frame = { l = 0, r = 0 }, } } end +function ListBox:onRenderFrame(dc,rect) + ListBox.super.onRenderFrame(self,dc,rect) + if self.select2_hint then + dc:seek(rect.x1+2,rect.y2):key('SEC_SELECT'):string(': '..self.select2_hint,COLOR_DARKGREY) + end +end + function ListBox:getWantedFrameSize() local mw, mh = InputBox.super.getWantedFrameSize(self) local list = self.subviews.list diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index 67090e114..ebbffbbbd 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -60,6 +60,7 @@ Panel = defclass(Panel, Widget) Panel.ATTRS { on_render = DEFAULT_NIL, + on_layout = DEFAULT_NIL, } function Panel:init(args) @@ -70,6 +71,10 @@ function Panel:onRenderBody(dc) if self.on_render then self.on_render(dc) end end +function Panel:postComputeFrame(body) + if self.on_layout then self.on_layout(body) end +end + ----------- -- Pages -- ----------- @@ -242,7 +247,7 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) end if token.text or token.key then - local text = getval(token.text) or '' + local text = ''..(getval(token.text) or '') local keypen if dc then @@ -256,7 +261,23 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) end end - x = x + #text + local width = getval(token.width) + local padstr + if width then + x = x + width + if #text > width then + text = string.sub(text,1,width) + else + if token.pad_char then + padstr = string.rep(token.pad_char,width-#text) + end + if dc and token.rjustify then + if padstr then dc:string(padstr) else dc:advance(width-#text) end + end + end + else + x = x + #text + end if token.key then local keystr = gui.getKeyDisplay(token.key) @@ -281,6 +302,10 @@ function render_text(obj,dc,x0,y0,pen,dpen,disabled) dc:string(text) end end + + if width and dc and not token.rjustify then + if padstr then dc:string(padstr) else dc:advance(width-#text) end + end end token.x2 = x @@ -384,6 +409,7 @@ List.ATTRS{ inactive_pen = DEFAULT_NIL, on_select = DEFAULT_NIL, on_submit = DEFAULT_NIL, + on_submit2 = DEFAULT_NIL, row_height = 1, scroll_keys = STANDARDSCROLL, icon_width = DEFAULT_NIL, @@ -542,10 +568,19 @@ function List:submit() end end +function List:submit2() + if self.on_submit2 and #self.choices > 0 then + self.on_submit2(self:getSelected()) + end +end + function List:onInput(keys) if self.on_submit and keys.SELECT then self:submit() return true + elseif self.on_submit2 and keys.SEC_SELECT then + self:submit2() + return true else for k,v in pairs(self.scroll_keys) do if keys[k] then @@ -581,10 +616,14 @@ end FilteredList = defclass(FilteredList, Widget) +FilteredList.ATTRS { + edit_below = false, +} + function FilteredList:init(info) self.edit = EditField{ text_pen = info.edit_pen or info.cursor_pen, - frame = { l = info.icon_width, t = 0 }, + frame = { l = info.icon_width, t = 0, h = 1 }, on_change = self:callback('onFilterChange'), on_char = self:callback('onFilterChar'), } @@ -598,6 +637,10 @@ function FilteredList:init(info) scroll_keys = info.scroll_keys, icon_width = info.icon_width, } + if self.edit_below then + self.edit.frame = { l = info.icon_width, b = 0, h = 1 } + self.list.frame = { t = 0, b = 2 } + end if info.on_select then self.list.on_select = function() return info.on_select(self:getSelected()) @@ -608,11 +651,16 @@ function FilteredList:init(info) return info.on_submit(self:getSelected()) end end + if info.on_submit2 then + self.list.on_submit2 = function() + return info.on_submit2(self:getSelected()) + end + end self.not_found = Label{ visible = false, text = info.not_found_label or 'No matches', text_pen = COLOR_LIGHTRED, - frame = { l = info.icon_width, t = 2 }, + frame = { l = info.icon_width, t = self.list.frame.t }, } self:addviews{ self.edit, self.list, self.not_found } self:setChoices(info.choices, info.selected) @@ -634,6 +682,10 @@ function FilteredList:submit() return self.list:submit() end +function FilteredList:submit2() + return self.list:submit2() +end + function FilteredList:canSubmit() return not self.not_found.visible end 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/CMakeLists.txt b/plugins/CMakeLists.txt index 29b1d3d79..1c89d9ab7 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -118,6 +118,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(forceequip forceequip.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(search search.cpp) + DFHACK_PLUGIN(automaterial automaterial.cpp) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) 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/automaterial.cpp b/plugins/automaterial.cpp new file mode 100644 index 000000000..9f383b935 --- /dev/null +++ b/plugins/automaterial.cpp @@ -0,0 +1,378 @@ +// Auto Material Select + +#include +#include +#include + +#include "Core.h" +#include +#include +#include +#include + + +// DF data structure definition headers +#include "DataDefs.h" +#include "MiscUtils.h" +#include "df/build_req_choice_genst.h" +#include "df/build_req_choice_specst.h" +#include "df/construction_type.h" +#include "df/item.h" +#include "df/ui.h" +#include "df/ui_build_selector.h" +#include "df/viewscreen_dwarfmodest.h" + +#include "modules/Gui.h" +#include "modules/Screen.h" + +using std::map; +using std::string; +using std::vector; + +using namespace DFHack; +using namespace df::enums; +using df::global::gps; +using df::global::ui; +using df::global::ui_build_selector; + +DFHACK_PLUGIN("automaterial"); + +struct MaterialDescriptor +{ + df::item_type item_type; + int16_t item_subtype; + int16_t type; + int32_t index; + bool valid; + + bool matches(const MaterialDescriptor &a) const + { + return a.valid && valid && + a.type == type && + a.index == index && + a.item_type == item_type && + a.item_subtype == item_subtype; + } +}; + +static map last_used_material; +static map last_moved_material; +static map< int16_t, vector > preferred_materials; +static map< int16_t, df::interface_key > hotkeys; +static bool last_used_moved = false; +static bool auto_choose_materials = true; +static bool auto_choose_attempted = true; +static bool revert_to_last_used_type = false; + +static command_result automaterial_cmd(color_ostream &out, vector & parameters) +{ + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +static inline bool in_material_choice_stage() +{ + return Gui::build_selector_hotkey(Core::getTopViewscreen()) && + ui_build_selector->building_type == df::building_type::Construction && + ui->main.mode == ui_sidebar_mode::Build && + ui_build_selector->stage == 2; +} + +static inline bool in_placement_stage() +{ + return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) && + ui->main.mode == ui_sidebar_mode::Build && + ui_build_selector && + ui_build_selector->building_type == df::building_type::Construction && + ui_build_selector->stage == 1; +} + +static inline bool in_type_choice_stage() +{ + return Gui::dwarfmode_hotkey(Core::getTopViewscreen()) && + ui->main.mode == ui_sidebar_mode::Build && + ui_build_selector && + ui_build_selector->building_type < 0; +} + +static inline vector &get_curr_constr_prefs() +{ + if (preferred_materials.find(ui_build_selector->building_subtype) == preferred_materials.end()) + preferred_materials[ui_build_selector->building_subtype] = vector(); + + return preferred_materials[ui_build_selector->building_subtype]; +} + +static inline MaterialDescriptor &get_last_used_material() +{ + if (last_used_material.find(ui_build_selector->building_subtype) == last_used_material.end()) + last_used_material[ui_build_selector->building_subtype] = MaterialDescriptor(); + + return last_used_material[ui_build_selector->building_subtype]; +} + +static void set_last_used_material(const MaterialDescriptor &matetial) +{ + last_used_material[ui_build_selector->building_subtype] = matetial; +} + +static MaterialDescriptor &get_last_moved_material() +{ + if (last_moved_material.find(ui_build_selector->building_subtype) == last_moved_material.end()) + last_moved_material[ui_build_selector->building_subtype] = MaterialDescriptor(); + + return last_moved_material[ui_build_selector->building_subtype]; +} + +static void set_last_moved_material(const MaterialDescriptor &matetial) +{ + last_moved_material[ui_build_selector->building_subtype] = matetial; +} + +static MaterialDescriptor get_material_in_list(size_t i) +{ + MaterialDescriptor result; + result.valid = false; + + if (VIRTUAL_CAST_VAR(gen, df::build_req_choice_genst, ui_build_selector->choices[i])) + { + result.item_type = gen->item_type; + result.item_subtype = gen->item_subtype; + result.type = gen->mat_type; + result.index = gen->mat_index; + result.valid = true; + } + else if (VIRTUAL_CAST_VAR(spec, df::build_req_choice_specst, ui_build_selector->choices[i])) + { + result.item_type = gen->item_type; + result.item_subtype = gen->item_subtype; + result.type = spec->candidate->getActualMaterial(); + result.index = spec->candidate->getActualMaterialIndex(); + result.valid = true; + } + + return result; +} + +static bool is_material_in_autoselect(size_t &i, MaterialDescriptor &material) +{ + for (i = 0; i < get_curr_constr_prefs().size(); i++) + { + if (get_curr_constr_prefs()[i].matches(material)) + return true; + } + + return false; +} + +static bool is_material_in_list(size_t &i, MaterialDescriptor &material) +{ + const size_t size = ui_build_selector->choices.size(); //Just because material list could be very big + for (i = 0; i < size; i++) + { + if (get_material_in_list(i).matches(material)) + return true; + } + + return false; +} + +static bool move_material_to_top(MaterialDescriptor &material) +{ + size_t i; + if (is_material_in_list(i, material)) + { + auto sel_item = ui_build_selector->choices[i]; + ui_build_selector->choices.erase(ui_build_selector->choices.begin() + i); + ui_build_selector->choices.insert(ui_build_selector->choices.begin(), sel_item); + + ui_build_selector->sel_index = 0; + set_last_moved_material(material); + return true; + } + + set_last_moved_material(MaterialDescriptor()); + return false; +} + +static bool check_autoselect(MaterialDescriptor &material, bool toggle) +{ + size_t idx; + if (is_material_in_autoselect(idx, material)) + { + if (toggle) + vector_erase_at(get_curr_constr_prefs(), idx); + + return true; + } + else + { + if (toggle) + get_curr_constr_prefs().push_back(material); + + return false; + } +} + +struct jobutils_hook : public df::viewscreen_dwarfmodest +{ + typedef df::viewscreen_dwarfmodest interpose_base; + + bool choose_materials() + { + size_t size = ui_build_selector->choices.size(); + for (size_t i = 0; i < size; i++) + { + MaterialDescriptor material = get_material_in_list(i); + size_t j; + if (is_material_in_autoselect(j, material)) + { + ui_build_selector->sel_index = i; + std::set< df::interface_key > keys; + keys.insert(df::interface_key::SELECT_ALL); + this->feed(&keys); + if (!in_material_choice_stage()) + return true; + } + } + + return false; + } + + DEFINE_VMETHOD_INTERPOSE(void, feed, (set *input)) + { + if (in_material_choice_stage()) + { + MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); + if (material.valid) + { + if (input->count(interface_key::SELECT) || input->count(interface_key::SEC_SELECT)) + { + if (get_last_moved_material().matches(material)) + last_used_moved = false; + + set_last_used_material(material); + } + else if (input->count(interface_key::CUSTOM_A)) + { + check_autoselect(material, true); + input->clear(); + } + } + } + else if (in_placement_stage()) + { + if (input->count(interface_key::CUSTOM_A)) + { + auto_choose_materials = !auto_choose_materials; + } + else if (input->count(interface_key::CUSTOM_T)) + { + revert_to_last_used_type = !revert_to_last_used_type; + } + } + + int16_t last_used_constr_subtype = (in_material_choice_stage()) ? ui_build_selector->building_subtype : -1; + INTERPOSE_NEXT(feed)(input); + + if (revert_to_last_used_type && + last_used_constr_subtype >= 0 && + !in_material_choice_stage() && + hotkeys.find(last_used_constr_subtype) != hotkeys.end()) + { + interface_key_set keys; + keys.insert(hotkeys[last_used_constr_subtype]); + INTERPOSE_NEXT(feed)(&keys); + } + } + + DEFINE_VMETHOD_INTERPOSE(void, render, ()) + { + if (in_material_choice_stage()) + { + if (!last_used_moved) + { + if (auto_choose_materials && get_curr_constr_prefs().size() > 0) + { + last_used_moved = true; + if (choose_materials()) + { + return; + } + } + else if (ui_build_selector->is_grouped) + { + last_used_moved = true; + move_material_to_top(get_last_used_material()); + } + } + else if (!ui_build_selector->is_grouped) + { + last_used_moved = false; + } + } + else + { + last_used_moved = false; + } + + INTERPOSE_NEXT(render)(); + + if (in_material_choice_stage()) + { + MaterialDescriptor material = get_material_in_list(ui_build_selector->sel_index); + if (material.valid) + { + string title = "Disabled"; + if (check_autoselect(material, false)) + { + title = "Enabled"; + } + + 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 < construction_type::TrackN) + { + string autoselect_toggle = (auto_choose_materials) ? "Disable" : "Enable"; + string revert_toggle = (revert_to_last_used_type) ? "Disable" : "Enable"; + + auto dims = Gui::getDwarfmodeViewDims(); + Screen::Painter dc(dims.menu()); + + 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"); + } + } +}; + +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 || !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; + hotkeys[construction_type::Floor] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FLOOR; + hotkeys[construction_type::Ramp] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_RAMP; + hotkeys[construction_type::UpStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UP; + hotkeys[construction_type::DownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_DOWN; + hotkeys[construction_type::UpDownStair] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_STAIR_UPDOWN; + hotkeys[construction_type::Fortification] = df::interface_key::HOTKEY_BUILDING_CONSTRUCTION_FORTIFICATION; + //Ignore tracks, DF already returns to track menu + + return CR_OK; +} \ No newline at end of file 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/lua/workflow.lua b/plugins/lua/workflow.lua index 19ca0a84a..b3bcf3d08 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -8,10 +8,11 @@ local utils = require 'utils' * isEnabled() * setEnabled(enable) - * listConstraints([job]) -> {...} + * listConstraints([job[,with_history]]) -> {{...},...} * findConstraint(token) -> {...} or nil * setConstraint(token[, by_count, goal, gap]) -> {...} * deleteConstraint(token) -> true/false + * getCountHistory(token) -> {{...},...} or nil --]] diff --git a/plugins/manipulator.cpp b/plugins/manipulator.cpp index 79999d468..88dc61726 100644 --- a/plugins/manipulator.cpp +++ b/plugins/manipulator.cpp @@ -456,21 +456,23 @@ 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]; 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] = 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; @@ -840,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) @@ -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"); @@ -1057,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); } } @@ -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, y, 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, y, 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/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/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 diff --git a/plugins/ruby/map.rb b/plugins/ruby/map.rb index 7c616d722..e9500efa3 100644 --- a/plugins/ruby/map.rb +++ b/plugins/ruby/map.rb @@ -159,8 +159,8 @@ module DFHack v.inorganic_mat if v end - # return the world_data.geo_biome for current tile - def geo_biome + # return the RegionMapEntry (from designation.biome) + def region_map_entry b = designation.biome wd = df.world.world_data @@ -174,7 +174,12 @@ module DFHack ry -= 1 if b < 3 and ry > 0 ry += 1 if b > 5 and ry < wd.world_height-1 - wd.geo_biomes[ wd.region_map[rx][ry].geo_index ] + wd.region_map[rx][ry] + end + + # return the world_data.geo_biome for current tile + def geo_biome + df.world.world_data.geo_biomes[ region_map_entry.geo_index ] end # return the world_data.geo_biome.layer for current tile @@ -182,14 +187,53 @@ module DFHack geo_biome.layers[designation.geolayer_index] end - # current tile mat_index (vein if applicable, or base material) - def mat_index - mat_index_vein or stone_layer.mat_index + # MaterialInfo: token for current tile, based on tilemat (vein, soil, plant, lava_stone...) + def mat_info + case tilemat + when :SOIL + base = stone_layer + if !df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY] + base = geo_biome.layers.find_all { |l| df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] }.last + end + mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length)) + MaterialInfo.new(0, mat_index) + + when :STONE + base = stone_layer + if df.world.raws.inorganics[base.mat_index].flags[:SOIL_ANY] + base = geo_biome.layers.find { |l| !df.world.raws.inorganics[l.mat_index].flags[:SOIL_ANY] } + end + mat_index = (base ? base.mat_index : rand(df.world.raws.inorganics.length)) + MaterialInfo.new(0, mat_index) + + when :MINERAL + mat_index = (mat_index_vein || stone_layer.mat_index) + MaterialInfo.new(0, mat_index) + + when :LAVA_STONE + # XXX this is wrong + # maybe should search world.region_details.pos == biome_region_pos ? + idx = mapblock.region_offset[designation.biome] + mat_index = df.world.world_data.region_details[idx].lava_stone + MaterialInfo.new(0, mat_index) + + # TODO + #when :PLANT + #when :GRASS_DARK, :GRASS_DEAD, :GRASS_DRY, :GRASS_LIGHT + #when :FEATURE + #when :FROZEN_LIQUID + #when :CONSTRUCTION + else # AIR ASHES BROOK CAMPFIRE DRIFTWOOD FIRE HFS MAGMA POOL RIVER + MaterialInfo.new(-1, -1) + end end - # MaterialInfo: inorganic token for current tile - def mat_info - MaterialInfo.new(0, mat_index) + def mat_type + mat_info.mat_type + end + + def mat_index + mat_info.mat_index end def inspect diff --git a/plugins/ruby/ruby-autogen-defs.rb b/plugins/ruby/ruby-autogen-defs.rb index c3203bd52..a3e810178 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 @@ -309,7 +308,7 @@ module DFHack DFHack.memory_write_int32(@_memaddr, v) end when nil; DFHack.memory_write_int32(@_memaddr, 0) - else _get._set(v) + else @_tg._at(_getp)._set(v) end end @@ -655,6 +654,13 @@ module DFHack DFHack.memory_bitarray_set(@_memaddr, idx, v) end end + def inspect + out = "#' + end include Enumerable end 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 ab095e8d8..27cde675a 100644 --- a/plugins/ruby/ruby.rb +++ b/plugins/ruby/ruby.rb @@ -23,9 +23,12 @@ module Kernel end module DFHack + VERSION = version + 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 +37,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 - rescue + # t0 = Time.now + @callback.call + # dt = Time.now - t0 ; puts "rb cb #@description took #{'%.02f' % dt}s" if dt > 0.1 + rescue Exception df.onupdate_unregister self - puts_err "onupdate cb #$!", $!.backtrace + puts_err "onupdate #@description unregistered: #$!", $!.backtrace end def <=>(o) @@ -61,10 +63,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 +76,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/plugins/search.cpp b/plugins/search.cpp index cc3f29c12..a14397fba 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"); } } @@ -519,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()) { 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(); } diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 6e15a4537..12174af5c 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -291,8 +291,18 @@ int ProtectedJob::cur_tick_idx = 0; typedef std::map, bool> TMaterialCache; +static const size_t MAX_HISTORY_SIZE = 28; + +enum HistoryItem { + HIST_COUNT = 0, + HIST_AMOUNT, + HIST_INUSE_COUNT, + HIST_INUSE_AMOUNT +}; + struct ItemConstraint { PersistentDataItem config; + PersistentDataItem history; // Fixed key parsed into fields bool is_craft; @@ -308,7 +318,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 +328,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 +362,37 @@ 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; + } + int history_value(int idx, HistoryItem item) { + size_t hsize = history_size(); + size_t base = ((history.ival(0)+1+idx) % hsize) * hist_entry_size; + return history.get_int28(base + item*int28_size); + } + int history_count(int idx) { return history_value(idx, HIST_COUNT); } + int history_amount(int idx) { return history_value(idx, HIST_AMOUNT); } + int history_inuse_count(int idx) { return history_value(idx, HIST_INUSE_COUNT); } + int history_inuse_amount(int idx) { return history_value(idx, HIST_INUSE_AMOUNT); } + + void updateHistory() + { + size_t buffer_size = history_size(); + if (buffer_size < MAX_HISTORY_SIZE && size_t(history.ival(0)+1) == buffer_size) + history.ensure_data(hist_entry_size*++buffer_size); + history.ival(0) = (history.ival(0)+1) % buffer_size; + + size_t base = history.ival(0) * hist_entry_size; + + history.set_int28(base + HIST_COUNT*int28_size, item_count); + history.set_int28(base + HIST_AMOUNT*int28_size, item_amount); + history.set_int28(base + HIST_INUSE_COUNT*int28_size, item_inuse_count); + history.set_int28(base + HIST_INUSE_AMOUNT*int28_size, item_inuse_amount); + } }; /****************************** @@ -649,6 +690,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 +703,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 +824,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 +837,7 @@ static void delete_constraint(ItemConstraint *cv) vector_erase_at(constraints, idx); World::DeletePersistentData(cv->config); + World::DeletePersistentData(cv->history); delete cv; } @@ -1064,7 +1115,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; @@ -1076,7 +1128,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); @@ -1177,7 +1229,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 { @@ -1333,6 +1386,25 @@ static void setEnabled(color_ostream &out, bool enable) } } +static void push_count_history(lua_State *L, ItemConstraint *icv) +{ + size_t hsize = icv->history_size(); + + lua_createtable(L, hsize, 0); + + for (size_t i = 0; i < hsize; 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, i+1); + } +} + static void push_constraint(lua_State *L, ItemConstraint *cv) { lua_newtable(L); @@ -1367,7 +1439,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 @@ -1378,19 +1451,31 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) lua_newtable(L); + bool resumed = false, want_resumed = false; + for (size_t i = 0, j = 0; i < cv->jobs.size(); i++) { if (!cv->jobs[i]->isLive()) continue; Lua::PushDFObject(L, cv->jobs[i]->actual_job); lua_rawseti(L, -2, ++j); + + if (cv->jobs[i]->want_resumed) { + want_resumed = true; + resumed = resumed || cv->jobs[i]->isActuallyResumed(); + } } lua_setfield(L, ctable, "jobs"); + + if (want_resumed && !resumed) + Lua::SetField(L, true, ctable, "is_delayed"); } static int listConstraints(lua_State *L) { + lua_settop(L, 2); auto job = Lua::CheckDFObject(L, 1); + bool with_history = lua_toboolean(L, 2); lua_pushnil(L); @@ -1415,6 +1500,13 @@ static int listConstraints(lua_State *L) for (size_t i = 0; i < vec.size(); i++) { push_constraint(L, vec[i]); + + if (with_history) + { + push_count_history(L, vec[i]); + lua_setfield(L, -2, "history"); + } + lua_rawseti(L, -2, i+1); } @@ -1463,6 +1555,24 @@ 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) + push_count_history(L, icv); + else + lua_pushnil(L); + + return 1; +} + + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(isEnabled), DFHACK_LUA_FUNCTION(setEnabled), @@ -1474,6 +1584,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 +1632,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; diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb index 9d39bc29a..3839a7ca9 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,7 +13,7 @@ class AutoFarm end end - def setdefault (v) + def setdefault(v) @thresholds.default = v.to_i end @@ -29,7 +29,7 @@ class AutoFarm def find_plantable_plants plantable = {} - counts = {} + counts = Hash.new(0) df.world.items.other[:SEEDS].each { |i| if (!i.flags.dump && !i.flags.forbid && !i.flags.garbage_collect && @@ -53,7 +53,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] @@ -79,7 +79,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 } @@ -108,13 +108,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/gui/workflow.lua b/scripts/gui/workflow.lua index 80c05d296..9a45f6554 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -66,20 +66,64 @@ function is_caste_mat(iobj) end function describe_material(iobj) - local matline = 'any material' + local matflags = utils.list_bitfield_flags(iobj.mat_mask) + if #matflags > 0 then + matflags = 'any '..table.concat(matflags, '/') + else + matflags = nil + end + if is_caste_mat(iobj) then - matline = 'no material' + return 'no material' elseif (iobj.mat_type or -1) >= 0 then local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) + local matline if info then matline = info:toString() else matline = iobj.mat_type..':'..iobj.mat_index end + return matline, matflags + else + return matflags or 'any material' + end +end + +function current_stock(iobj) + if iobj.goal_by_count then + return iobj.cur_count + else + return iobj.cur_amount end - return matline end +function if_by_count(iobj,bc,ba) + if iobj.goal_by_count then + return bc + else + return ba + end +end + +function compute_trend(history,field) + local count = #history + if count == 0 then + return 0 + end + local sumX,sumY,sumXY,sumXX = 0,0,0,0 + for i,v in ipairs(history) do + sumX = sumX + i + sumY = sumY + v[field] + sumXY = sumXY + i*v[field] + sumXX = sumXX + i*i + end + return (count * sumXY - sumX * sumY) / (count * sumXX - sumX * sumX) +end + +------------------------ +-- RANGE EDITOR GROUP -- +------------------------ + local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } RangeEditor = defclass(RangeEditor, widgets.Label) @@ -162,6 +206,10 @@ function RangeEditor:onIncRange(field, delta) self.save_cb(cons) end +--------------------------- +-- NEW CONSTRAINT DIALOG -- +--------------------------- + NewConstraint = defclass(NewConstraint, gui.FramedScreen) NewConstraint.focus_path = 'workflow/new' @@ -177,7 +225,7 @@ NewConstraint.ATTRS { } function NewConstraint:init(args) - self.constraint = args.constraint or {} + self.constraint = args.constraint or { item_type = -1 } rawset_default(self.constraint, { goal_value = 10, goal_gap = 5, goal_by_count = false }) local matlist = {} @@ -202,8 +250,16 @@ function NewConstraint:init(args) frame = { l = 1, t = 2, w = 26 }, text = { 'Type: ', - { pen = COLOR_LIGHTCYAN, - text = function() return describe_item_type(self.constraint) end }, + { pen = function() + if self:isValid() then return COLOR_LIGHTCYAN else return COLOR_LIGHTRED end + end, + text = function() + if self:isValid() then + return describe_item_type(self.constraint) + else + return 'item not set' + end + end }, NEWLINE, ' ', { key = 'CUSTOM_T', text = ': Select, ', on_activate = self:callback('chooseType') }, @@ -277,6 +333,7 @@ function NewConstraint:init(args) { key = 'LEAVESCREEN', text = ': Cancel, ', on_activate = self:callback('dismiss') }, { key = 'MENU_CONFIRM', key_sep = ': ', + enabled = self:callback('isValid'), text = function() if self.is_existing then return 'Update' else return 'Create new' end end, @@ -295,9 +352,17 @@ function NewConstraint:postinit() self:onChange() end +function NewConstraint:isValid() + return self.constraint.item_type >= 0 +end + function NewConstraint:onChange() local token = workflow.constraintToToken(self.constraint) - local out = workflow.findConstraint(token) + local out + + if self:isValid() then + out = workflow.findConstraint(token) + end if out then self.constraint = out @@ -390,6 +455,288 @@ function NewConstraint:onRangeChange() cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1)) end +------------------------------ +-- GLOBAL CONSTRAINT SCREEN -- +------------------------------ + +ConstraintList = defclass(ConstraintList, gui.FramedScreen) + +ConstraintList.focus_path = 'workflow/list' + +ConstraintList.ATTRS { + frame_title = 'Workflow Status', + frame_inset = 0, + frame_background = COLOR_BLACK, + frame_style = gui.BOUNDARY_FRAME, +} + +function ConstraintList:init(args) + local fwidth_cb = self:cb_getfield('fwidth') + + self.fwidth = 20 + self.sort_by_severity = false + + self:addviews{ + widgets.Panel{ + frame = { w = 31, r = 0, h = 6, t = 0 }, + frame_inset = 1, + subviews = { + widgets.Label{ + frame = { l = 0, t = 0 }, + enabled = self:callback('isAnySelected'), + text = { + { text = function() + local cur = self:getCurConstraint() + if cur then + return string.format( + 'Currently %d (%d in use)', + current_stock(cur), + if_by_count(cur, cur.cur_in_use_count, cur.cur_in_use_amount) + ) + else + return 'No constraint selected' + end + end } + } + }, + RangeEditor{ + frame = { l = 0, t = 2 }, + enabled = self:callback('isAnySelected'), + get_cb = self:callback('getCurConstraint'), + save_cb = self:callback('saveConstraint'), + }, + } + }, + widgets.Widget{ + active = false, + frame = { w = 1, r = 31 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + widgets.Widget{ + active = false, + frame = { w = 31, r = 0, h = 1, t = 6 }, + frame_background = gui.BOUNDARY_FRAME.frame_pen, + }, + widgets.Panel{ + frame = { l = 0, r = 32 }, + frame_inset = 1, + on_layout = function(body) + self.fwidth = body.width - (12+1+1+7+1+1+1+7) + end, + subviews = { + widgets.Label{ + frame = { l = 0, t = 0 }, + text_pen = COLOR_CYAN, + text = { + { text = 'Item', width = 12 }, ' ', + { text = 'Material etc', width = fwidth_cb }, ' ', + { text = 'Stock / Limit' }, + } + }, + widgets.FilteredList{ + view_id = 'list', + frame = { t = 2, b = 2 }, + edit_below = true, + not_found_label = 'No matching constraints', + edit_pen = COLOR_LIGHTCYAN, + text_pen = { fg = COLOR_GREY, bg = COLOR_BLACK }, + cursor_pen = { fg = COLOR_WHITE, bg = COLOR_GREEN }, + }, + widgets.Label{ + frame = { b = 0, h = 1 }, + text = { + { key = 'CUSTOM_SHIFT_A', text = ': Add', + on_activate = self:callback('onNewConstraint') }, ', ', + { key = 'CUSTOM_SHIFT_X', text = ': Delete', + on_activate = self:callback('onDeleteConstraint') }, ', ', + { key = 'CUSTOM_SHIFT_O', text = ': Severity Order', + on_activate = self:callback('onSwitchSort'), + pen = function() + if self.sort_by_severity then + return COLOR_LIGHTCYAN + else + return COLOR_WHITE + end + end }, ', ', + { key = 'CUSTOM_SHIFT_S', text = ': Search', + on_activate = function() + self.subviews.list.edit.active = not self.subviews.list.edit.active + end, + pen = function() + if self.subviews.list.edit.active then + return COLOR_LIGHTCYAN + else + return COLOR_WHITE + end + end } + } + } + } + }, + } + + self.subviews.list.edit.active = false + + self:initListChoices() +end + +function stock_trend_color(cons) + local stock = current_stock(cons) + if stock >= cons.goal_value - cons.goal_gap then + return COLOR_LIGHTGREEN, 0 + elseif stock <= cons.goal_gap then + return COLOR_LIGHTRED, 4 + elseif stock >= cons.goal_value - 2*cons.goal_gap then + return COLOR_GREEN, 1 + elseif stock <= 2*cons.goal_gap then + return COLOR_RED, 3 + else + local trend = if_by_count(cons, cons.trend_count, cons.trend_amount) + if trend > 0.3 then + return COLOR_GREEN, 1 + elseif trend < -0.3 then + return COLOR_RED, 3 + else + return COLOR_GREY, 2 + end + end +end + +function ConstraintList:initListChoices(clist, sel_token) + clist = clist or workflow.listConstraints(nil, true) + + local fwidth_cb = self:cb_getfield('fwidth') + local choices = {} + + for i,cons in ipairs(clist) do + cons.trend_count = compute_trend(cons.history, 'cur_count') + cons.trend_amount = compute_trend(cons.history, 'cur_amount') + + local itemstr = describe_item_type(cons) + local matstr,matflagstr = describe_material(cons) + if matflagstr then + matstr = matflagstr .. ' ' .. matstr + end + + if cons.min_quality > 0 or cons.is_local then + local lst = {} + if cons.is_local then + table.insert(lst, 'local') + end + if cons.min_quality > 0 then + table.insert(lst, string.lower(df.item_quality[cons.min_quality])) + end + matstr = matstr .. ' ('..table.concat(lst,',')..')' + end + + local goal_color = COLOR_GREY + if #cons.jobs == 0 then + goal_color = COLOR_RED + elseif cons.is_delayed then + goal_color = COLOR_YELLOW + end + + table.insert(choices, { + text = { + { text = itemstr, width = 12, pad_char = ' ' }, ' ', + { text = matstr, width = fwidth_cb, pad_char = ' ' }, ' ', + { text = curry(current_stock,cons), width = 7, rjustify = true, + pen = function() return { fg = stock_trend_color(cons) } end }, + { text = curry(if_by_count,cons,'S','I'), gap = 1, + pen = { fg = COLOR_GREY } }, + { text = function() return cons.goal_value end, gap = 1, + pen = { fg = goal_color } } + }, + severity = select(2, stock_trend_color(cons)), + search_key = itemstr .. ' | ' .. matstr, + token = cons.token, + obj = cons + }) + end + + self:setChoices(choices, sel_token) +end + +function ConstraintList:isAnySelected() + return self.subviews.list:getSelected() ~= nil +end + +function ConstraintList:getCurConstraint() + local selidx,selobj = self.subviews.list:getSelected() + if selobj then return selobj.obj end +end + +function ConstraintList:onSwitchSort() + self.sort_by_severity = not self.sort_by_severity + self:setChoices(self.subviews.list:getChoices()) +end + +function ConstraintList:setChoices(choices, sel_token) + if self.sort_by_severity then + table.sort(choices, function(a,b) + return a.severity > b.severity + or (a.severity == b.severity and + current_stock(a.obj)/a.obj.goal_value < current_stock(b.obj)/b.obj.goal_value) + end) + else + table.sort(choices, function(a,b) return a.search_key < b.search_key end) + end + + local selidx = nil + if sel_token then + selidx = utils.linear_index(choices, sel_token, 'token') + end + + local list = self.subviews.list + local filter = list:getFilter() + + list:setChoices(choices, selidx) + + if filter ~= '' then + list:setFilter(filter, selidx) + + if selidx and list:getSelected() ~= selidx then + list:setFilter('', selidx) + end + end +end + +function ConstraintList:onInput(keys) + if keys.LEAVESCREEN then + self:dismiss() + else + ConstraintList.super.onInput(self, keys) + end +end + +function ConstraintList:onNewConstraint() + NewConstraint{ + on_submit = self:callback('saveConstraint') + }:show() +end + +function ConstraintList:saveConstraint(cons) + local out = workflow.setConstraint(cons.token, cons.goal_by_count, cons.goal_value, cons.goal_gap) + self:initListChoices(nil, out.token) +end + +function ConstraintList:onDeleteConstraint() + local cons = self:getCurConstraint() + dlg.showYesNoPrompt( + 'Delete Constraint', + 'Really delete the current constraint?', + COLOR_YELLOW, + function() + workflow.deleteConstraint(cons.token) + self:initListChoices() + end + ) +end + +------------------------------- +-- WORKSHOP JOB INFO OVERLAY -- +------------------------------- + JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) JobConstraints.focus_path = 'workflow/job' @@ -480,24 +827,12 @@ function JobConstraints:initListChoices(clist, sel_token) end itemstr = itemstr .. ' ('..table.concat(lst,',')..')' end - local matstr = describe_material(cons) - local matflagstr = '' - local matflags = utils.list_bitfield_flags(cons.mat_mask) - if #matflags > 0 then - matflags[1] = 'any '..matflags[1] - if matstr == 'any material' then - matstr = table.concat(matflags, ', ') - matflags = {} - end - end - if #matflags > 0 then - matflagstr = table.concat(matflags, ', ') - end + local matstr,matflagstr = describe_material(cons) table.insert(choices, { text = { goal, ' ', { text = '(now '..curval..')', pen = order_pen }, NEWLINE, - ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', matflagstr + ' ', itemstr, NEWLINE, ' ', matstr, NEWLINE, ' ', (matflagstr or '') }, token = cons.token, obj = cons @@ -552,18 +887,22 @@ function JobConstraints:onNewConstraint() table.insert(choices, { text = itemstr..' of '..matstr, obj = cons }) end - dlg.showListPrompt( - 'New limit', - 'Select one of the possible outputs:', - COLOR_WHITE, - choices, - function(idx,item) + dlg.ListBox{ + frame_title = 'New limit', + text = 'Select one of the possible outputs:', + text_pen = COLOR_WHITE, + choices = choices, + on_select = function(idx,item) + self:saveConstraint(item.obj) + end, + select2_hint = 'Advanced', + on_select2 = function(idx,item) NewConstraint{ constraint = item.obj, on_submit = self:callback('saveConstraint') }:show() - end - ) + end, + }:show() end function JobConstraints:onDeleteConstraint() @@ -589,20 +928,25 @@ function JobConstraints:onInput(keys) end end -if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then - qerror("This script requires a workshop job selected in the 'q' mode") -end +local args = {...} -local job = dfhack.gui.getSelectedJob() +if args[1] == 'list' then + check_enabled(function() ConstraintList{}:show() end) +else + if not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/QueryBuilding/Some/Workshop/Job') then + qerror("This script requires a workshop job selected in the 'q' mode") + end -check_enabled(function() - check_repeat(job, function() - local clist = workflow.listConstraints(job) - if not clist then - dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) - return - end - JobConstraints{ job = job, clist = clist }:show() - end) -end) + local job = dfhack.gui.getSelectedJob() + check_enabled(function() + check_repeat(job, function() + local clist = workflow.listConstraints(job) + if not clist then + dlg.showMessage('Not Supported', 'This type of job is not supported by workflow.', COLOR_LIGHTRED) + return + end + JobConstraints{ job = job, clist = clist }:show() + end) + end) +end diff --git a/scripts/lever.rb b/scripts/lever.rb new file mode 100644 index 000000000..43aa29b04 --- /dev/null +++ b/scripts/lever.rb @@ -0,0 +1,120 @@ +# 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 + + puts lever_descr(bld) +end + +def lever_pull_cheat(bld) + bld.linked_mechanisms.each { |i| + i.general_refs.grep(DFHack::GeneralRefBuildingHolderst).each { |r| + r.building_tg.setTriggerState(bld.state) + } + } + + bld.state = (bld.state == 0 ? 1 : 0) + + 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.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) + }.flatten.each { |r| + # linked building description + tg = r.building_tg + state = '' + if tg.respond_to?(:gate_flags) + state << (tg.gate_flags.closed ? 'closed' : 'opened') + state << ", closing (#{tg.timer})" if tg.gate_flags.closing + state << ", opening (#{tg.timer})" if tg.gate_flags.opening + end + + 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 < 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) + empty_cages = 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 + + 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 + + +# 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 <