diff --git a/CMakeLists.txt b/CMakeLists.txt index 41c38bd44..50d50d18d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,7 +121,7 @@ ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS) ADD_DEFINITIONS(-DLUA_BUILD_AS_DLL) if(APPLE) - add_definitions(-D_DARWIN) + add_definitions(-D_DARWIN) elseif(UNIX) add_definitions(-D_LINUX) elseif(WIN32) @@ -172,6 +172,7 @@ IF(BUILD_LIBRARY) add_subdirectory (library) ## install the default documentation files install(FILES LICENSE "Lua API.html" Readme.html Compile.html Contributors.html DESTINATION ${DFHACK_USERDOC_DESTINATION}) + install(DIRECTORY images DESTINATION ${DFHACK_USERDOC_DESTINATION}) endif() #build the plugins @@ -181,11 +182,11 @@ endif() # Packaging with CPack! IF(UNIX) - if(APPLE) - SET(CPACK_GENERATOR "ZIP") - else() - SET(CPACK_GENERATOR "TGZ") - endif() + if(APPLE) + SET(CPACK_GENERATOR "ZIP") + else() + SET(CPACK_GENERATOR "TGZ") + endif() ELSEIF(WIN32) SET(CPACK_GENERATOR "ZIP") ENDIF() diff --git a/Lua API.html b/Lua API.html index d2e0da1ef..04af5d672 100644 --- a/Lua API.html +++ b/Lua API.html @@ -1109,6 +1109,12 @@ above operations accordingly. If enabled, pauses and zooms to position.

  • dfhack.job.printItemDetails(jobitem,idx)

    Prints info about the job item.

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

    +

    Searches for a general_ref with the given type.

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

    +

    Searches for a specific_ref with the given type.

    +
  • dfhack.job.getHolder(job)

    Returns the building holding the job.

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

  • dfhack.units.getPosition(unit)

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

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

    +

    Searches for a general_ref with the given type.

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

    +

    Searches for a specific_ref with the given type.

    +
  • dfhack.units.getContainer(unit)

    Returns the container (cage) item or nil.

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

  • dfhack.units.getEffectiveSkill(unit, skill)

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

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

    +

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

    +
  • dfhack.units.computeMovementSpeed(unit)

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

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

    Buildings module

  • 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,31 +2783,68 @@ 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

    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.

    +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 @@ -2804,8 +2872,9 @@ 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.

    To exit, press ESC or Enter; ESC recenters on the original building, while Enter leaves @@ -2838,31 +2936,40 @@ 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.

    • gui/rename [building] in 'q' mode changes the name of a building.

      +images/rename-bld.png

      The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. It is also possible to rename zones from the 'i' menu.

    • gui/rename [unit] with a unit selected changes the nickname.

      +

      Unlike the built-in interface, this works even on enemies and animals.

    • gui/rename unit-profession changes the selected unit's custom profession name.

      +images/rename-prof.png +

      Likewise, this can be applied to any unit, and when used on animals it overrides +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 @@ -2871,19 +2978,29 @@ 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.

    Specifically, pressing the 'i' key pops up a dialog that lets you select an item -type from a list. Pressing 'm', unless the item type does not allow a material, +type from a list.

    +images/workshop-job-item.png +

    Pressing 'm', unless the item type does not allow a material, lets you choose a material.

    +images/workshop-job-material.png +

    Since there are a lot more materials than item types, this dialog is more complex +and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked +with an arrow on the left.

    Warning

    Due to the way input reagent matching works in DF, you must select an item type @@ -2905,8 +3022,10 @@ 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 current job, and their current status.

    @@ -2920,15 +3039,23 @@ Pressing 'm' lets you input the range directly; 'e', 'r', 'd', 'f' adjust the bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting items and expanding the range each gives a 5x bonus).

    Pressing 'n' produces a list of possible outputs of this job as guessed by -workflow, and lets you create a new constraint by just choosing one. If you +workflow, and lets you create a new constraint by choosing one as template. If you don't see the choice you want in the list, it likely means you have to adjust the job material first using job item-material or gui/workshop-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 +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:

      @@ -2945,11 +3072,12 @@ unless the fix-armory

    The script interface simply lets you designate one of the squads that are assigned to the barracks/armory containing the selected stand as -the intended user.

    +the intended user. In order to aid in the choice, it shows the number +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.

    @@ -2960,22 +3088,24 @@ 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 hit by the selected engine: green for fully reachable, blue for out of range, red for blocked, @@ -2994,20 +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 @@ -3018,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.

    @@ -3042,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 @@ -3073,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 @@ -3082,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 @@ -3093,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 dbf04f9e8..7d715077a 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. @@ -1968,12 +2026,18 @@ Dwarf Manipulator Implemented by the manipulator plugin. To activate, open the unit screen and press 'l'. +.. image:: images/manipulator.png + 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. @@ -2012,6 +2076,8 @@ Search The search plugin adds search to the Stocks, Trading and Unit List screens. +.. image:: images/search.png + Searching works the same way as the search option in "Move to Depot" does. You will see the Search option displayed on screen with a hotkey (usually 's'). Pressing it lets you start typing a query and the relevant list will start @@ -2032,10 +2098,48 @@ 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 While active, use the suggested keys to switch the usual liquids parameters, and Enter to select the target area and apply changes. @@ -2044,7 +2148,9 @@ 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 Lists mechanisms connected to the building, and their links. Navigating the list centers the view on the relevant linked buildings. @@ -2062,21 +2168,35 @@ via a simple dialog in the game ui. * ``gui/rename [building]`` in 'q' mode changes the name of a building. + .. image:: images/rename-bld.png + The selected building must be one of stockpile, workshop, furnace, trap, or siege engine. It is also possible to rename zones from the 'i' menu. * ``gui/rename [unit]`` with a unit selected changes the nickname. + Unlike the built-in interface, this works even on enemies and animals. + * ``gui/rename unit-profession`` changes the selected unit's custom profession name. + .. image:: images/rename-prof.png + + Likewise, this can be applied to any unit, and when used on animals it overrides + 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. +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 The script lists other rooms owned by the same owner, or by the unit selected in the assign list, and allows unassigning them. @@ -2085,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 @@ -2099,7 +2220,10 @@ 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 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. @@ -2108,15 +2232,28 @@ 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 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. Specifically, pressing the 'i' key pops up a dialog that lets you select an item -type from a list. Pressing 'm', unless the item type does not allow a material, +type from a list. + +.. image:: images/workshop-job-item.png + +Pressing 'm', unless the item type does not allow a material, lets you choose a material. +.. image:: images/workshop-job-material.png + +Since there are a lot more materials than item types, this dialog is more complex +and uses a hierarchy of sub-menus. List choices that open a sub-menu are marked +with an arrow on the left. + .. warning:: Due to the way input reagent matching works in DF, you must select an item type @@ -2143,7 +2280,10 @@ 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 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 @@ -2161,17 +2301,31 @@ bounds by 1, 5, or 25 depending on the direction and the 'c' setting (counting items and expanding the range each gives a 5x bonus). Pressing 'n' produces a list of possible outputs of this job as guessed by -workflow, and lets you create a new constraint by just choosing one. If you +workflow, and lets you create a new constraint by choosing one as template. If you don't see the choice you want in the list, it likely means you have to adjust the job material first using ``job item-material`` or ``gui/workshop-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. +.. image:: images/workflow-new1.png + +After selecting one of the presented outputs, 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 This script is part of a group of related fixes to make the armory storage work again. The existing issues are: @@ -2191,7 +2345,8 @@ work again. The existing issues are: The script interface simply lets you designate one of the squads that are assigned to the barracks/armory containing the selected stand as -the intended user. +the intended user. In order to aid in the choice, it shows the number +of currently assigned racks for every valid squad. gui/advfort @@ -2253,7 +2408,10 @@ 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 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 @@ -2283,7 +2441,10 @@ 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 The script follows the general look and feel of the regular pressure plate build configuration page, but configures parameters relevant to the modded power meter building. diff --git a/dfhack.init-example b/dfhack.init-example index 885849c33..8fafa4cf4 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -133,6 +133,9 @@ tweak military-stable-assign # in same list, color units already assigned to squads in brown & green tweak military-color-assigned +# remove inverse dependency of squad training speed on unit list size and use more sparring +tweak military-training + ####################################################### # Apply binary patches at runtime # # # diff --git a/images/assign-rack.png b/images/assign-rack.png new file mode 100644 index 000000000..4ebcbf590 Binary files /dev/null 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 new file mode 100644 index 000000000..afb6511fa Binary files /dev/null and b/images/guide-path.png differ diff --git a/images/liquids.png b/images/liquids.png new file mode 100644 index 000000000..da70a058b Binary files /dev/null and b/images/liquids.png differ diff --git a/images/manipulator.png b/images/manipulator.png new file mode 100644 index 000000000..1f452549f Binary files /dev/null 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 new file mode 100644 index 000000000..062d126cd Binary files /dev/null and b/images/mechanisms.png differ diff --git a/images/power-meter.png b/images/power-meter.png new file mode 100644 index 000000000..b1a4b6229 Binary files /dev/null and b/images/power-meter.png differ diff --git a/images/rename-bld.png b/images/rename-bld.png new file mode 100644 index 000000000..0028a87a5 Binary files /dev/null and b/images/rename-bld.png differ diff --git a/images/rename-prof.png b/images/rename-prof.png new file mode 100644 index 000000000..5f2cfc453 Binary files /dev/null and b/images/rename-prof.png differ diff --git a/images/room-list.png b/images/room-list.png new file mode 100644 index 000000000..5a60398f8 Binary files /dev/null and b/images/room-list.png differ diff --git a/images/search.png b/images/search.png new file mode 100644 index 000000000..384c3c533 Binary files /dev/null and b/images/search.png differ diff --git a/images/siege-engine.png b/images/siege-engine.png new file mode 100644 index 000000000..032e265dc Binary files /dev/null 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 new file mode 100644 index 000000000..25b498bca Binary files /dev/null and b/images/workflow-new1.png differ diff --git a/images/workflow-new2.png b/images/workflow-new2.png new file mode 100644 index 000000000..74a4922be Binary files /dev/null and b/images/workflow-new2.png differ diff --git a/images/workflow.png b/images/workflow.png new file mode 100644 index 000000000..a0a0d4216 Binary files /dev/null and b/images/workflow.png differ diff --git a/images/workshop-job-item.png b/images/workshop-job-item.png new file mode 100644 index 000000000..cef691918 Binary files /dev/null and b/images/workshop-job-item.png differ diff --git a/images/workshop-job-material.png b/images/workshop-job-material.png new file mode 100644 index 000000000..d75797766 Binary files /dev/null and b/images/workshop-job-material.png differ diff --git a/images/workshop-job.png b/images/workshop-job.png new file mode 100644 index 000000000..938b84374 Binary files /dev/null and b/images/workshop-job.png differ diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index b141e9fa5..f67b6fe44 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -167,7 +167,7 @@ IF(UNIX) IF(BUILD_EGGY) LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX_EGGY}) ELSEIF(APPLE) - LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN}) + LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_DARWIN}) ELSE() LIST(APPEND PROJECT_SOURCES ${MAIN_SOURCES_LINUX}) ENDIF() @@ -235,7 +235,7 @@ ENDIF() IF(UNIX) SET(PROJECT_LIBS rt dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) IF(APPLE) - SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) + SET(PROJECT_LIBS dl dfhack-md5 dfhack-tinyxml dfhack-tinythread) ENDIF() ELSE(WIN32) #FIXME: do we really need psapi? @@ -273,14 +273,14 @@ ENDIF() SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) IF(APPLE) - SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) - SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) - SET(ZIP_LIBRARY /usr/lib/libz.dylib) - TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) - TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY}) - TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY}) - SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0) - SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) + SET(SDL_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/SDL.framework) + SET(CXX_LIBRARY ${CMAKE_INSTALL_PREFIX}/libs/libstdc++.6.dylib) + SET(ZIP_LIBRARY /usr/lib/libz.dylib) + TARGET_LINK_LIBRARIES(dfhack ${SDL_LIBRARY}) + TARGET_LINK_LIBRARIES(dfhack ${CXX_LIBRARY}) + TARGET_LINK_LIBRARIES(dfhack ${ZIP_LIBRARY}) + SET_TARGET_PROPERTIES(dfhack PROPERTIES VERSION 1.0.0) + SET_TARGET_PROPERTIES(dfhack PROPERTIES SOVERSION 1.0.0) ENDIF() TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket lua ${PROJECT_LIBS}) @@ -290,20 +290,20 @@ TARGET_LINK_LIBRARIES(dfhack-client protobuf-lite clsocket) TARGET_LINK_LIBRARIES(dfhack-run dfhack-client) if(APPLE) - add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...") + add_custom_command(TARGET dfhack-run COMMAND ${dfhack_SOURCE_DIR}/package/darwin/fix-libs.sh WORKING_DIRECTORY ../ COMMENT "Fixing library dependencies...") endif() IF(UNIX) - if (APPLE) - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack - DESTINATION .) - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run - DESTINATION .) + if (APPLE) + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack + DESTINATION .) + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/darwin/dfhack-run + DESTINATION .) else() - # On linux, copy our version of the df launch script which sets LD_PRELOAD - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack + # On linux, copy our version of the df launch script which sets LD_PRELOAD + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack DESTINATION .) - install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run + install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run DESTINATION .) endif() ELSE() diff --git a/library/Core.cpp b/library/Core.cpp index 26c0acbb0..fd96d5601 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -375,7 +375,7 @@ command_result Core::runCommand(color_ostream &con, const std::string &first, ve " reload PLUGIN|all - Reload a plugin or all loaded plugins.\n" ); - con.print("\nDFHack version " DFHACK_VERSION ".\n"); + con.print("\nDFHack version " DFHACK_VERSION ".\n"); } else if (parts.size() == 1) { @@ -747,13 +747,13 @@ void fIOthread(void * iodata) else if(ret) { // a proper, non-empty command was entered - fprintf(stderr,"Adding command to history\n"); + fprintf(stderr,"Adding command to history\n"); main_history.add(command); - fprintf(stderr,"Saving history\n"); + fprintf(stderr,"Saving history\n"); main_history.save("dfhack.history"); } - fprintf(stderr,"Running command\n"); + fprintf(stderr,"Running command\n"); auto rv = core->runCommand(con, command); diff --git a/library/Hooks-windows.cpp b/library/Hooks-windows.cpp index 1e6679855..c8bd22dda 100644 --- a/library/Hooks-windows.cpp +++ b/library/Hooks-windows.cpp @@ -271,39 +271,39 @@ DFhackCExport vPtr SDL_SetVideoMode(int width, int height, int bpp, uint32_t fla static int (*_SDL_UpperBlit)(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) = 0; DFhackCExport int SDL_UpperBlit(DFHack::DFSDL_Surface* src, DFHack::DFSDL_Rect* srcrect, DFHack::DFSDL_Surface* dst, DFHack::DFSDL_Rect* dstrect) { - if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) - { - DFHack::Core & c = DFHack::Core::getInstance(); - DFHack::Graphic* g = c.getGraphic(); - DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h); - - if ( ov != NULL ) - { - if ( ov->paintOver ) - { - _SDL_UpperBlit(src, srcrect, dst, dstrect); - } - - DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect; - dstrect2->x = dstrect->x; - dstrect2->y = dstrect->y; - dstrect2->w = dstrect->w; - dstrect2->h = dstrect->h; - - if ( ov->dstResize != NULL ) - { - DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize; - dstrect2->x += r->x; - dstrect2->y += r->y; - dstrect2->w += r->w; - dstrect2->h += r->h; - } - - int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2); - delete dstrect2; - return result; - } - } + if ( dstrect != NULL && dstrect->h != 0 && dstrect->w != 0 ) + { + DFHack::Core & c = DFHack::Core::getInstance(); + DFHack::Graphic* g = c.getGraphic(); + DFHack::DFTileSurface* ov = g->Call(dstrect->x/dstrect->w, dstrect->y/dstrect->h); + + if ( ov != NULL ) + { + if ( ov->paintOver ) + { + _SDL_UpperBlit(src, srcrect, dst, dstrect); + } + + DFHack::DFSDL_Rect* dstrect2 = new DFHack::DFSDL_Rect; + dstrect2->x = dstrect->x; + dstrect2->y = dstrect->y; + dstrect2->w = dstrect->w; + dstrect2->h = dstrect->h; + + if ( ov->dstResize != NULL ) + { + DFHack::DFSDL_Rect* r = (DFHack::DFSDL_Rect*)ov->dstResize; + dstrect2->x += r->x; + dstrect2->y += r->y; + dstrect2->w += r->w; + dstrect2->h += r->h; + } + + int result = _SDL_UpperBlit(ov->surface, ov->rect, dst, dstrect2); + delete dstrect2; + return result; + } + } return _SDL_UpperBlit(src, srcrect, dst, dstrect); } diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index b186316a8..a4ebbea5b 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1180,6 +1180,7 @@ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getAge), WRAPM(Units, getNominalSkill), WRAPM(Units, getEffectiveSkill), + WRAPM(Units, getExperience), WRAPM(Units, computeMovementSpeed), WRAPM(Units, getProfessionName), WRAPM(Units, getCasteProfessionName), diff --git a/library/MacPool.mm b/library/MacPool.mm index 2453aca29..8f26543e9 100644 --- a/library/MacPool.mm +++ b/library/MacPool.mm @@ -9,14 +9,14 @@ NSAutoreleasePool *thePool; int create_pool() { - fprintf(stderr,"Creating autorelease pool\n"); - thePool = [[NSAutoreleasePool alloc] init]; - return 1; + fprintf(stderr,"Creating autorelease pool\n"); + thePool = [[NSAutoreleasePool alloc] init]; + return 1; } int destroy_pool() { - fprintf(stderr,"Draining and releasing autorelease pool\n"); - [thePool drain]; - [thePool release]; - return 0; + fprintf(stderr,"Draining and releasing autorelease pool\n"); + [thePool drain]; + [thePool release]; + return 0; } \ No newline at end of file diff --git a/library/Process-darwin.cpp b/library/Process-darwin.cpp index d081c8c5c..2e2be277b 100644 --- a/library/Process-darwin.cpp +++ b/library/Process-darwin.cpp @@ -50,13 +50,13 @@ using namespace DFHack; Process::Process(VersionInfoFactory * known_versions) { int target_result; - + char path[1024]; char *real_path; - uint32_t size = sizeof(path); - if (_NSGetExecutablePath(path, &size) == 0) { - real_path = realpath(path, NULL); - } + uint32_t size = sizeof(path); + if (_NSGetExecutablePath(path, &size) == 0) { + real_path = realpath(path, NULL); + } identified = false; my_descriptor = 0; @@ -166,29 +166,29 @@ void Process::getMemRanges( vector & ranges ) (vm_region_info_t)&info, &info_count, &object); if (kr == KERN_SUCCESS) { if (info.reserved==1) { - address += vmsize; - continue; + address += vmsize; + continue; } Dl_info dlinfo; int dlcheck; dlcheck = dladdr((const void*)address, &dlinfo); if (dlcheck==0) { - dlinfo.dli_fname = ""; + dlinfo.dli_fname = ""; } - + t_memrange temp; - strncpy( temp.name, dlinfo.dli_fname, 1023 ); - temp.name[1023] = 0; - temp.start = (void *) address; - temp.end = (void *) (address+vmsize); - temp.read = (info.protection & VM_PROT_READ); - temp.write = (info.protection & VM_PROT_WRITE); - temp.execute = (info.protection & VM_PROT_EXECUTE); - temp.shared = info.shared; - temp.valid = true; - ranges.push_back(temp); - - fprintf(stderr, + strncpy( temp.name, dlinfo.dli_fname, 1023 ); + temp.name[1023] = 0; + temp.start = (void *) address; + temp.end = (void *) (address+vmsize); + temp.read = (info.protection & VM_PROT_READ); + temp.write = (info.protection & VM_PROT_WRITE); + temp.execute = (info.protection & VM_PROT_EXECUTE); + temp.shared = info.shared; + temp.valid = true; + ranges.push_back(temp); + + fprintf(stderr, "%08x-%08x %8uK %c%c%c/%c%c%c %11s %6s %10s uwir=%hu sub=%u dlname: %s\n", address, (address + vmsize), (vmsize >> 10), (info.protection & VM_PROT_READ) ? 'r' : '-', @@ -203,7 +203,7 @@ void Process::getMemRanges( vector & ranges ) info.user_wired_count, info.reserved, dlinfo.dli_fname); - + address += vmsize; } else if (kr != KERN_INVALID_ADDRESS) { @@ -277,15 +277,15 @@ uint32_t Process::getTickCount() string Process::getPath() { - char path[1024]; + char path[1024]; char *real_path; - uint32_t size = sizeof(path); - if (_NSGetExecutablePath(path, &size) == 0) { - real_path = realpath(path, NULL); - } - std::string path_string(real_path); - int last_slash = path_string.find_last_of("/"); - std::string directory = path_string.substr(0,last_slash); + uint32_t size = sizeof(path); + if (_NSGetExecutablePath(path, &size) == 0) { + real_path = realpath(path, NULL); + } + std::string path_string(real_path); + int last_slash = path_string.find_last_of("/"); + std::string directory = path_string.substr(0,last_slash); return directory; } @@ -304,4 +304,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); return result==0; -} \ No newline at end of file +} + +// returns -1 on error +void* Process::memAlloc(const int length) +{ + return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANON, -1, 0); +} + +int Process::memDealloc(const void *ptr, const int length) +{ + return munmap(ptr, length); +} + +int Process::memProtect(const void *ptr, const int length, const int prot) +{ + int prot_native = 0; + + if (prot & Process::MemProt::READ) + prot_native |= PROT_READ; + if (prot & Process::MemProt::WRITE) + prot_native |= PROT_WRITE; + if (prot & Process::MemProt::EXEC) + prot_native |= PROT_EXEC; + + return mprotect(ptr, length, prot_native); +} diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 046b7696d..c3995a2aa 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -235,4 +235,29 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) result=mprotect((void *)range.start, (size_t)range.end-(size_t)range.start,protect); return result==0; -} \ No newline at end of file +} + +// returns -1 on error +void* Process::memAlloc(const int length) +{ + return mmap(0, length, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0); +} + +int Process::memDealloc(void *ptr, const int length) +{ + return munmap(ptr, length); +} + +int Process::memProtect(void *ptr, const int length, const int prot) +{ + int prot_native = 0; + + if (prot & Process::MemProt::READ) + prot_native |= PROT_READ; + if (prot & Process::MemProt::WRITE) + prot_native |= PROT_WRITE; + if (prot & Process::MemProt::EXEC) + prot_native |= PROT_EXEC; + + return mprotect(ptr, length, prot_native); +} diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 6f79236f9..fdef9e225 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -473,3 +473,42 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) return result; } + +void* Process::memAlloc(const int length) +{ + void *ret; + // returns 0 on error + ret = VirtualAlloc(0, length, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE); + if (!ret) + ret = (void*)-1; + return ret; +} + +int Process::memDealloc(void *ptr, const int length) +{ + // can only free the whole region at once + // vfree returns 0 on error + return !VirtualFree(ptr, 0, MEM_RELEASE); +} + +int Process::memProtect(void *ptr, const int length, const int prot) +{ + int prot_native = 0; + DWORD old_prot = 0; + + // only support a few constant combinations + if (prot == 0) + prot_native = PAGE_NOACCESS; + else if (prot == Process::MemProt::READ) + prot_native = PAGE_READONLY; + else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE)) + prot_native = PAGE_READWRITE; + else if (prot == (Process::MemProt::READ | Process::MemProt::WRITE | Process::MemProt::EXEC)) + prot_native = PAGE_EXECUTE_READWRITE; + else if (prot == (Process::MemProt::READ | Process::MemProt::EXEC)) + prot_native = PAGE_EXECUTE_READ; + else + return -1; + + return !VirtualProtect(ptr, length, prot_native, &old_prot); +} diff --git a/library/include/Console.h b/library/include/Console.h index 8d848135a..2f2f68cfa 100644 --- a/library/include/Console.h +++ b/library/include/Console.h @@ -65,20 +65,20 @@ namespace DFHack bool save (const char * filename) { std::ofstream outfile (filename); - //fprintf(stderr,"Save: Initialized stream\n"); + //fprintf(stderr,"Save: Initialized stream\n"); if(outfile.bad()) return false; - //fprintf(stderr,"Save: Iterating...\n"); + //fprintf(stderr,"Save: Iterating...\n"); for(auto iter = history.begin();iter < history.end(); iter++) { - //fprintf(stderr,"Save: Dumping %s\n",(*iter).c_str()); + //fprintf(stderr,"Save: Dumping %s\n",(*iter).c_str()); outfile << *iter << std::endl; - //fprintf(stderr,"Save: Flushing\n"); - outfile.flush(); + //fprintf(stderr,"Save: Flushing\n"); + outfile.flush(); } - //fprintf(stderr,"Save: Closing\n"); + //fprintf(stderr,"Save: Closing\n"); outfile.close(); - //fprintf(stderr,"Save: Done\n"); + //fprintf(stderr,"Save: Done\n"); return true; } /// add a command to the history diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 32ffabc32..3591be045 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -35,7 +35,7 @@ distribution. // Stop some MS stupidity #ifdef interface - #undef interface + #undef interface #endif typedef struct lua_State lua_State; diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 22f15eecf..31647a25e 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -291,6 +291,27 @@ namespace DFHack /// write a possibly read-only memory area bool patchMemory(void *target, const void* src, size_t count); + + /// allocate new memory pages for code or stuff + /// returns -1 on error (0 is a valid address) + void* memAlloc(const int length); + + /// free memory pages from memAlloc + /// should have length = alloced length for portability + /// returns 0 on success + int memDealloc(void *ptr, const int length); + + /// change memory page permissions + /// prot is a bitwise OR of the MemProt enum + /// returns 0 on success + int memProtect(void *ptr, const int length, const int prot); + + enum MemProt { + READ = 1, + WRITE = 2, + EXEC = 4 + }; + private: VersionInfo * my_descriptor; PlatformSpecific *d; diff --git a/library/include/TileTypes.h b/library/include/TileTypes.h index 48c10146a..f46cf2f2c 100644 --- a/library/include/TileTypes.h +++ b/library/include/TileTypes.h @@ -41,7 +41,7 @@ namespace DFHack struct { //Maybe should add 'up' and 'down' for Z-levels? - unsigned char north,south,west,east; + unsigned char north,south,west,east; }; inline TileDirection() diff --git a/library/include/Types.h b/library/include/Types.h index 98dbb7e74..5412b443b 100644 --- a/library/include/Types.h +++ b/library/include/Types.h @@ -74,6 +74,28 @@ namespace DFHack uint32_t xpNxtLvl; }; + typedef std::pair rect2d; + + inline rect2d intersect(rect2d a, rect2d b) { + df::coord2d g1 = a.first, g2 = a.second; + df::coord2d c1 = b.first, c2 = b.second; + df::coord2d rc1 = df::coord2d(std::max(g1.x, c1.x), std::max(g1.y, c1.y)); + df::coord2d rc2 = df::coord2d(std::min(g2.x, c2.x), std::min(g2.y, c2.y)); + return rect2d(rc1, rc2); + } + + inline rect2d mkrect_xy(int x1, int y1, int x2, int y2) { + return rect2d(df::coord2d(x1, y1), df::coord2d(x2, y2)); + } + + inline rect2d mkrect_wh(int x, int y, int w, int h) { + return rect2d(df::coord2d(x, y), df::coord2d(x+w-1, y+h-1)); + } + + inline df::coord2d rect_size(const rect2d &rect) { + return rect.second - rect.first + df::coord2d(1,1); + } + DFHACK_EXPORT int getdir(std::string dir, std::vector &files); DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending); diff --git a/library/include/VersionInfo.h b/library/include/VersionInfo.h index 2ea4170fa..26aee19d1 100644 --- a/library/include/VersionInfo.h +++ b/library/include/VersionInfo.h @@ -27,7 +27,7 @@ distribution. #include "Pragma.h" #include "Export.h" -#include "Types.h" +/* #include "Types.h" */ #include #include #include diff --git a/library/include/modules/Graphic.h b/library/include/modules/Graphic.h index a4b2dad10..8ee9c33b0 100644 --- a/library/include/modules/Graphic.h +++ b/library/include/modules/Graphic.h @@ -36,55 +36,55 @@ distribution. namespace DFHack { - // SDL stuff - typedef signed short SINT16; - typedef struct - { - int16_t x, y; - uint16_t w, h; - } DFSDL_Rect; - typedef struct - { - uint32_t flags; - void* format; // PixelFormat* - int w, h; - int pitch; - void* pixels; - void* userdata; // as far as i could see DF doesnt use this - int locked; - void* lock_data; - DFSDL_Rect clip_rect; - void* map; - int refcount; - } DFSDL_Surface; + // SDL stuff + typedef signed short SINT16; + typedef struct + { + int16_t x, y; + uint16_t w, h; + } DFSDL_Rect; + typedef struct + { + uint32_t flags; + void* format; // PixelFormat* + int w, h; + int pitch; + void* pixels; + void* userdata; // as far as i could see DF doesnt use this + int locked; + void* lock_data; + DFSDL_Rect clip_rect; + void* map; + int refcount; + } DFSDL_Surface; - // ========= - struct DFTileSurface - { - bool paintOver; // draw over original tile? - DFSDL_Surface* surface; // from where it should be drawn - DFSDL_Rect* rect; // from which coords (NULL to draw whole surface) - DFSDL_Rect* dstResize; // if not NULL dst rect will be resized (x/y/w/h will be added to original dst) - }; + // ========= + struct DFTileSurface + { + bool paintOver; // draw over original tile? + DFSDL_Surface* surface; // from where it should be drawn + DFSDL_Rect* rect; // from which coords (NULL to draw whole surface) + DFSDL_Rect* dstResize; // if not NULL dst rect will be resized (x/y/w/h will be added to original dst) + }; - class DFHACK_EXPORT Graphic : public Module - { - public: - Graphic(); - ~Graphic(); - bool Finish() - { - return true; - } - bool Register(DFTileSurface* (*func)(int,int)); - bool Unregister(DFTileSurface* (*func)(int,int)); - DFTileSurface* Call(int x, int y); + class DFHACK_EXPORT Graphic : public Module + { + public: + Graphic(); + ~Graphic(); + bool Finish() + { + return true; + } + bool Register(DFTileSurface* (*func)(int,int)); + bool Unregister(DFTileSurface* (*func)(int,int)); + DFTileSurface* Call(int x, int y); - private: - struct Private; - Private *d; - }; + private: + struct Private; + Private *d; + }; } diff --git a/library/include/modules/Gui.h b/library/include/modules/Gui.h index 147f27883..794cb89ff 100644 --- a/library/include/modules/Gui.h +++ b/library/include/modules/Gui.h @@ -29,6 +29,8 @@ distribution. #include "ColorText.h" #include +#include "Types.h" + #include "DataDefs.h" #include "df/init.h" #include "df/ui.h" @@ -116,6 +118,9 @@ namespace DFHack int map_x1, map_x2, menu_x1, menu_x2, area_x1, area_x2; int y1, y2; bool menu_on, area_on, menu_forced; + + rect2d map() { return mkrect_xy(map_x1, y1, map_x2, y2); } + rect2d menu() { return mkrect_xy(menu_x1, y1, menu_x2, y2); } }; DFHACK_EXPORT DwarfmodeDims getDwarfmodeViewDims(); diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index c1c478bc6..2a6aebf73 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -111,8 +111,8 @@ public: { if (!basemats) init_tiles(true); return t_matpair( - index_tile(basemats->mattype,p), - index_tile(basemats->matindex,p) + index_tile(basemats->mat_type,p), + index_tile(basemats->mat_index,p) ); } bool isVeinAt(df::coord2d p) @@ -151,8 +151,8 @@ public: if (!basemats) init_tiles(true); if (tiles->con_info) return t_matpair( - index_tile(tiles->con_info->mattype,p), - index_tile(tiles->con_info->matindex,p) + index_tile(tiles->con_info->mat_type,p), + index_tile(tiles->con_info->mat_index,p) ); return baseMaterialAt(p); } @@ -284,8 +284,8 @@ private: struct ConInfo { df::tile_bitmask constructed; df::tiletype tiles[16][16]; - t_blockmaterials mattype; - t_blockmaterials matindex; + t_blockmaterials mat_type; + t_blockmaterials mat_index; }; struct TileInfo { df::tile_bitmask frozen; @@ -304,8 +304,8 @@ private: }; struct BasematInfo { df::tile_bitmask dirty; - t_blockmaterials mattype; - t_blockmaterials matindex; + t_blockmaterials mat_type; + t_blockmaterials mat_index; t_blockmaterials layermat; BasematInfo(); diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index e44ca89ed..86814898f 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -338,10 +338,10 @@ namespace DFHack */ struct t_material { - t_itemType itemType; - t_itemSubtype subType; - t_materialType material; - t_materialIndex index; + t_itemType item_type; + t_itemSubtype item_subtype; + t_materialType mat_type; + t_materialIndex mat_index; uint32_t flags; }; /** diff --git a/library/include/modules/Screen.h b/library/include/modules/Screen.h index d8b5774e9..bfc393734 100644 --- a/library/include/modules/Screen.h +++ b/library/include/modules/Screen.h @@ -27,7 +27,10 @@ distribution. #include "Module.h" #include "BitArray.h" #include "ColorText.h" +#include "Types.h" + #include +#include #include "DataDefs.h" #include "df/graphic.h" @@ -50,6 +53,8 @@ namespace DFHack { class Core; + typedef std::set interface_key_set; + /** * The Screen module * \ingroup grp_modules @@ -94,11 +99,67 @@ namespace DFHack : ch(ch), fg(fg), bg(bg), bold(bold), tile(tile), tile_mode(TileColor), tile_fg(tile_fg), tile_bg(tile_bg) {} + + void adjust(int8_t nfg) { fg = nfg&7; bold = !!(nfg&8); } + void adjust(int8_t nfg, bool nbold) { fg = nfg; bold = nbold; } + void adjust(int8_t nfg, int8_t nbg) { adjust(nfg); bg = nbg; } + void adjust(int8_t nfg, bool nbold, int8_t nbg) { adjust(nfg, nbold); bg = nbg; } + + Pen color(int8_t nfg) const { Pen cp(*this); cp.adjust(nfg); return cp; } + Pen color(int8_t nfg, bool nbold) const { Pen cp(*this); cp.adjust(nfg, nbold); return cp; } + Pen color(int8_t nfg, int8_t nbg) const { Pen cp(*this); cp.adjust(nfg, nbg); return cp; } + Pen color(int8_t nfg, bool nbold, int8_t nbg) const { Pen cp(*this); cp.adjust(nfg, nbold, nbg); return cp; } + + Pen chtile(char ch) { Pen cp(*this); cp.ch = ch; return cp; } + Pen chtile(char ch, int tile) { Pen cp(*this); cp.ch = ch; cp.tile = tile; return cp; } + }; + + struct DFHACK_EXPORT ViewRect { + rect2d view, clip; + + ViewRect(rect2d area) : view(area), clip(area) {} + ViewRect(rect2d area, rect2d clip) : view(area), clip(clip) {} + + bool isDefunct() const { + return clip.first.x > clip.second.x || clip.first.y > clip.second.y; + } + int width() const { return view.second.x-view.first.x+1; } + int height() const { return view.second.y-view.first.y+1; } + df::coord2d local(df::coord2d pos) const { + return df::coord2d(pos.x - view.first.x, pos.y - view.first.y); + } + df::coord2d global(df::coord2d pos) const { + return df::coord2d(pos.x + view.first.x, pos.y + view.first.y); + } + df::coord2d global(int x, int y) const { + return df::coord2d(x + view.first.x, y + view.first.y); + } + bool inClipGlobal(int x, int y) const { + return x >= clip.first.x && x <= clip.second.x && + y >= clip.first.y && y <= clip.second.y; + } + bool inClipGlobal(df::coord2d pos) const { + return inClipGlobal(pos.x, pos.y); + } + bool inClipLocal(int x, int y) const { + return inClipGlobal(x + view.first.x, y + view.first.y); + } + bool inClipLocal(df::coord2d pos) const { + return inClipLocal(pos.x, pos.y); + } + ViewRect viewport(rect2d area) const { + rect2d nview(global(area.first), global(area.second)); + return ViewRect(nview, intersect(nview, clip)); + } }; DFHACK_EXPORT df::coord2d getMousePos(); DFHACK_EXPORT df::coord2d getWindowSize(); + inline rect2d getScreenRect() { + return rect2d(df::coord2d(0,0), getWindowSize()-df::coord2d(1,1)); + } + /// Returns the state of [GRAPHICS:YES/NO] DFHACK_EXPORT bool inGraphicsMode(); @@ -133,6 +194,77 @@ namespace DFHack /// Retrieve the string representation of the bound key. DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key); + + /// A painter class that implements a clipping area and cursor/pen state + struct DFHACK_EXPORT Painter : ViewRect { + df::coord2d gcursor; + Pen cur_pen, cur_key_pen; + + static const Pen default_pen; + static const Pen default_key_pen; + + Painter(const ViewRect &area, const Pen &pen = default_pen, const Pen &kpen = default_key_pen) + : ViewRect(area), gcursor(area.view.first), cur_pen(pen), cur_key_pen(kpen) + {} + + df::coord2d cursor() const { return local(gcursor); } + int cursorX() const { return gcursor.x - view.first.x; } + int cursorY() const { return gcursor.y - view.first.y; } + + bool isValidPos() const { return inClipGlobal(gcursor); } + + Painter viewport(rect2d area) const { + return Painter(ViewRect::viewport(area), cur_pen, cur_key_pen); + } + + Painter &seek(df::coord2d pos) { gcursor = global(pos); return *this; } + Painter &seek(int x, int y) { gcursor = global(x,y); return *this; } + Painter &advance(int dx) { gcursor.x += dx; return *this; } + Painter &advance(int dx, int dy) { gcursor.x += dx; gcursor.y += dy; return *this; } + Painter &newline(int dx = 0) { gcursor.y++; gcursor.x = view.first.x + dx; return *this; } + + const Pen &pen() const { return cur_pen; } + Painter &pen(const Pen &np) { cur_pen = np; return *this; } + Painter &pen(int8_t fg) { cur_pen.adjust(fg); return *this; } + + const Pen &key_pen() const { return cur_key_pen; } + Painter &key_pen(const Pen &np) { cur_key_pen = np; return *this; } + Painter &key_pen(int8_t fg) { cur_key_pen.adjust(fg); return *this; } + + Painter &clear() { + fillRect(Pen(' ',0,0,false), clip.first.x, clip.first.y, clip.second.x, clip.second.y); + return *this; + } + + Painter &fill(const rect2d &area, const Pen &pen) { + rect2d irect = intersect(area, clip); + fillRect(pen, irect.first.x, irect.first.y, irect.second.x, irect.second.y); + return *this; + } + Painter &fill(const rect2d &area) { return fill(area, cur_pen); } + + Painter &tile(const Pen &pen) { + if (isValidPos()) paintTile(pen, gcursor.x, gcursor.y); + return advance(1); + } + Painter &tile() { return tile(cur_pen); } + Painter &tile(char ch) { return tile(cur_pen.chtile(ch)); } + Painter &tile(char ch, int tileid) { return tile(cur_pen.chtile(ch, tileid)); } + + Painter &string(const std::string &str, const Pen &pen) { + do_paint_string(str, pen); return advance(str.size()); + } + Painter &string(const std::string &str) { return string(str, cur_pen); } + Painter &string(const std::string &str, int8_t fg) { return string(str, cur_pen.color(fg)); } + + Painter &key(df::interface_key kc, const Pen &pen) { + return string(getKeyDisplay(kc), pen); + } + Painter &key(df::interface_key kc) { return key(kc, cur_key_pen); } + + private: + void do_paint_string(const std::string &str, const Pen &pen); + }; } class DFHACK_EXPORT dfhack_viewscreen : public df::viewscreen { diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index 93e11afb1..c2eb7ca18 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -238,6 +238,8 @@ DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); DFHACK_EXPORT int getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust = false); DFHACK_EXPORT int getEffectiveSkill(df::unit *unit, df::job_skill skill_id); +DFHACK_EXPORT int getExperience(df::unit *unit, df::job_skill skill_id, bool total = false); + DFHACK_EXPORT int computeMovementSpeed(df::unit *unit); struct NoblePosition { 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..2145cfad1 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -10,13 +10,19 @@ local to_pen = dfhack.pen.parse CLEAR_PEN = to_pen{ch=32,fg=0,bg=0} +local FAKE_INPUT_KEYS = { + _MOUSE_L = true, + _MOUSE_R = true, + _STRING = true, +} + function simulateInput(screen,...) local keys = {} local function push_key(arg) local kv = arg if type(arg) == 'string' then kv = df.interface_key[arg] - if kv == nil then + if kv == nil and not FAKE_INPUT_KEYS[arg] then error('Invalid keycode: '..arg) end end diff --git a/library/lua/gui/materials.lua b/library/lua/gui/materials.lua index 871c60014..f29f530a5 100644 --- a/library/lua/gui/materials.lua +++ b/library/lua/gui/materials.lua @@ -23,6 +23,7 @@ MaterialDialog.ATTRS{ frame_title = 'Select Material', -- new attrs none_caption = 'none', + hide_none = false, use_inorganic = true, use_creature = true, use_plant = true, @@ -68,7 +69,7 @@ function MaterialDialog:init(info) end function MaterialDialog:getWantedFrameSize(rect) - return math.max(40, #self.prompt), math.min(28, rect.height-8) + return math.max(self.frame_width or 40, #self.prompt), math.min(28, rect.height-8) end function MaterialDialog:onDestroy() @@ -78,9 +79,10 @@ function MaterialDialog:onDestroy() end function MaterialDialog:initBuiltinMode() - local choices = { - { text = self.none_caption, mat_type = -1, mat_index = -1 }, - } + local choices = {} + if not self.hide_none then + table.insert(choices, { text = self.none_caption, mat_type = -1, mat_index = -1 }) + end if self.use_inorganic then table.insert(choices, { @@ -281,9 +283,15 @@ function ItemTypeDialog(args) args.with_filter = true args.icon_width = 2 - local choices = { { - icon = '?', text = args.none_caption or 'none', item_type = -1, item_subtype = -1 - } } + local choices = {} + + if not args.hide_none then + table.insert(choices, { + icon = '?', text = args.none_caption or 'none', + item_type = -1, item_subtype = -1 + }) + end + local filter = args.item_filter for itype = 0,df.item_type._last_item do diff --git a/library/lua/gui/widgets.lua b/library/lua/gui/widgets.lua index ad408a2ea..67090e114 100644 --- a/library/lua/gui/widgets.lua +++ b/library/lua/gui/widgets.lua @@ -488,6 +488,18 @@ function List:onRenderBody(dc) local iend = math.min(#choices, top+self.page_size-1) local iw = self.icon_width + local function paint_icon(icon, obj) + if type(icon) ~= 'string' then + dc:char(nil,icon) + else + if current then + dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen) + else + dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen) + end + end + end + for i = top,iend do local obj = choices[i] local current = (i == self.selected) @@ -499,30 +511,28 @@ function List:onRenderBody(dc) end local y = (i - top)*self.row_height + local icon = getval(obj.icon) - if iw and obj.icon then - local icon = getval(obj.icon) - if icon then - dc:seek(0, y) - if type(icon) ~= 'string' then - dc:char(nil,icon) - else - if current then - dc:string(icon, obj.icon_pen or self.icon_pen or cur_pen) - else - dc:string(icon, obj.icon_pen or self.icon_pen or cur_dpen) - end - end - end + if iw and icon then + dc:seek(0, y) + paint_icon(icon, obj) end render_text(obj, dc, iw or 0, y, cur_pen, cur_dpen, not current) + local ip = dc.width + if obj.key then local keystr = gui.getKeyDisplay(obj.key) - dc:seek(dc.width-2-#keystr,y):pen(self.text_pen) + ip = ip-2-#keystr + dc:seek(ip,y):pen(self.text_pen) dc:string('('):string(keystr,COLOR_LIGHTGREEN):string(')') end + + if icon and not iw then + dc:seek(ip-1,y) + paint_icon(icon, obj) + end end end diff --git a/library/modules/Graphic.cpp b/library/modules/Graphic.cpp index 4188df2b8..e965bc7e1 100644 --- a/library/modules/Graphic.cpp +++ b/library/modules/Graphic.cpp @@ -48,63 +48,63 @@ Module* DFHack::createGraphic() struct Graphic::Private { - bool Started; - vector funcs; + bool Started; + vector funcs; }; Graphic::Graphic() { - d = new Private; + d = new Private; - d->Started = true; + d->Started = true; } Graphic::~Graphic() { - delete d; + delete d; } bool Graphic::Register(DFTileSurface* (*func)(int,int)) { - d->funcs.push_back(func); - return true; + d->funcs.push_back(func); + return true; } bool Graphic::Unregister(DFTileSurface* (*func)(int,int)) { - if ( d->funcs.empty() ) return false; - - vector::iterator it = d->funcs.begin(); - while ( it != d->funcs.end() ) - { - if ( *it == func ) - { - d->funcs.erase(it); - return true; - } - it++; - } - - return false; + if ( d->funcs.empty() ) return false; + + vector::iterator it = d->funcs.begin(); + while ( it != d->funcs.end() ) + { + if ( *it == func ) + { + d->funcs.erase(it); + return true; + } + it++; + } + + return false; } // This will return first DFTileSurface it can get (or NULL if theres none) DFTileSurface* Graphic::Call(int x, int y) { - if ( d->funcs.empty() ) return NULL; - - DFTileSurface* temp = NULL; - - vector::iterator it = d->funcs.begin(); - while ( it != d->funcs.end() ) - { - temp = (*it)(x,y); - if ( temp != NULL ) - { - return temp; - } - it++; - } - - return NULL; + if ( d->funcs.empty() ) return NULL; + + DFTileSurface* temp = NULL; + + vector::iterator it = d->funcs.begin(); + while ( it != d->funcs.end() ) + { + temp = (*it)(x,y); + if ( temp != NULL ) + { + return temp; + } + it++; + } + + return NULL; } \ No newline at end of file diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 91448ab3f..ec01b3f61 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -495,10 +495,10 @@ bool Items::copyItem(df::item * itembase, DFHack::dfh_item &item) item.id = itreal->id; item.age = itreal->age; item.flags = itreal->flags; - item.matdesc.itemType = itreal->getType(); - item.matdesc.subType = itreal->getSubtype(); - item.matdesc.material = itreal->getMaterial(); - item.matdesc.index = itreal->getMaterialIndex(); + item.matdesc.item_type = itreal->getType(); + item.matdesc.item_subtype = itreal->getSubtype(); + item.matdesc.mat_type = itreal->getMaterial(); + item.matdesc.mat_index = itreal->getMaterialIndex(); item.wear_level = itreal->getWear(); item.quality = itreal->getQuality(); item.quantity = itreal->getStackSize(); diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 482b950ba..363de8064 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -650,15 +650,15 @@ void MapExtras::Block::TileInfo::init_coninfo() con_info = new ConInfo(); con_info->constructed.clear(); COPY(con_info->tiles, base_tiles); - memset(con_info->mattype, -1, sizeof(con_info->mattype)); - memset(con_info->matindex, -1, sizeof(con_info->matindex)); + memset(con_info->mat_type, -1, sizeof(con_info->mat_type)); + memset(con_info->mat_index, -1, sizeof(con_info->mat_index)); } MapExtras::Block::BasematInfo::BasematInfo() { dirty.clear(); - memset(mattype,0,sizeof(mattype)); - memset(matindex,-1,sizeof(matindex)); + memset(mat_type,0,sizeof(mat_type)); + memset(mat_index,-1,sizeof(mat_index)); memset(layermat,-1,sizeof(layermat)); } @@ -719,8 +719,8 @@ void MapExtras::Block::ParseTiles(TileInfo *tiles) is_con = true; tiles->con_info->constructed.setassignment(x,y,true); tiles->con_info->tiles[x][y] = tt; - tiles->con_info->mattype[x][y] = con->mat_type; - tiles->con_info->matindex[x][y] = con->mat_index; + tiles->con_info->mat_type[x][y] = con->mat_type; + tiles->con_info->mat_index[x][y] = con->mat_index; tt = con->original_tile; } @@ -753,14 +753,14 @@ void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats) auto tt = tiles->base_tiles[x][y]; auto mat = info.getBaseMaterial(tt, df::coord2d(x,y)); - bmats->mattype[x][y] = mat.mat_type; - bmats->matindex[x][y] = mat.mat_index; + bmats->mat_type[x][y] = mat.mat_type; + bmats->mat_index[x][y] = mat.mat_index; // Copy base info back to construction layer if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y)) { - tiles->con_info->mattype[x][y] = mat.mat_type; - tiles->con_info->matindex[x][y] = mat.mat_index; + tiles->con_info->mat_type[x][y] = mat.mat_type; + tiles->con_info->mat_index[x][y] = mat.mat_index; } } } diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 4da484ade..e15e7e83d 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -840,7 +840,7 @@ bool Materials::ReadAllMaterials(void) std::string Materials::getDescription(const t_material & mat) { - MaterialInfo mi(mat.material, mat.index); + MaterialInfo mi(mat.mat_type, mat.mat_index); if (mi.creature) return mi.creature->creature_id + " " + mi.material->id; else if (mi.plant) @@ -853,7 +853,7 @@ std::string Materials::getDescription(const t_material & mat) // This is completely worthless now std::string Materials::getType(const t_material & mat) { - MaterialInfo mi(mat.material, mat.index); + MaterialInfo mi(mat.mat_type, mat.mat_index); switch (mi.mode) { case MaterialInfo::Builtin: diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index e8d23261b..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() @@ -234,6 +237,21 @@ bool Screen::invalidate() return true; } +const Pen Screen::Painter::default_pen(0,COLOR_GREY,0); +const Pen Screen::Painter::default_key_pen(0,COLOR_LIGHTGREEN,0); + +void Screen::Painter::do_paint_string(const std::string &str, const Pen &pen) +{ + if (gcursor.y < clip.first.y || gcursor.y > clip.second.y) + return; + + int dx = std::max(0, int(clip.first.x - gcursor.x)); + int len = std::min((int)str.size(), int(clip.second.x - gcursor.x + 1)); + + if (len > dx) + paintString(pen, gcursor.x + dx, gcursor.y, str.substr(dx, len-dx)); +} + bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *ptile, int *pgs) { if (!gps || !texture || x < 0 || y < 0) return false; diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index ddbb8cb79..a603226e8 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -405,7 +405,7 @@ bool Creatures::WriteJob(const t_creature * furball, std::vector con for(i=0;iwriteWord(cmats[i] + off.job_material_itemtype_o, mat[i].itemType); - p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].subType); + p->writeWord(cmats[i] + off.job_material_subtype_o, mat[i].itemSubtype); p->writeWord(cmats[i] + off.job_material_subindex_o, mat[i].subIndex); p->writeDWord(cmats[i] + off.job_material_index_o, mat[i].index); p->writeDWord(cmats[i] + off.job_material_flags_o, mat[i].flags); @@ -475,7 +475,7 @@ bool Creatures::ReadJob(const t_creature * furball, vector & mat) for(i=0;ireadWord(cmats[i] + off.job_material_itemtype_o); - mat[i].subType = p->readWord(cmats[i] + off.job_material_subtype_o); + mat[i].itemSubtype = p->readWord(cmats[i] + off.job_material_subtype_o); mat[i].subIndex = p->readWord(cmats[i] + off.job_material_subindex_o); mat[i].index = p->readDWord(cmats[i] + off.job_material_index_o); mat[i].flags = p->readDWord(cmats[i] + off.job_material_flags_o); @@ -909,6 +909,24 @@ int Units::getNominalSkill(df::unit *unit, df::job_skill skill_id, bool use_rust return 0; } +int Units::getExperience(df::unit *unit, df::job_skill skill_id, bool total) +{ + CHECK_NULL_POINTER(unit); + + if (!unit->status.current_soul) + return 0; + + auto skill = binsearch_in_vector(unit->status.current_soul->skills, &df::unit_skill::id, skill_id); + if (!skill) + return 0; + + int xp = skill->experience; + // exact formula used by the game: + if (total && skill->rating > 0) + xp += 500*skill->rating + 100*skill->rating*(skill->rating - 1)/2; + return xp; +} + int Units::getEffectiveSkill(df::unit *unit, df::job_skill skill_id) { /* diff --git a/library/modules/World.cpp b/library/modules/World.cpp index f3283c3a3..9ae4266b2 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -197,11 +197,15 @@ PersistentDataItem World::AddPersistentData(const std::string &key) std::vector &hfvec = df::historical_figure::get_vector(); df::historical_figure *hfig = new df::historical_figure(); - hfig->id = next_persistent_id--; + hfig->id = next_persistent_id; hfig->name.has_name = true; hfig->name.first_name = key; memset(hfig->name.words, 0xFF, sizeof(hfig->name.words)); + if (!hfvec.empty()) + hfig->id = std::min(hfig->id, hfvec[0]->id-1); + next_persistent_id = hfig->id-1; + hfvec.insert(hfvec.begin(), hfig); persistent_index.insert(T_persistent_item(key, -hfig->id)); diff --git a/library/xml b/library/xml index 327a9662b..42e26b368 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 327a9662be81627ebcbb3aea11ffbca3e536b7ee +Subproject commit 42e26b368f48a148aba07fea295c6d19bca3fcbc diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index c8fda425f..b72739228 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 4ee5c0a54..e5047b434 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -964,7 +964,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) else if (building_type::TradeDepot == type) { df::building_tradedepotst* depot = (df::building_tradedepotst*) build; - trader_requested = depot->trade_flags.bits.trader_requested; + trader_requested = trader_requested || depot->trade_flags.bits.trader_requested; if (print_debug) { if (trader_requested) @@ -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/cleanowned.cpp b/plugins/cleanowned.cpp index cd01fd616..28314d3f0 100644 --- a/plugins/cleanowned.cpp +++ b/plugins/cleanowned.cpp @@ -117,7 +117,7 @@ command_result df_cleanowned (color_ostream &out, vector & parameters) else if (item->flags.bits.on_ground) { df::item_type type = item->getType(); - if(type == item_type::MEAT || + if(type == item_type::MEAT || type == item_type::FISH || type == item_type::VERMIN || type == item_type::PET || diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index 29b8403f8..a43e6a7bf 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -17,16 +17,16 @@ static tthread::mutex* mymutex=0; struct memory_data { - void * addr; - size_t len; - size_t refresh; - int state; - uint8_t *buf,*lbuf; - vector ranges; + void * addr; + size_t len; + size_t refresh; + int state; + uint8_t *buf,*lbuf; + vector ranges; }memdata; enum HEXVIEW_STATES { - STATE_OFF,STATE_ON + STATE_OFF,STATE_ON }; command_result memview (color_ostream &out, vector & parameters); @@ -34,151 +34,151 @@ DFHACK_PLUGIN("memview"); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand("memview","Shows memory in real time. Params: adrr length refresh_rate. If addr==0 then stop viewing",memview)); - memdata.state=STATE_OFF; - mymutex=new tthread::mutex; + commands.push_back(PluginCommand("memview","Shows memory in real time. Params: adrr length refresh_rate. If addr==0 then stop viewing",memview)); + memdata.state=STATE_OFF; + mymutex=new tthread::mutex; return CR_OK; } size_t convert(const std::string& p,bool ishex=false) { - size_t ret; - std::stringstream conv; - if(ishex) - conv<>ret; - return ret; + size_t ret; + std::stringstream conv; + if(ishex) + conv<>ret; + return ret; } bool isAddr(uint32_t *trg,vector & ranges) { - if(trg[0]%4==0) - for(size_t i=0;i & ranges) { const size_t page_size=16; - for(size_t i=0;i31)&&(buf[j+i]<128)) //only printable ascii - con.print("%c",buf[j+i]); - else - con.print("."); - //con.print("\n"); - } - con.print("\n"); + } + if(lbuf[j+i]!=buf[j+i]) + con.print("*%02X",buf[j+i]); //if modfied show a star + else + con.print(" %02X",buf[j+i]); + } + con.reset_color(); + con.print(" | "); + for(size_t j=0;(j31)&&(buf[j+i]<128)) //only printable ascii + con.print("%c",buf[j+i]); + else + con.print("."); + //con.print("\n"); + } + con.print("\n"); } void Deinit() { - if(memdata.state==STATE_ON) - { - memdata.state=STATE_OFF; - delete [] memdata.buf; - delete [] memdata.lbuf; - } + if(memdata.state==STATE_ON) + { + memdata.state=STATE_OFF; + delete [] memdata.buf; + delete [] memdata.lbuf; + } } DFhackCExport command_result plugin_onupdate (color_ostream &out) { - mymutex->lock(); - if(memdata.state==STATE_OFF) - { - mymutex->unlock(); - return CR_OK; - } - //Console &con=out; - uint64_t time2 = GetTimeMs64(); - uint64_t delta = time2-timeLast; + mymutex->lock(); + if(memdata.state==STATE_OFF) + { + mymutex->unlock(); + return CR_OK; + } + //Console &con=out; + uint64_t time2 = GetTimeMs64(); + uint64_t delta = time2-timeLast; - if(memdata.refresh!=0) - if(deltaunlock(); - return CR_OK; - } - timeLast = time2; + if(memdata.refresh!=0) + if(deltaunlock(); + return CR_OK; + } + timeLast = time2; - Core::getInstance().p->read(memdata.addr,memdata.len,memdata.buf); - outputHex(memdata.buf,memdata.lbuf,memdata.len,(size_t)memdata.addr,out,memdata.ranges); + Core::getInstance().p->read(memdata.addr,memdata.len,memdata.buf); + outputHex(memdata.buf,memdata.lbuf,memdata.len,(size_t)memdata.addr,out,memdata.ranges); memcpy(memdata.lbuf, memdata.buf, memdata.len); - if(memdata.refresh==0) - Deinit(); - mymutex->unlock(); - return CR_OK; + if(memdata.refresh==0) + Deinit(); + mymutex->unlock(); + return CR_OK; } command_result memview (color_ostream &out, vector & parameters) { - mymutex->lock(); - Core::getInstance().p->getMemRanges(memdata.ranges); - memdata.addr=(void *)convert(parameters[0],true); - if(memdata.addr==0) - { - Deinit(); - memdata.state=STATE_OFF; - mymutex->unlock(); - return CR_OK; - } - else - { - Deinit(); - bool isValid=false; - for(size_t i=0;iunlock(); - return CR_OK; - } - memdata.state=STATE_ON; - } - if(parameters.size()>1) - memdata.len=convert(parameters[1]); - else - memdata.len=20*16; + mymutex->lock(); + Core::getInstance().p->getMemRanges(memdata.ranges); + memdata.addr=(void *)convert(parameters[0],true); + if(memdata.addr==0) + { + Deinit(); + memdata.state=STATE_OFF; + mymutex->unlock(); + return CR_OK; + } + else + { + Deinit(); + bool isValid=false; + for(size_t i=0;iunlock(); + return CR_OK; + } + memdata.state=STATE_ON; + } + if(parameters.size()>1) + memdata.len=convert(parameters[1]); + else + memdata.len=20*16; - if(parameters.size()>2) - memdata.refresh=convert(parameters[2]); - else - memdata.refresh=0; + if(parameters.size()>2) + memdata.refresh=convert(parameters[2]); + else + memdata.refresh=0; - memdata.buf=new uint8_t[memdata.len]; - memdata.lbuf=new uint8_t[memdata.len]; - Core::getInstance().p->getMemRanges(memdata.ranges); - mymutex->unlock(); - return CR_OK; + memdata.buf=new uint8_t[memdata.len]; + memdata.lbuf=new uint8_t[memdata.len]; + Core::getInstance().p->getMemRanges(memdata.ranges); + mymutex->unlock(); + return CR_OK; } DFhackCExport command_result plugin_shutdown (color_ostream &out) { - mymutex->lock(); - Deinit(); - delete mymutex; - mymutex->unlock(); - return CR_OK; + mymutex->lock(); + Deinit(); + delete mymutex; + mymutex->unlock(); + return CR_OK; } diff --git a/plugins/devel/nestboxes.cpp b/plugins/devel/nestboxes.cpp index 42c3c0660..8c51b57ae 100644 --- a/plugins/devel/nestboxes.cpp +++ b/plugins/devel/nestboxes.cpp @@ -35,32 +35,32 @@ static bool enabled = false; static void eggscan(color_ostream &out) { - CoreSuspender suspend; - - for (int i = 0; i < world->buildings.all.size(); ++i) - { - df::building *build = world->buildings.all[i]; - auto type = build->getType(); - if (df::enums::building_type::NestBox == type) - { + CoreSuspender suspend; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (df::enums::building_type::NestBox == type) + { bool fertile = false; - df::building_nest_boxst *nb = virtual_cast(build); - if (nb->claimed_by != -1) + df::building_nest_boxst *nb = virtual_cast(build); + if (nb->claimed_by != -1) { df::unit* u = df::unit::find(nb->claimed_by); - if (u && u->relations.pregnancy_timer > 0) + if (u && u->relations.pregnancy_timer > 0) fertile = true; } for (int j = 1; j < nb->contained_items.size(); j++) { df::item* item = nb->contained_items[j]->item; - if (item->flags.bits.forbid != fertile) - { - item->flags.bits.forbid = fertile; - out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl; - } + if (item->flags.bits.forbid != fertile) + { + item->flags.bits.forbid = fertile; + out << item->getStackSize() << " eggs " << (fertile ? "forbidden" : "unforbidden.") << endl; + } } - } + } } } @@ -68,12 +68,12 @@ static void eggscan(color_ostream &out) DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (world && ui) { - commands.push_back( - PluginCommand("nestboxes", "Derp.", - nestboxes, false, - "Derp.\n" - ) - ); + commands.push_back( + PluginCommand("nestboxes", "Derp.", + nestboxes, false, + "Derp.\n" + ) + ); } return CR_OK; } @@ -92,28 +92,28 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) if ((++cnt % 5) != 0) return CR_OK; - eggscan(out); + eggscan(out); return CR_OK; } static command_result nestboxes(color_ostream &out, vector & parameters) { - CoreSuspender suspend; - bool clean = false; + CoreSuspender suspend; + bool clean = false; int dump_count = 0; int good_egg = 0; - if (parameters.size() == 1) { - if (parameters[0] == "enable") - enabled = true; - else if (parameters[0] == "disable") - enabled = false; - else - return CR_WRONG_USAGE; - } else { - out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl; - } - return CR_OK; + if (parameters.size() == 1) { + if (parameters[0] == "enable") + enabled = true; + else if (parameters[0] == "disable") + enabled = false; + else + return CR_WRONG_USAGE; + } else { + out << "Plugin " << (enabled ? "enabled" : "disabled") << "." << endl; + } + return CR_OK; } diff --git a/plugins/devel/rprobe.cpp b/plugins/devel/rprobe.cpp index 805489d5e..9591fa8f1 100644 --- a/plugins/devel/rprobe.cpp +++ b/plugins/devel/rprobe.cpp @@ -67,10 +67,10 @@ command_result rprobe (color_ostream &out, vector & parameters) // Embark screen active: estimate using world geology data VIRTUAL_CAST_VAR(screen, df::viewscreen_choose_start_sitest, Core::getTopViewscreen()); - - if (!screen) - return CR_WRONG_USAGE; - + + if (!screen) + return CR_WRONG_USAGE; + if (!world || !world->world_data) { out.printerr("World data is not available.\n"); @@ -78,9 +78,9 @@ command_result rprobe (color_ostream &out, vector & parameters) } - if (parameters.size() == 2) + if (parameters.size() == 2) { - if (parameters[0] == "rai") + if (parameters[0] == "rai") set_field = 0; else if (parameters[0] == "veg") set_field = 1; @@ -97,7 +97,7 @@ command_result rprobe (color_ostream &out, vector & parameters) else return CR_WRONG_USAGE; - if (screen->biome_highlighted) + if (screen->biome_highlighted) to_set = screen->biome_idx; else to_set = 0; @@ -110,7 +110,7 @@ command_result rprobe (color_ostream &out, vector & parameters) coord2d cur_region = screen->region_pos; // Compute biomes - for (int i = 0; i < screen->biome_rgn.size(); i++) + for (int i = 0; i < screen->biome_rgn.size(); i++) { coord2d rg = screen->biome_rgn[i]; @@ -140,7 +140,7 @@ command_result rprobe (color_ostream &out, vector & parameters) " geo_index: " << rd->geo_index << " landmass_id: " << rd->landmass_id << " flags: " << hex << rd->flags.as_int() << dec << endl; - out << + out << "rai: " << rd->rainfall << " " << "veg: " << rd->vegetation << " " << "tem: " << rd->temperature << " " << @@ -148,17 +148,17 @@ command_result rprobe (color_ostream &out, vector & parameters) "dra: " << rd->drainage << " " << "sav: " << rd->savagery << " " << "sal: " << rd->salinity; - + int32_t *p = (int32_t *)rd; int c = sizeof(*rd) / sizeof(int32_t); for (int j = 0; j < c; j++) { - if (j % 8 == 0) + if (j % 8 == 0) out << endl << setfill('0') << setw(8) << hex << (int)(rd+j) << ": "; out << " " << setfill('0') << setw(8) << hex << p[j]; } out << setfill(' ') << setw(0) << dec << endl; } - + return CR_OK; } diff --git a/plugins/devel/stockcheck.cpp b/plugins/devel/stockcheck.cpp index 57be4b126..679411b0e 100644 --- a/plugins/devel/stockcheck.cpp +++ b/plugins/devel/stockcheck.cpp @@ -38,12 +38,12 @@ DFHACK_PLUGIN("stockcheck"); DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) { if (world && ui) { - commands.push_back( - PluginCommand("stockcheck", "Check for unprotected rottable items.", - stockcheck, false, - "Scan world for items that are susceptible to rot. Currently just lists the items.\n" - ) - ); + commands.push_back( + PluginCommand("stockcheck", "Check for unprotected rottable items.", + stockcheck, false, + "Scan world for items that are susceptible to rot. Currently just lists the items.\n" + ) + ); } return CR_OK; } @@ -54,121 +54,121 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) } struct StockpileInfo { - building_stockpilest* sp; - int size; - int free; - int x1, x2, y1, y2, z; + building_stockpilest* sp; + int size; + int free; + int x1, x2, y1, y2, z; public: - StockpileInfo(building_stockpilest *sp_) : sp(sp_) - { - MapExtras::MapCache mc; - - z = sp_->z; - x1 = sp_->room.x; - x2 = sp_->room.x + sp_->room.width; - y1 = sp_->room.y; - y2 = sp_->room.y + sp_->room.height; - int e = 0; - size = 0; - free = 0; - for (int y = y1; y < y2; y++) - for (int x = x1; x < x2; x++) - if (sp_->room.extents[e++] == 1) - { - size++; - DFCoord cursor (x,y,z); - uint32_t blockX = x / 16; - uint32_t tileX = x % 16; - uint32_t blockY = y / 16; - uint32_t tileY = y % 16; - MapExtras::Block * b = mc.BlockAt(cursor/16); - if(b && b->is_valid()) - { - auto &block = *b->getRaw(); - df::tile_occupancy &occ = block.occupancy[tileX][tileY]; - if (!occ.bits.item) - free++; - } - } - } - - bool isFull() { return free == 0; } - - bool canHold(df::item *i) - { - return false; - } - - bool inStockpile(df::item *i) - { - df::item *container = Items::getContainer(i); - if (container) - return inStockpile(container); - - if (i->pos.z != z) return false; - if (i->pos.x < x1 || i->pos.x >= x2 || - i->pos.y < y1 || i->pos.y >= y2) return false; - int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; - return sp->room.extents[e] == 1; - } - - int getId() { return sp->id; } + StockpileInfo(building_stockpilest *sp_) : sp(sp_) + { + MapExtras::MapCache mc; + + z = sp_->z; + x1 = sp_->room.x; + x2 = sp_->room.x + sp_->room.width; + y1 = sp_->room.y; + y2 = sp_->room.y + sp_->room.height; + int e = 0; + size = 0; + free = 0; + for (int y = y1; y < y2; y++) + for (int x = x1; x < x2; x++) + if (sp_->room.extents[e++] == 1) + { + size++; + DFCoord cursor (x,y,z); + uint32_t blockX = x / 16; + uint32_t tileX = x % 16; + uint32_t blockY = y / 16; + uint32_t tileY = y % 16; + MapExtras::Block * b = mc.BlockAt(cursor/16); + if(b && b->is_valid()) + { + auto &block = *b->getRaw(); + df::tile_occupancy &occ = block.occupancy[tileX][tileY]; + if (!occ.bits.item) + free++; + } + } + } + + bool isFull() { return free == 0; } + + bool canHold(df::item *i) + { + return false; + } + + bool inStockpile(df::item *i) + { + df::item *container = Items::getContainer(i); + if (container) + return inStockpile(container); + + if (i->pos.z != z) return false; + if (i->pos.x < x1 || i->pos.x >= x2 || + i->pos.y < y1 || i->pos.y >= y2) return false; + int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; + return sp->room.extents[e] == 1; + } + + int getId() { return sp->id; } }; static command_result stockcheck(color_ostream &out, vector & parameters) { - CoreSuspender suspend; - - std::vector stockpiles; - - for (int i = 0; i < world->buildings.all.size(); ++i) - { - df::building *build = world->buildings.all[i]; - auto type = build->getType(); - if (df::enums::building_type::Stockpile == type) - { - building_stockpilest *sp = virtual_cast(build); - StockpileInfo *spi = new StockpileInfo(sp); - stockpiles.push_back(spi); - } - - } - - std::vector &items = world->items.other[items_other_id::IN_PLAY]; - - // Precompute a bitmask with the bad flags + CoreSuspender suspend; + + std::vector stockpiles; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (df::enums::building_type::Stockpile == type) + { + building_stockpilest *sp = virtual_cast(build); + StockpileInfo *spi = new StockpileInfo(sp); + stockpiles.push_back(spi); + } + + } + + std::vector &items = world->items.other[items_other_id::IN_PLAY]; + + // Precompute a bitmask with the bad flags df::item_flags bad_flags; bad_flags.whole = 0; #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(spider_web); F(owned); F(in_job); + F(in_building); F(construction); F(artifact); + F(spider_web); F(owned); F(in_job); #undef F for (size_t i = 0; i < items.size(); i++) { df::item *item = items[i]; - if (item->flags.whole & bad_flags.whole) + if (item->flags.whole & bad_flags.whole) continue; - // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG + // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG - df::item_type typ = item->getType(); - if (typ != df::enums::item_type::MEAT && - typ != df::enums::item_type::FISH && - typ != df::enums::item_type::FISH_RAW && - typ != df::enums::item_type::PLANT && - typ != df::enums::item_type::CHEESE && - typ != df::enums::item_type::FOOD && - typ != df::enums::item_type::EGG) - continue; + df::item_type typ = item->getType(); + if (typ != df::enums::item_type::MEAT && + typ != df::enums::item_type::FISH && + typ != df::enums::item_type::FISH_RAW && + typ != df::enums::item_type::PLANT && + typ != df::enums::item_type::CHEESE && + typ != df::enums::item_type::FOOD && + typ != df::enums::item_type::EGG) + continue; - df::item *container = 0; - df::unit *holder = 0; - df::building *building = 0; + df::item *container = 0; + df::unit *holder = 0; + df::building *building = 0; for (size_t i = 0; i < item->general_refs.size(); i++) { @@ -177,106 +177,106 @@ static command_result stockcheck(color_ostream &out, vector & parameter switch (ref->getType()) { case general_ref_type::CONTAINED_IN_ITEM: - container = ref->getItem(); + container = ref->getItem(); break; case general_ref_type::UNIT_HOLDER: - holder = ref->getUnit(); + holder = ref->getUnit(); break; case general_ref_type::BUILDING_HOLDER: - building = ref->getBuilding(); + building = ref->getBuilding(); break; default: break; - } - } + } + } - df::item *nextcontainer = container; - df::item *lastcontainer = 0; + df::item *nextcontainer = container; + df::item *lastcontainer = 0; - while(nextcontainer) { - df::item *thiscontainer = nextcontainer; - nextcontainer = 0; - for (size_t i = 0; i < thiscontainer->general_refs.size(); i++) - { - df::general_ref *ref = thiscontainer->general_refs[i]; + while(nextcontainer) { + df::item *thiscontainer = nextcontainer; + nextcontainer = 0; + for (size_t i = 0; i < thiscontainer->general_refs.size(); i++) + { + df::general_ref *ref = thiscontainer->general_refs[i]; - switch (ref->getType()) - { - case general_ref_type::CONTAINED_IN_ITEM: - lastcontainer = nextcontainer = ref->getItem(); - break; + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + lastcontainer = nextcontainer = ref->getItem(); + break; - case general_ref_type::UNIT_HOLDER: - holder = ref->getUnit(); - break; + case general_ref_type::UNIT_HOLDER: + holder = ref->getUnit(); + break; - case general_ref_type::BUILDING_HOLDER: - building = ref->getBuilding(); - break; + case general_ref_type::BUILDING_HOLDER: + building = ref->getBuilding(); + break; default: break; - } - } - } - - if (holder) - continue; // carried items do not rot as far as i know - - if (building) { - df::building_type btype = building->getType(); - if (btype == df::enums::building_type::TradeDepot || - btype == df::enums::building_type::Wagon) - continue; // items in trade depot or the embark wagon do not rot - - if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox) - continue; // eggs in nest box do not rot - } - - int canHoldCount = 0; - StockpileInfo *current = 0; - - for (int idx = 0; idx < stockpiles.size(); idx++) - { - StockpileInfo *spi = stockpiles[idx]; - if (spi->canHold(item)) canHoldCount++; - if (spi->inStockpile(item)) current=spi; - } - - if (current) - continue; - - std::string description; - item->getItemDescription(&description, 0); - out << " * " << description; + } + } + } + + if (holder) + continue; // carried items do not rot as far as i know + + if (building) { + df::building_type btype = building->getType(); + if (btype == df::enums::building_type::TradeDepot || + btype == df::enums::building_type::Wagon) + continue; // items in trade depot or the embark wagon do not rot + + if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox) + continue; // eggs in nest box do not rot + } + + int canHoldCount = 0; + StockpileInfo *current = 0; + + for (int idx = 0; idx < stockpiles.size(); idx++) + { + StockpileInfo *spi = stockpiles[idx]; + if (spi->canHold(item)) canHoldCount++; + if (spi->inStockpile(item)) current=spi; + } + + if (current) + continue; + + std::string description; + item->getItemDescription(&description, 0); + out << " * " << description; if (container) { - std::string containerDescription; - container->getItemDescription(&containerDescription, 0); - out << ", in container " << containerDescription; - if (lastcontainer) { - std::string lastcontainerDescription; - lastcontainer->getItemDescription(&lastcontainerDescription, 0); - out << ", in container " << lastcontainerDescription; - } - } - - if (holder) { - out << ", carried"; - } - - if (building) { - out << ", in building " << building->id << " (type=" << building->getType() << ")"; - } - - out << ", flags=" << std::hex << item->flags.whole << std::dec; - out << endl; - - } - - return CR_OK; + std::string containerDescription; + container->getItemDescription(&containerDescription, 0); + out << ", in container " << containerDescription; + if (lastcontainer) { + std::string lastcontainerDescription; + lastcontainer->getItemDescription(&lastcontainerDescription, 0); + out << ", in container " << lastcontainerDescription; + } + } + + if (holder) { + out << ", carried"; + } + + if (building) { + out << ", in building " << building->id << " (type=" << building->getType() << ")"; + } + + out << ", flags=" << std::hex << item->flags.whole << std::dec; + out << endl; + + } + + return CR_OK; } diff --git a/plugins/devel/stripcaged.cpp b/plugins/devel/stripcaged.cpp index a7683a9bb..30d0b0d2b 100644 --- a/plugins/devel/stripcaged.cpp +++ b/plugins/devel/stripcaged.cpp @@ -61,11 +61,11 @@ bool isContainedInItem(df::unit* unit) DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) { - commands.push_back(PluginCommand( - "stripcaged", "strip caged units of all items", - df_stripcaged, false, - "Clears forbid and sets dump for the inventories of all caged units." - )); + commands.push_back(PluginCommand( + "stripcaged", "strip caged units of all items", + df_stripcaged, false, + "Clears forbid and sets dump for the inventories of all caged units." + )); return CR_OK; } @@ -77,39 +77,39 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) command_result df_stripcaged(color_ostream &out, vector & parameters) { CoreSuspender suspend; - bool keeparmor = true; + bool keeparmor = true; - if (parameters.size() == 1 && parameters[0] == "dumparmor") - { - out << "Dumping armor too" << endl; - keeparmor = false; - } + if (parameters.size() == 1 && parameters[0] == "dumparmor") + { + out << "Dumping armor too" << endl; + keeparmor = false; + } - size_t count = 0; + size_t count = 0; for (size_t i=0; i < world->units.all.size(); i++) { df::unit* unit = world->units.all[i]; - if (isContainedInItem(unit)) - { - for (size_t j=0; j < unit->inventory.size(); j++) - { - df::unit_inventory_item* uii = unit->inventory[j]; - if (uii->item) - { - if (keeparmor && (uii->item->isArmorNotClothing() || uii->item->isClothing())) - continue; - std::string desc; - uii->item->getItemDescription(&desc,0); - out << "Item " << desc << " dumped." << endl; - uii->item->flags.bits.forbid = 0; - uii->item->flags.bits.dump = 1; - count++; - } - } - } - } + if (isContainedInItem(unit)) + { + for (size_t j=0; j < unit->inventory.size(); j++) + { + df::unit_inventory_item* uii = unit->inventory[j]; + if (uii->item) + { + if (keeparmor && (uii->item->isArmorNotClothing() || uii->item->isClothing())) + continue; + std::string desc; + uii->item->getItemDescription(&desc,0); + out << "Item " << desc << " dumped." << endl; + uii->item->flags.bits.forbid = 0; + uii->item->flags.bits.dump = 1; + count++; + } + } + } + } - out << count << " items marked for dumping" << endl; + out << count << " items marked for dumping" << endl; return CR_OK; } diff --git a/plugins/dfstream.cpp b/plugins/dfstream.cpp index 1ed906881..19334abae 100644 --- a/plugins/dfstream.cpp +++ b/plugins/dfstream.cpp @@ -323,7 +323,7 @@ public: auto_renderer_decorator & operator=(renderer_decorator *p) { reset(); this->p = p; - return *this; + return *this; } renderer_decorator * get() { diff --git a/plugins/eventful.cpp b/plugins/eventful.cpp index ba0f8742d..6327583d6 100644 --- a/plugins/eventful.cpp +++ b/plugins/eventful.cpp @@ -185,7 +185,7 @@ df::item* find_item( } static void handle_reaction_done(color_ostream &out,df::reaction*, df::unit *unit, std::vector *in_items,std::vector *in_reag - , std::vector *out_items,bool *call_native){}; + , std::vector *out_items,bool *call_native){}; DEFINE_LUA_EVENT_6(onReactionComplete, handle_reaction_done,df::reaction*, df::unit *, std::vector *,std::vector *,std::vector *,bool *); @@ -208,13 +208,13 @@ struct product_hook : item_product { ) { if (auto product = products[this]) { - df::reaction* this_reaction=product->react; - CoreSuspendClaimer suspend; - color_ostream_proxy out(Core::getInstance().getConsole()); - bool call_native=true; + df::reaction* this_reaction=product->react; + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + bool call_native=true; onReactionComplete(out,this_reaction,unit,in_items,in_reag,out_items,&call_native); - if(!call_native) - return; + if(!call_native) + return; } INTERPOSE_NEXT(produce)(unit, out_items, in_reag, in_items, quantity, skill, entity, site); @@ -233,12 +233,12 @@ IMPLEMENT_VMETHOD_INTERPOSE(product_hook, produce); static void parse_product( - color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod - ) { - info.react = react; - info.product = prod; - info.material.mat_type = prod->mat_type; - info.material.mat_index = prod->mat_index; + color_ostream &out, ProductInfo &info, df::reaction *react, item_product *prod + ) { + info.react = react; + info.product = prod; + info.material.mat_type = prod->mat_type; + info.material.mat_index = prod->mat_index; } static bool find_reactions(color_ostream &out) diff --git a/plugins/filltraffic.cpp b/plugins/filltraffic.cpp index 6e87fd854..eeacc8781 100644 --- a/plugins/filltraffic.cpp +++ b/plugins/filltraffic.cpp @@ -1,7 +1,7 @@ // Wide-area traffic designation utility. // Flood-fill from cursor or fill entire map. -#include //For toupper(). +#include //For toupper(). #include //for min(). #include #include diff --git a/plugins/forceequip.cpp b/plugins/forceequip.cpp index abf4a95db..54da374b5 100644 --- a/plugins/forceequip.cpp +++ b/plugins/forceequip.cpp @@ -57,161 +57,161 @@ command_result df_forceequip(color_ostream &out, vector & parameters); const string forceequip_help = "ForceEquip moves local items into a unit's inventory. It is typically\n" - "used to equip specific clothing/armor items onto a dwarf, but can also\n" - "be used to put armor onto a war animal or to add unusual items (such\n" - "as crowns) to any unit.\n" - "This plugin can process multiple items in a single call, but will only\n" - "work with a single unit (the first one it finds under the cursor).\n" - "In order to minimize confusion, it is recommended that you use\n" - "forceequip only when you have a unit standing alone atop a pile of\n" - "gear that you would like it to wear. Items which are stored in bins\n" - "or other containers (e.g. chests, armor racks) may also work, but\n" - "piling items on the floor (via a garbage dump activity zone, of the\n" - "DFHack autodump command) is the most reliable way to do it.\n" - "The plugin will ignore any items that are forbidden. Hence, you\n" - "can setup a large pile of surplus gear, walk a unit onto it (or\n" - "pasture an animal on it), unforbid a few items and run forceequip.\n" - "The (forbidden) majority of your gear will remain in-place, ready\n" - "for the next passerby." - "\n" - "As mentioned above, this plugin can be used to equip items onto\n" - "units (such as animals) which cannot normally equip gear. There's\n" - "an important caveat here - such creatures will automatically drop\n" - "inappropriate gear almost immediately (within 10 game ticks).\n" - "If you want them to retain their equipment, you must forbid it\n" - "AFTER using forceequip to get it into their inventory.\n" - "This technique can also be used to clothe dwarven infants, but\n" - "only if you're able to separate them from their mothers.\n" + "used to equip specific clothing/armor items onto a dwarf, but can also\n" + "be used to put armor onto a war animal or to add unusual items (such\n" + "as crowns) to any unit.\n" + "This plugin can process multiple items in a single call, but will only\n" + "work with a single unit (the first one it finds under the cursor).\n" + "In order to minimize confusion, it is recommended that you use\n" + "forceequip only when you have a unit standing alone atop a pile of\n" + "gear that you would like it to wear. Items which are stored in bins\n" + "or other containers (e.g. chests, armor racks) may also work, but\n" + "piling items on the floor (via a garbage dump activity zone, of the\n" + "DFHack autodump command) is the most reliable way to do it.\n" + "The plugin will ignore any items that are forbidden. Hence, you\n" + "can setup a large pile of surplus gear, walk a unit onto it (or\n" + "pasture an animal on it), unforbid a few items and run forceequip.\n" + "The (forbidden) majority of your gear will remain in-place, ready\n" + "for the next passerby." "\n" - "By default, the forceequip plugin will attempt to avoid\n" - "conflicts and outright cheating. For instance, it will skip\n" - "any item which is flagged for use in a job, and will not\n" - "equip more than one piece of clothing/armor onto any given\n" - "body part. These restrictions can be overridden via command\n" - "switches (see examples below) but doing so puts you at greater\n" - "risk of unexpected consequences. For instance, a dwarf who\n" - "is wearing three breastplates will not be able to move very\n" - "quickly.\n" - "\n" - "Items equipped by this plugin DO NOT become owned by the\n" - "recipient. Adult dwarves are free to adjust their own\n" - "wardrobe, and may promptly decide to doff your gear in\n" - "favour of their owned items. Animals, as described above,\n" - "will tend to discard ALL clothing immediately unless it is\n" - "manually forbidden. Armor items seem to be an exception;\n" - "an animal will tend to retain an equipped suit of mail\n" - "even if you neglect to Forbid it.\n" - "\n" - "Please note that armored animals are quite vulnerable to ranged\n" - "attacks. Unlike dwarves, animals cannot block, dodge, or deflect\n" - "arrows, and they are slowed by the weight of their armor.\n" - "\n" - "This plugin currently does not support weapons.\n" - "\n" - "Options:\n" - " here, h - process the unit and item(s) under the cursor.\n" - " - This option is enabled by default since the plugin\n" - " - does not currently support remote equpping.\n" + "As mentioned above, this plugin can be used to equip items onto\n" + "units (such as animals) which cannot normally equip gear. There's\n" + "an important caveat here - such creatures will automatically drop\n" + "inappropriate gear almost immediately (within 10 game ticks).\n" + "If you want them to retain their equipment, you must forbid it\n" + "AFTER using forceequip to get it into their inventory.\n" + "This technique can also be used to clothe dwarven infants, but\n" + "only if you're able to separate them from their mothers.\n" + "\n" + "By default, the forceequip plugin will attempt to avoid\n" + "conflicts and outright cheating. For instance, it will skip\n" + "any item which is flagged for use in a job, and will not\n" + "equip more than one piece of clothing/armor onto any given\n" + "body part. These restrictions can be overridden via command\n" + "switches (see examples below) but doing so puts you at greater\n" + "risk of unexpected consequences. For instance, a dwarf who\n" + "is wearing three breastplates will not be able to move very\n" + "quickly.\n" + "\n" + "Items equipped by this plugin DO NOT become owned by the\n" + "recipient. Adult dwarves are free to adjust their own\n" + "wardrobe, and may promptly decide to doff your gear in\n" + "favour of their owned items. Animals, as described above,\n" + "will tend to discard ALL clothing immediately unless it is\n" + "manually forbidden. Armor items seem to be an exception;\n" + "an animal will tend to retain an equipped suit of mail\n" + "even if you neglect to Forbid it.\n" + "\n" + "Please note that armored animals are quite vulnerable to ranged\n" + "attacks. Unlike dwarves, animals cannot block, dodge, or deflect\n" + "arrows, and they are slowed by the weight of their armor.\n" + "\n" + "This plugin currently does not support weapons.\n" + "\n" + "Options:\n" + " here, h - process the unit and item(s) under the cursor.\n" + " - This option is enabled by default since the plugin\n" + " - does not currently support remote equpping.\n" " ignore, i - bypasses the usual item eligibility checks (such as\n" - " - \"Never equip gear belonging to another dwarf\" and\n" - " - \"Nobody is allowed to equip a Hive\".)\n" - " multi, m - bypasses the 1-item-per-bodypart limit, allowing\n" - " - the unit to receive an unlimited amount of gear.\n" - " - Can be used legitimately (e.g. mitten + gauntlet)\n" - " - or for cheating (e.g. twelve breastplates).\n" - " m2, m3, m4 - alters the 1-item-per-bodypart limit, allowing\n" - " - each part to receive 2, 3, or 4 pieces of gear.\n" - " selected, s - rather than processing all items piled at a unit's\n" - " - feet, process only the one item currently selected.\n" - " bodypart, bp - must be followed by a bodypart code (e.g. LH).\n" - " - Instructs the plugin to equip all available items\n" - " - onto this body part only. Typically used in\n" - " - conjunction with the f switch (to over-armor\n" - " - a particular bodypart) or the i switch (to equip\n" - " - an unusual item onto a specific slot).\n" + " - \"Never equip gear belonging to another dwarf\" and\n" + " - \"Nobody is allowed to equip a Hive\".)\n" + " multi, m - bypasses the 1-item-per-bodypart limit, allowing\n" + " - the unit to receive an unlimited amount of gear.\n" + " - Can be used legitimately (e.g. mitten + gauntlet)\n" + " - or for cheating (e.g. twelve breastplates).\n" + " m2, m3, m4 - alters the 1-item-per-bodypart limit, allowing\n" + " - each part to receive 2, 3, or 4 pieces of gear.\n" + " selected, s - rather than processing all items piled at a unit's\n" + " - feet, process only the one item currently selected.\n" + " bodypart, bp - must be followed by a bodypart code (e.g. LH).\n" + " - Instructs the plugin to equip all available items\n" + " - onto this body part only. Typically used in\n" + " - conjunction with the f switch (to over-armor\n" + " - a particular bodypart) or the i switch (to equip\n" + " - an unusual item onto a specific slot).\n" " verbose, v - provides detailed narration and error messages.\n" - " - Can be helpful in resolving failures; not needed\n" - " - for casual use.\n" - "\n" + " - Can be helpful in resolving failures; not needed\n" + " - for casual use.\n" + "\n" "Examples:\n" - " forceequip\n" - " attempts to equip all of the items under the cursor onto the unit\n" - " under the cursor. Uses only clothing/armor items; ignores all\n" - " other types. Equips a maximum of 1 item onto each bodypart,\n" - " and equips only \"appropriate\" items in each slot (e.g. glove\n" - " --> hand). Bypasses any item which might cause a conflict,\n" - " such as a Boot belonging to a different dwarf.\n" - " forceequip bp LH\n" - " attempts to equip all local items onto the left hand of the local\n" - " unit. If the hand is already equipped then nothing will happen,\n" - " and if it is not equipped then only one appropriate item (e.g. \n" - " a single mitten or gauntlet) will be equipped. This command can\n" - " be useful if you don't want to selectively forbid individual items\n" - " and simply want the unit to equip, say, a Helmet while leaving\n" - " the rest of the pile alone.\n" - " forceequip m bp LH\n" - " as above, but will equip ALL appropriate items onto the unit's\n" - " left hand. After running this command, it might end up wearing\n" - " a dozen left-handed mittens. Use with caution, and remember\n" - " that dwarves will tend to drop supernumary items ASAP.\n" - " forceequip m\n" - " as above, but will equip ALL appropriate items onto any\n" - " appropriate bodypart. Tends to put several boots onto the right\n" - " foot while leaving the left foot bare.\n" - " forceequip m2\n" - " as above, but will equip up to two appropriate items onto each\n" - " bodypart. Helps to balance footwear, but doesn't ensure proper\n" - " placement (e.g. left foot gets two socks, right foot gets two\n" - " shoes). For best results, use \"selected bp LH\" and\n" - " \"selected bp RH\" instead.\n" - " forceequip i\n" - " performs the standard \"equip appropriate items onto appropriate\n" - " bodyparts\" logic, but also includes items that would normally\n" - " be considered ineligible (such as a sock which is owned by\n" - " a different dwarf).\n" - " forceequip bp NECK\n" - " attempts to equip any appropriate gear onto the Neck of the\n" - " local unit. Since the plugin believes that no items are actually\n" - " appropriate for the Neck slot, this command does nothing.\n" - " forceequip i bp NECK\n" - " attempts to equip items from the local pile onto the Neck\n" - " of the local unit. Ignores appropriateness restrictions.\n" - " If there's a millstone or an albatross carcass sitting on\n" - " the same square as the targeted unit, then there's a good\n" - " chance that it will end up around his neck. For precise\n" - " control, remember that you can selectively forbid some of\n" - " the items that are piled on the ground.\n" - " forceequip i m bp NECK\n" - " as above, but equips an unlimited number of items onto the\n" - " targeted bodypart. Effectively, all unforbidden items\n" - " (including helms, millstones, boulders, etc) will be\n" - " moved from the local pile and placed in the dwarf's\n" - " inventory (specifically, on his neck). When used with\n" - " a large pile of goods, this will leave the dwarf heavily\n" - " encumbered and very slow to move.\n" - " forceequip s\n" - " requires that a single item be selected using the k menu.\n" - " This item must occupy the same square as the target unit,\n" - " and must be unforbidden. Attempts to equip this single\n" - " item onto an appropriate slot in the unit's inventory.\n" - " Can serve as a quicker alternative to the selective-\n" - " unforbidding approach described above.\n" - " forceequip s m i bp HD\n" - " equips the selected item onto the unit's head. Ignores\n" - " all possible restrictions and conflicts. If you know\n" - " exactly what you want to equip, and exactly where you\n" - " want it to go, then this is the most straightforward\n" - " and reliable option.\n" - " forceequip v bp QQQ\n" - " guaranteed to fail (and accomplish nothing) because\n" - " there are no bodyparts called QQQ. However, since the\n" - " verbose switch is used, the resulting error messages\n" - " will list every bodypart that the unit DOES possess.\n" - " May be useful if you're unfamiliar with the BP codes\n" - " used by Dwarf Fortress, or if you're experimenting\n" - " with an exotic creature.\n" - "\n" - ; + " forceequip\n" + " attempts to equip all of the items under the cursor onto the unit\n" + " under the cursor. Uses only clothing/armor items; ignores all\n" + " other types. Equips a maximum of 1 item onto each bodypart,\n" + " and equips only \"appropriate\" items in each slot (e.g. glove\n" + " --> hand). Bypasses any item which might cause a conflict,\n" + " such as a Boot belonging to a different dwarf.\n" + " forceequip bp LH\n" + " attempts to equip all local items onto the left hand of the local\n" + " unit. If the hand is already equipped then nothing will happen,\n" + " and if it is not equipped then only one appropriate item (e.g. \n" + " a single mitten or gauntlet) will be equipped. This command can\n" + " be useful if you don't want to selectively forbid individual items\n" + " and simply want the unit to equip, say, a Helmet while leaving\n" + " the rest of the pile alone.\n" + " forceequip m bp LH\n" + " as above, but will equip ALL appropriate items onto the unit's\n" + " left hand. After running this command, it might end up wearing\n" + " a dozen left-handed mittens. Use with caution, and remember\n" + " that dwarves will tend to drop supernumary items ASAP.\n" + " forceequip m\n" + " as above, but will equip ALL appropriate items onto any\n" + " appropriate bodypart. Tends to put several boots onto the right\n" + " foot while leaving the left foot bare.\n" + " forceequip m2\n" + " as above, but will equip up to two appropriate items onto each\n" + " bodypart. Helps to balance footwear, but doesn't ensure proper\n" + " placement (e.g. left foot gets two socks, right foot gets two\n" + " shoes). For best results, use \"selected bp LH\" and\n" + " \"selected bp RH\" instead.\n" + " forceequip i\n" + " performs the standard \"equip appropriate items onto appropriate\n" + " bodyparts\" logic, but also includes items that would normally\n" + " be considered ineligible (such as a sock which is owned by\n" + " a different dwarf).\n" + " forceequip bp NECK\n" + " attempts to equip any appropriate gear onto the Neck of the\n" + " local unit. Since the plugin believes that no items are actually\n" + " appropriate for the Neck slot, this command does nothing.\n" + " forceequip i bp NECK\n" + " attempts to equip items from the local pile onto the Neck\n" + " of the local unit. Ignores appropriateness restrictions.\n" + " If there's a millstone or an albatross carcass sitting on\n" + " the same square as the targeted unit, then there's a good\n" + " chance that it will end up around his neck. For precise\n" + " control, remember that you can selectively forbid some of\n" + " the items that are piled on the ground.\n" + " forceequip i m bp NECK\n" + " as above, but equips an unlimited number of items onto the\n" + " targeted bodypart. Effectively, all unforbidden items\n" + " (including helms, millstones, boulders, etc) will be\n" + " moved from the local pile and placed in the dwarf's\n" + " inventory (specifically, on his neck). When used with\n" + " a large pile of goods, this will leave the dwarf heavily\n" + " encumbered and very slow to move.\n" + " forceequip s\n" + " requires that a single item be selected using the k menu.\n" + " This item must occupy the same square as the target unit,\n" + " and must be unforbidden. Attempts to equip this single\n" + " item onto an appropriate slot in the unit's inventory.\n" + " Can serve as a quicker alternative to the selective-\n" + " unforbidding approach described above.\n" + " forceequip s m i bp HD\n" + " equips the selected item onto the unit's head. Ignores\n" + " all possible restrictions and conflicts. If you know\n" + " exactly what you want to equip, and exactly where you\n" + " want it to go, then this is the most straightforward\n" + " and reliable option.\n" + " forceequip v bp QQQ\n" + " guaranteed to fail (and accomplish nothing) because\n" + " there are no bodyparts called QQQ. However, since the\n" + " verbose switch is used, the resulting error messages\n" + " will list every bodypart that the unit DOES possess.\n" + " May be useful if you're unfamiliar with the BP codes\n" + " used by Dwarf Fortress, or if you're experimenting\n" + " with an exotic creature.\n" + "\n" + ; DFhackCExport command_result plugin_init ( color_ostream &out, vector &commands) { @@ -233,7 +233,7 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u { // Step 1: Check for anti-requisite conditions df::unit * itemOwner = Items::getOwner(item); - if (ignoreRestrictions) + if (ignoreRestrictions) { // If the ignoreRestrictions cmdline switch was specified, then skip all of the normal preventative rules if (verbose) { Core::print("Skipping integrity checks...\n"); } @@ -244,8 +244,8 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u return false; } else if (item->getType() != df::enums::item_type::GLOVES && - item->getType() != df::enums::item_type::HELM && - item->getType() != df::enums::item_type::ARMOR && + item->getType() != df::enums::item_type::HELM && + item->getType() != df::enums::item_type::ARMOR && item->getType() != df::enums::item_type::PANTS && item->getType() != df::enums::item_type::SHOES && !targetBodyPart) @@ -295,7 +295,7 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u if (verbose) { Core::printerr("Found bodypart %s; not a match; continuing search.\n", currPart->token.c_str()); } continue; } - else + else { // The specified body part has not been found, and we've reached the end of the list. Report failure. if (verbose) { Core::printerr("The specified body part (%s) does not belong to the chosen unit. Please double-check to ensure that your spelling is correct, and that you have not chosen a dismembered bodypart.\n"); } @@ -366,14 +366,14 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u // Collision detected; have we reached the limit? if (++collisions >= multiEquipLimit) { - if (verbose) { Core::printerr(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); } + if (verbose) { Core::printerr(" but it already carries %d piece(s) of equipment. Either remove the existing equipment or use the Multi option.\n", multiEquipLimit); } confirmedBodyPart = NULL; break; } } } - if (confirmedBodyPart) + if (confirmedBodyPart) { // Match found; no need to examine any other BPs if (verbose) { Core::print(" eligibility confirmed..."); } @@ -412,31 +412,31 @@ static bool moveToInventory(MapExtras::MapCache &mc, df::item *item, df::unit *u command_result df_forceequip(color_ostream &out, vector & parameters) { - // The "here" option is hardcoded to true, because the plugin currently doesn't support - // equip-at-a-distance (e.g. grab items within 10 squares of the targeted unit) + // The "here" option is hardcoded to true, because the plugin currently doesn't support + // equip-at-a-distance (e.g. grab items within 10 squares of the targeted unit) bool here = true; - // For balance (anti-cheating) reasons, the plugin applies a limit on the number of - // item that can be equipped on any bodypart. This limit defaults to 1 but can be - // overridden with cmdline switches. + // For balance (anti-cheating) reasons, the plugin applies a limit on the number of + // item that can be equipped on any bodypart. This limit defaults to 1 but can be + // overridden with cmdline switches. int multiEquipLimit = 1; - // The plugin applies several pre-checks in order to reduce the risk of conflict - // and unintended side-effects. Most of these checks can be disabled via cmdline - bool ignore = false; - // By default, the plugin uses all gear piled on the selected square. Optionally, - // it can target only a single item (selected on the k menu) instead - bool selected = false; - // Most of the plugin's text output is suppressed by default. It can be enabled - // to provide insight into errors, and/or for debugging purposes. - bool verbose = false; - // By default, the plugin will mate each item to an appropriate bodypart. This - // behaviour can be skipped if the user specifies a particular BP in the cmdline input. + // The plugin applies several pre-checks in order to reduce the risk of conflict + // and unintended side-effects. Most of these checks can be disabled via cmdline + bool ignore = false; + // By default, the plugin uses all gear piled on the selected square. Optionally, + // it can target only a single item (selected on the k menu) instead + bool selected = false; + // Most of the plugin's text output is suppressed by default. It can be enabled + // to provide insight into errors, and/or for debugging purposes. + bool verbose = false; + // By default, the plugin will mate each item to an appropriate bodypart. This + // behaviour can be skipped if the user specifies a particular BP in the cmdline input. std::string targetBodyPartCode; - // Parse the input + // Parse the input for (size_t i = 0; i < parameters.size(); i++) { string & p = parameters[i]; - + if (p == "help" || p == "?" || p == "h" || p == "/?" || p == "info" || p == "man") { out << forceequip_help << endl; @@ -446,34 +446,34 @@ command_result df_forceequip(color_ostream &out, vector & parameters) { here = true; } - else if (p == "ignore" || p == "i") - { - ignore = true; - } + else if (p == "ignore" || p == "i") + { + ignore = true; + } else if (p == "multi" || p == "m") { - multiEquipLimit = INT_MAX; - } - else if (p == "m2") - { - multiEquipLimit = 2; - } - else if (p == "m3") - { - multiEquipLimit = 3; - } - else if (p == "m4") - { - multiEquipLimit = 4; - } - else if (p == "selected" || p == "s") - { - selected = true; - } - else if (p == "verbose" || p == "v") - { - verbose = true; - } + multiEquipLimit = INT_MAX; + } + else if (p == "m2") + { + multiEquipLimit = 2; + } + else if (p == "m3") + { + multiEquipLimit = 3; + } + else if (p == "m4") + { + multiEquipLimit = 4; + } + else if (p == "selected" || p == "s") + { + selected = true; + } + else if (p == "verbose" || p == "v") + { + verbose = true; + } else if (p == "bodypart" || p == "bp" ) { // must be followed by bodypart code (e.g. NECK) @@ -492,150 +492,150 @@ command_result df_forceequip(color_ostream &out, vector & parameters) } } - // Ensure that the map information is available (e.g. a game is actually in-progress) + // Ensure that the map information is available (e.g. a game is actually in-progress) if (!Maps::IsValid()) { out.printerr("Map is not available!\n"); return CR_FAILURE; } - // Lookup the cursor position - int cx, cy, cz; - DFCoord pos_cursor; - - // needs a cursor - if (!Gui::getCursorCoords(cx,cy,cz)) - { - out.printerr("Cursor position not found. Please enable the cursor.\n"); - return CR_FAILURE; - } - pos_cursor = DFCoord(cx,cy,cz); - - // Iterate over all units, process the first one whose pos == pos_cursor - df::unit * targetUnit; - size_t numUnits = world->units.all.size(); - for(size_t i=0; i< numUnits; i++) - { - targetUnit = world->units.all[i]; // tentatively assume that we have a match; then verify - DFCoord pos_unit(targetUnit->pos.x, targetUnit->pos.y, targetUnit->pos.z); - - if (pos_unit == pos_cursor) - break; - - if (i + 1 == numUnits) - { - out.printerr("No unit found at cursor!\n"); - return CR_FAILURE; - } - } - - // Assert: unit found. - - // If a specific bodypart was included in the command arguments, then search for it now - df::body_part_raw * targetBodyPart = NULL; - if (targetBodyPartCode.size() > 0) { - for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++) - { - // Tentatively assume that the part is a match - targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex); - if (targetBodyPart->token.compare(targetBodyPartCode) == 0) - { - // It is indeed a match; exit the loop (while leaving the variable populated) - if (verbose) { out.print("Matching bodypart (%s) found.\n", targetBodyPart->token.c_str()); } - break; - } - else - { - // Not a match; nullify the variable (it will get re-populated on the next pass through the loop) - if (verbose) { out.printerr("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->token.c_str(), targetBodyPartCode.c_str()); } - targetBodyPart = NULL; - } - } - - if (!targetBodyPart) - { - // Loop iteration is complete but no match was found. - out.printerr("The unit does not possess a bodypart of type \"%s\". Please check the spelling or choose a different unit.\n", targetBodyPartCode.c_str()); - return CR_FAILURE; - } - } - - // Search for item(s) - MapCache mc; - - // iterate over all items, process those where pos == pos_cursor - int itemsEquipped = 0; - int itemsFound = 0; - int numItems = world->items.all.size(); // Normally, we iterate through EVERY ITEM in the world. This is expensive, but currently necessary. - if (selected) { numItems = 1; } // If the user wants to process only the selected item, then the loop is trivialized (only one pass is needed). - for(int i=0; i< numItems; i++) - { - df::item * currentItem; - - // Search behaviour depends on whether the operation is driven by cursor location or UI selection - if (selected) - { - // The "search" is trivial - the selection must always cover either one or zero items - currentItem = Gui::getSelectedItem(out); - if (!currentItem) { return CR_FAILURE; } - } - else - { - // Lookup the current item in the world-space - currentItem = world->items.all[i]; - // Test the item's position - DFCoord pos_item(currentItem->pos.x, currentItem->pos.y, currentItem->pos.z); - if (pos_item != pos_cursor) - { - // The item is in the wrong place; skip it - // Note: we do not emit any notification, even with the "verbose" switch, because the search space is enormous and we'd invariably flood the UI with useless text - continue; - } - // Bypass any forbidden items - else if (currentItem->flags.bits.forbid == 1) - { - // The item is forbidden; skip it - if (verbose) { out.printerr("Forbidden item encountered; skipping to next item.\n"); } - } - - } - - // Test the item; check whether we have any grounds to disqualify/reject it - if (currentItem->flags.bits.in_inventory == 1) - { - // The item is in a unit's inventory; skip it - if (verbose) { out.printerr("Inventory item encountered; skipping to next item.\n"); } - } - else - { - itemsFound ++; // Track the number of items found under the cursor (for feedback purposes) - if (moveToInventory(mc, currentItem, targetUnit, targetBodyPart, ignore, multiEquipLimit, verbose)) - { -// // TODO TEMP EXPERIMENTAL - try to alter the item size in order to conform to its wearer -// currentItem->getRace(); -// out.print("Critter size: %d| %d | Armor size: %d", world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_1, world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_2, currentItem->getTotalDimension()); - - itemsEquipped++; // Track the number of items successfully processed (for feedback purposes) - } - } - } - - if (itemsFound == 0) { - out.printerr("No usable items found at the cursor position. Please choose a different location and try again.\n"); - return CR_OK; - } - - - if (itemsEquipped == 0 && !verbose) { out.printerr("Some items were found but no equipment changes could be made. Use the /verbose switch to display the reasons for failure.\n"); } - if (itemsEquipped > 0) { out.print("%d items equipped.\n", itemsEquipped); } - // At this point, some changes may have been made (such as detaching items from their original position), regardless of whether any equipment changes succeeded. - // Therefore, we must update the map. - mc.WriteAll(); - - // Note: we might expect to recalculate the unit's weight at this point, in order to account for the - // added items. In fact, this recalculation occurs automatically during each dwarf's "turn". - // The slight delay in recalculation is probably not worth worrying about. - - // Work complete; report success - return CR_OK; + // Lookup the cursor position + int cx, cy, cz; + DFCoord pos_cursor; + + // needs a cursor + if (!Gui::getCursorCoords(cx,cy,cz)) + { + out.printerr("Cursor position not found. Please enable the cursor.\n"); + return CR_FAILURE; + } + pos_cursor = DFCoord(cx,cy,cz); + + // Iterate over all units, process the first one whose pos == pos_cursor + df::unit * targetUnit; + size_t numUnits = world->units.all.size(); + for(size_t i=0; i< numUnits; i++) + { + targetUnit = world->units.all[i]; // tentatively assume that we have a match; then verify + DFCoord pos_unit(targetUnit->pos.x, targetUnit->pos.y, targetUnit->pos.z); + + if (pos_unit == pos_cursor) + break; + + if (i + 1 == numUnits) + { + out.printerr("No unit found at cursor!\n"); + return CR_FAILURE; + } + } + + // Assert: unit found. + + // If a specific bodypart was included in the command arguments, then search for it now + df::body_part_raw * targetBodyPart = NULL; + if (targetBodyPartCode.size() > 0) { + for (int bpIndex = 0; bpIndex < targetUnit->body.body_plan->body_parts.size(); bpIndex ++) + { + // Tentatively assume that the part is a match + targetBodyPart = targetUnit->body.body_plan->body_parts.at(bpIndex); + if (targetBodyPart->token.compare(targetBodyPartCode) == 0) + { + // It is indeed a match; exit the loop (while leaving the variable populated) + if (verbose) { out.print("Matching bodypart (%s) found.\n", targetBodyPart->token.c_str()); } + break; + } + else + { + // Not a match; nullify the variable (it will get re-populated on the next pass through the loop) + if (verbose) { out.printerr("Bodypart \"%s\" does not match \"%s\".\n", targetBodyPart->token.c_str(), targetBodyPartCode.c_str()); } + targetBodyPart = NULL; + } + } + + if (!targetBodyPart) + { + // Loop iteration is complete but no match was found. + out.printerr("The unit does not possess a bodypart of type \"%s\". Please check the spelling or choose a different unit.\n", targetBodyPartCode.c_str()); + return CR_FAILURE; + } + } + + // Search for item(s) + MapCache mc; + + // iterate over all items, process those where pos == pos_cursor + int itemsEquipped = 0; + int itemsFound = 0; + int numItems = world->items.all.size(); // Normally, we iterate through EVERY ITEM in the world. This is expensive, but currently necessary. + if (selected) { numItems = 1; } // If the user wants to process only the selected item, then the loop is trivialized (only one pass is needed). + for(int i=0; i< numItems; i++) + { + df::item * currentItem; + + // Search behaviour depends on whether the operation is driven by cursor location or UI selection + if (selected) + { + // The "search" is trivial - the selection must always cover either one or zero items + currentItem = Gui::getSelectedItem(out); + if (!currentItem) { return CR_FAILURE; } + } + else + { + // Lookup the current item in the world-space + currentItem = world->items.all[i]; + // Test the item's position + DFCoord pos_item(currentItem->pos.x, currentItem->pos.y, currentItem->pos.z); + if (pos_item != pos_cursor) + { + // The item is in the wrong place; skip it + // Note: we do not emit any notification, even with the "verbose" switch, because the search space is enormous and we'd invariably flood the UI with useless text + continue; + } + // Bypass any forbidden items + else if (currentItem->flags.bits.forbid == 1) + { + // The item is forbidden; skip it + if (verbose) { out.printerr("Forbidden item encountered; skipping to next item.\n"); } + } + + } + + // Test the item; check whether we have any grounds to disqualify/reject it + if (currentItem->flags.bits.in_inventory == 1) + { + // The item is in a unit's inventory; skip it + if (verbose) { out.printerr("Inventory item encountered; skipping to next item.\n"); } + } + else + { + itemsFound ++; // Track the number of items found under the cursor (for feedback purposes) + if (moveToInventory(mc, currentItem, targetUnit, targetBodyPart, ignore, multiEquipLimit, verbose)) + { +// // TODO TEMP EXPERIMENTAL - try to alter the item size in order to conform to its wearer +// currentItem->getRace(); +// out.print("Critter size: %d| %d | Armor size: %d", world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_1, world->raws.creatures.all[targetUnit->race]->caste[targetUnit->caste]->body_size_2, currentItem->getTotalDimension()); + + itemsEquipped++; // Track the number of items successfully processed (for feedback purposes) + } + } + } + + if (itemsFound == 0) { + out.printerr("No usable items found at the cursor position. Please choose a different location and try again.\n"); + return CR_OK; + } + + + if (itemsEquipped == 0 && !verbose) { out.printerr("Some items were found but no equipment changes could be made. Use the /verbose switch to display the reasons for failure.\n"); } + if (itemsEquipped > 0) { out.print("%d items equipped.\n", itemsEquipped); } + // At this point, some changes may have been made (such as detaching items from their original position), regardless of whether any equipment changes succeeded. + // Therefore, we must update the map. + mc.WriteAll(); + + // Note: we might expect to recalculate the unit's weight at this point, in order to account for the + // added items. In fact, this recalculation occurs automatically during each dwarf's "turn". + // The slight delay in recalculation is probably not worth worrying about. + + // Work complete; report success + return CR_OK; } diff --git a/plugins/lua/workflow.lua b/plugins/lua/workflow.lua index e3fb7b32e..19ca0a84a 100644 --- a/plugins/lua/workflow.lua +++ b/plugins/lua/workflow.lua @@ -9,6 +9,7 @@ local utils = require 'utils' * isEnabled() * setEnabled(enable) * listConstraints([job]) -> {...} + * findConstraint(token) -> {...} or nil * setConstraint(token[, by_count, goal, gap]) -> {...} * deleteConstraint(token) -> true/false @@ -255,7 +256,7 @@ function constraintToToken(cspec) end local mask_part if cspec.mat_mask then - mask_part = table.concat(utils.list_bitfield_flags(cspec.mat_mask), ',') + mask_part = string.upper(table.concat(utils.list_bitfield_flags(cspec.mat_mask), ',')) end local mat_part if cspec.mat_type and cspec.mat_type >= 0 then @@ -270,8 +271,9 @@ function constraintToToken(cspec) if cspec.is_local then table.insert(qlist, "LOCAL") end - if cspec.quality and cspec.quality > 0 then - table.insert(qlist, df.item_quality[cspec.quality] or error('invalid quality: '..cspec.quality)) + if cspec.min_quality and cspec.min_quality > 0 then + local qn = df.item_quality[cspec.min_quality] or error('invalid quality: '..cspec.min_quality) + table.insert(qlist, qn) end local qpart if #qlist > 0 then 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..4148659a6 100644 --- a/plugins/ruby/ruby-autogen-defs.rb +++ b/plugins/ruby/ruby-autogen-defs.rb @@ -138,7 +138,6 @@ module DFHack @@inspecting = {} # avoid infinite recursion on mutually-referenced objects def inspect cn = self.class.name.sub(/^DFHack::/, '') - cn << ' @' << ('0x%X' % _memaddr) if cn != '' out = "#<#{cn}" return out << ' ...>' if @@inspecting[_memaddr] @@inspecting[_memaddr] = true @@ -655,6 +654,13 @@ module DFHack DFHack.memory_bitarray_set(@_memaddr, idx, v) end end + def inspect + out = "#' + end include Enumerable end 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/ruby/unit.rb b/plugins/ruby/unit.rb index 4fbf75d8d..4c638b1a9 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -21,7 +21,7 @@ module DFHack when :SelectTrainer v.trainer_unit[v.trainer_cursor] end - else + when :viewscreen_dwarfmodest case ui.main.mode when :ViewUnits # nobody selected => idx == 0 @@ -33,6 +33,15 @@ module DFHack else ui.follow_unit_tg if ui.follow_unit != -1 end + when :viewscreen_dungeonmodest + case ui_advmode.menu + when :Default + world.units.active[0] + else + unit_find(cursor) # XXX + end + when :viewscreen_dungeon_monsterstatusst + curview.unit end elsif what.kind_of?(Integer) # search by id 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 b4a435421..70d915ffc 100644 --- a/plugins/tweak.cpp +++ b/plugins/tweak.cpp @@ -51,6 +51,12 @@ #include "df/squad_position.h" #include "df/job.h" #include "df/general_ref_building_holderst.h" +#include "df/unit_health_info.h" +#include "df/activity_entry.h" +#include "df/activity_event_combat_trainingst.h" +#include "df/activity_event_individual_skill_drillst.h" +#include "df/activity_event_skill_demonstrationst.h" +#include "df/activity_event_sparringst.h" #include @@ -128,6 +134,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector 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) @@ -233,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(); } @@ -662,6 +686,238 @@ struct military_assign_hook : df::viewscreen_layer_militaryst { IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, feed); IMPLEMENT_VMETHOD_INTERPOSE(military_assign_hook, render); +// Unit updates are executed based on an action divisor variable, +// which is computed from the alive unit count and has range 10-100. +static int adjust_unit_divisor(int value) { + return value*10/DF_GLOBAL_FIELD(ui, unit_action_divisor, 10); +} + +static bool can_spar(df::unit *unit) { + return unit->counters2.exhaustion <= 2000 && // actually 4000, but leave a gap + (unit->status2.able_grasp_impair > 0 || unit->status2.able_grasp == 0) && + (!unit->health || (unit->health->flags.whole&0x7FF) == 0) && + (!unit->job.current_job || unit->job.current_job != job_type::Rest); +} + +static bool has_spar_inventory(df::unit *unit, df::job_skill skill) +{ + using namespace df::enums::job_skill; + + auto type = ENUM_ATTR(job_skill, type, skill); + + if (type == job_skill_class::MilitaryWeapon) + { + for (size_t i = 0; i < unit->inventory.size(); i++) + { + auto item = unit->inventory[i]; + if (item->mode == df::unit_inventory_item::Weapon && + item->item->getMeleeSkill() == skill) + return true; + } + + return false; + } + + switch (skill) { + case THROW: + case RANGED_COMBAT: + return false; + + case SHIELD: + for (size_t i = 0; i < unit->inventory.size(); i++) + { + auto item = unit->inventory[i]; + if (item->mode == df::unit_inventory_item::Weapon && + item->item->getType() == item_type::SHIELD) + return true; + } + return false; + + case ARMOR: + for (size_t i = 0; i < unit->inventory.size(); i++) + { + auto item = unit->inventory[i]; + if (item->mode == df::unit_inventory_item::Worn && + item->item->isArmorNotClothing()) + return true; + } + return false; + + default: + return true; + } +} + +struct military_training_ct_hook : df::activity_event_combat_trainingst { + typedef df::activity_event_combat_trainingst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) + { + auto act = df::activity_entry::find(activity_id); + int cur_neid = act ? act->next_event_id : 0; + int cur_oc = organize_counter; + + INTERPOSE_NEXT(process)(unit); + + // Shorten the time it takes to organize stuff, so that in + // reality it remains the same instead of growing proportionally + // to the unit count. + if (organize_counter > cur_oc && organize_counter > 0) + organize_counter = adjust_unit_divisor(organize_counter); + + if (act && act->next_event_id > cur_neid) + { + // New events were added. Check them. + for (size_t i = 0; i < act->events.size(); i++) + { + auto event = act->events[i]; + if (event->flags.bits.dismissed || event->event_id < cur_neid) + continue; + + if (auto sp = strict_virtual_cast(event)) + { + // Sparring has a problem in that all of its participants decrement + // the countdown variable. Fix this by multiplying it by the member count. + sp->countdown = sp->countdown * sp->participants.units.size(); + } + else if (auto sd = strict_virtual_cast(event)) + { + // Adjust initial counter values + sd->train_countdown = adjust_unit_divisor(sd->train_countdown); + sd->wait_countdown = adjust_unit_divisor(sd->wait_countdown); + + // Check if the game selected the most skilled unit as the teacher + auto &units = sd->participants.units; + int maxv = -1, cur_xp = -1, minv = 0; + int best = -1; + size_t spar = 0; + + for (size_t j = 0; j < units.size(); j++) + { + auto unit = df::unit::find(units[j]); + if (!unit) continue; + int xp = Units::getExperience(unit, sd->skill, true); + if (units[j] == sd->unit_id) + cur_xp = xp; + if (j == 0 || xp < minv) + minv = xp; + if (xp > maxv) { + maxv = xp; + best = j; + } + if (can_spar(unit) && has_spar_inventory(unit, sd->skill)) + spar++; + } + + color_ostream_proxy out(Core::getInstance().getConsole()); + + // If the xp gap is low, sometimes replace with sparring + if ((maxv - minv) < 64*15 && spar == units.size() && + random_int(45) >= 30 + (maxv-minv)/64) + { + out.print("Replacing %s demonstration (xp %d-%d, gap %d) with sparring.\n", + ENUM_KEY_STR(job_skill, sd->skill).c_str(), minv, maxv, maxv-minv); + + if (auto spar = df::allocate()) + { + spar->event_id = sd->event_id; + spar->activity_id = sd->activity_id; + spar->parent_event_id = sd->parent_event_id; + spar->flags = sd->flags; + spar->participants = sd->participants; + spar->building_id = sd->building_id; + spar->countdown = 300*units.size(); + + delete sd; + act->events[i] = spar; + + continue; + } + } + + // If the teacher has less xp than somebody else, switch + if (best >= 0 && maxv > cur_xp) + { + out.print("Replacing %s teacher %d (%d xp) with %d (%d xp); xp gap %d.\n", + ENUM_KEY_STR(job_skill, sd->skill).c_str(), + sd->unit_id, cur_xp, units[best], maxv, maxv-minv); + + sd->hist_figure_id = sd->participants.histfigs[best]; + sd->unit_id = units[best]; + } + else + { + out.print("Not changing %s demonstration (xp %d-%d, gap %d).\n", + ENUM_KEY_STR(job_skill, sd->skill).c_str(), + minv, maxv, maxv-minv); + } + } + } + } + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_training_ct_hook, process); + +struct military_training_sd_hook : df::activity_event_skill_demonstrationst { + typedef df::activity_event_skill_demonstrationst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) + { + int cur_oc = organize_counter; + int cur_tc = train_countdown; + + INTERPOSE_NEXT(process)(unit); + + // Shorten the counters if they changed + if (organize_counter > cur_oc && organize_counter > 0) + organize_counter = adjust_unit_divisor(organize_counter); + if (train_countdown > cur_tc) + train_countdown = adjust_unit_divisor(train_countdown); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_training_sd_hook, process); + +template +bool is_done(T *event, df::unit *unit) +{ + return event->flags.bits.dismissed || + binsearch_index(event->participants.units, unit->id) < 0; +} + +struct military_training_sp_hook : df::activity_event_sparringst { + typedef df::activity_event_sparringst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) + { + INTERPOSE_NEXT(process)(unit); + + // Since there are no counters to fix, repeat the call + int cnt = (DF_GLOBAL_FIELD(ui, unit_action_divisor, 10)+5) / 10; + for (int i = 1; i < cnt && !is_done(this, unit); i++) + INTERPOSE_NEXT(process)(unit); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_training_sp_hook, process); + +struct military_training_id_hook : df::activity_event_individual_skill_drillst { + typedef df::activity_event_individual_skill_drillst interpose_base; + + DEFINE_VMETHOD_INTERPOSE(void, process, (df::unit *unit)) + { + INTERPOSE_NEXT(process)(unit); + + // Since there are no counters to fix, repeat the call + int cnt = (DF_GLOBAL_FIELD(ui, unit_action_divisor, 10)+5) / 10; + for (int i = 1; i < cnt && !is_done(this, unit); i++) + INTERPOSE_NEXT(process)(unit); + } +}; + +IMPLEMENT_VMETHOD_INTERPOSE(military_training_id_hook, process); + static void enable_hook(color_ostream &out, VMethodInterposeLinkBase &hook, vector ¶meters) { if (vector_get(parameters, 1) == "disable") @@ -835,6 +1091,13 @@ static command_result tweak(color_ostream &out, vector ¶meters) { enable_hook(out, INTERPOSE_HOOK(military_assign_hook, render), parameters); } + else if (cmd == "military-training") + { + enable_hook(out, INTERPOSE_HOOK(military_training_ct_hook, process), parameters); + enable_hook(out, INTERPOSE_HOOK(military_training_sd_hook, process), parameters); + enable_hook(out, INTERPOSE_HOOK(military_training_sp_hook, process), parameters); + enable_hook(out, INTERPOSE_HOOK(military_training_id_hook, process), parameters); + } else return CR_WRONG_USAGE; diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 6ba45cfd3..05fdca55b 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -293,6 +293,7 @@ typedef std::map, bool> TMaterialCache; struct ItemConstraint { PersistentDataItem config; + PersistentDataItem history; // Fixed key parsed into fields bool is_craft; @@ -308,7 +309,7 @@ struct ItemConstraint { int weight; std::vector jobs; - int item_amount, item_count, item_inuse; + int item_amount, item_count, item_inuse_amount, item_inuse_count; bool request_suspend, request_resume; bool is_active, cant_resume_reported; @@ -318,7 +319,7 @@ struct ItemConstraint { public: ItemConstraint() : is_craft(false), min_quality(item_quality::Ordinary), is_local(false), - weight(0), item_amount(0), item_count(0), item_inuse(0), + weight(0), item_amount(0), item_count(0), item_inuse_amount(0), item_inuse_count(0), is_active(false), cant_resume_reported(false) {} @@ -326,9 +327,8 @@ public: void setGoalCount(int v) { config.ival(0) = v; } int goalGap() { - int cval = (config.ival(1) <= 0) ? 5 : config.ival(1); - int cmax = std::max(goalCount()-5, goalCount()/2); - return std::max(1, std::min(cmax, cval)); + int cval = (config.ival(1) <= 0) ? std::min(5,goalCount()/2) : config.ival(1); + return std::max(1, std::min(goalCount()-1, cval)); } void setGoalGap(int v) { config.ival(1) = v; } @@ -353,6 +353,44 @@ public: request_resume = (size <= goalCount()-goalGap()); request_suspend = (size >= goalCount()); } + + static const size_t int28_size = PersistentDataItem::int28_size; + static const size_t hist_entry_size = PersistentDataItem::int28_size * 4; + + size_t history_size() { + return history.data_size() / hist_entry_size; + } + size_t history_base(int idx) { + size_t hsize = history_size(); + return ((history.ival(0)+hsize-idx) % hsize) * hist_entry_size; + } + int history_count(int idx) { + return history.get_int28(history_base(idx) + 0*int28_size); + } + int history_amount(int idx) { + return history.get_int28(history_base(idx) + 1*int28_size); + } + int history_inuse_count(int idx) { + return history.get_int28(history_base(idx) + 2*int28_size); + } + int history_inuse_amount(int idx) { + return history.get_int28(history_base(idx) + 3*int28_size); + } + + void updateHistory() + { + size_t buffer_size = history_size(); + if (buffer_size < 28) + history.ensure_data(hist_entry_size*buffer_size++, hist_entry_size); + history.ival(0) = (history.ival(0)+1) % buffer_size; + + size_t base = history.ival(0) * hist_entry_size; + + history.set_int28(base + 0*int28_size, item_count); + history.set_int28(base + 1*int28_size, item_amount); + history.set_int28(base + 2*int28_size, item_inuse_count); + history.set_int28(base + 3*int28_size, item_inuse_amount); + } }; /****************************** @@ -446,7 +484,7 @@ static void cleanup_state(color_ostream &out) } static void check_lost_jobs(color_ostream &out, int ticks); -static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg = NULL); +static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg = NULL, bool create = true); static void start_protect(color_ostream &out) { @@ -650,6 +688,9 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) update_job_data(out); process_constraints(out); + + for (size_t i = 0; i < constraints.size(); i++) + constraints[i]->updateHistory(); } } @@ -660,7 +701,11 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) * ITEM COUNT CONSTRAINT * ******************************/ -static ItemConstraint *get_constraint(color_ostream &out, const std::string &str, PersistentDataItem *cfg) +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; split_string(&tokens, str, "/"); @@ -683,7 +728,7 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str if (item.subtype >= 0) weight += 10000; - df::dfhack_material_category mat_mask; + df::dfhack_material_category mat_mask(0); std::string maskstr = vector_get(tokens,1); if (!maskstr.empty() && !parseJobMaterialCategory(&mat_mask, maskstr)) { out.printerr("Cannot decode material mask: %s\n", maskstr.c_str()); @@ -757,6 +802,9 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str return ct; } + if (!create) + return NULL; + ItemConstraint *nct = new ItemConstraint; nct->is_craft = is_craft; nct->item = item; @@ -774,6 +822,8 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str nct->init(str); } + nct->history = World::GetPersistentData(history_key(nct->config), NULL); + constraints.push_back(nct); return nct; } @@ -785,6 +835,7 @@ static void delete_constraint(ItemConstraint *cv) vector_erase_at(constraints, idx); World::DeletePersistentData(cv->config); + World::DeletePersistentData(cv->history); delete cv; } @@ -1062,7 +1113,8 @@ static void map_job_items(color_ostream &out) { constraints[i]->item_amount = 0; constraints[i]->item_count = 0; - constraints[i]->item_inuse = 0; + constraints[i]->item_inuse_amount = 0; + constraints[i]->item_inuse_count = 0; } meltable_count = 0; @@ -1074,7 +1126,7 @@ static void map_job_items(color_ostream &out) #define F(x) bad_flags.bits.x = true; F(dump); F(forbid); F(garbage_collect); F(hostile); F(on_fire); F(rotten); F(trader); - F(in_building); F(construction); F(artifact1); + F(in_building); F(construction); F(artifact); #undef F bool dry_buckets = isOptionEnabled(CF_DRYBUCKETS); @@ -1095,9 +1147,9 @@ static void map_job_items(color_ostream &out) bool is_invalid = false; - // don't count worn items - if (item->getWear() >= 1) - is_invalid = true; + // don't count worn items + if (item->getWear() >= 1) + is_invalid = true; // Special handling switch (itype) { @@ -1175,7 +1227,8 @@ static void map_job_items(color_ostream &out) isAssignedSquad(item)) { is_invalid = true; - cv->item_inuse++; + cv->item_inuse_count++; + cv->item_inuse_amount += item->getStackSize(); } else { @@ -1346,7 +1399,9 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) Lua::SetField(L, cv->is_craft, ctable, "is_craft"); + lua_getglobal(L, "copyall"); Lua::PushDFObject(L, &cv->mat_mask); + lua_call(L, 1, 1); lua_setfield(L, -2, "mat_mask"); Lua::SetField(L, cv->material.type, ctable, "mat_type"); @@ -1363,7 +1418,8 @@ static void push_constraint(lua_State *L, ItemConstraint *cv) Lua::SetField(L, cv->item_amount, ctable, "cur_amount"); Lua::SetField(L, cv->item_count, ctable, "cur_count"); - Lua::SetField(L, cv->item_inuse, ctable, "cur_in_use"); + Lua::SetField(L, cv->item_inuse_amount, ctable, "cur_in_use_amount"); + Lua::SetField(L, cv->item_inuse_count, ctable, "cur_in_use_count"); // Current state value @@ -1417,6 +1473,22 @@ static int listConstraints(lua_State *L) return 1; } +static int findConstraint(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_constraint(L, icv); + else + lua_pushnil(L); + return 1; +} + static int setConstraint(lua_State *L) { auto token = luaL_checkstring(L, 1); @@ -1425,6 +1497,7 @@ static int setConstraint(lua_State *L) int gap = luaL_optint(L, 4, -1); color_ostream &out = *Lua::GetOutput(L); + update_data_structures(out); ItemConstraint *icv = get_constraint(out, token); if (!icv) @@ -1442,6 +1515,40 @@ static int setConstraint(lua_State *L) return 1; } +static int getCountHistory(lua_State *L) +{ + auto token = luaL_checkstring(L, 1); + + color_ostream &out = *Lua::GetOutput(L); + update_data_structures(out); + + ItemConstraint *icv = get_constraint(out, token, NULL, false); + + if (icv) + { + size_t hsize = icv->history_size(); + + lua_createtable(L, hsize, 0); + + for (int i = hsize-1; i >= 0; i--) + { + lua_createtable(L, 0, 4); + + Lua::SetField(L, icv->history_amount(i), -1, "cur_amount"); + Lua::SetField(L, icv->history_count(i), -1, "cur_count"); + Lua::SetField(L, icv->history_inuse_amount(i), -1, "cur_in_use_amount"); + Lua::SetField(L, icv->history_inuse_count(i), -1, "cur_in_use_count"); + + lua_rawseti(L, -2, hsize-i); // reverse order + } + } + else + lua_pushnil(L); + + return 1; +} + + DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_LUA_FUNCTION(isEnabled), DFHACK_LUA_FUNCTION(setEnabled), @@ -1451,7 +1558,9 @@ DFHACK_PLUGIN_LUA_FUNCTIONS { DFHACK_PLUGIN_LUA_COMMANDS { DFHACK_LUA_COMMAND(listConstraints), + DFHACK_LUA_COMMAND(findConstraint), DFHACK_LUA_COMMAND(setConstraint), + DFHACK_LUA_COMMAND(getCountHistory), DFHACK_LUA_END }; @@ -1499,10 +1608,10 @@ static void print_constraint(color_ostream &out, ItemConstraint *cv, bool no_job << cv->goalCount() << " (gap " << cv->goalGap() << ")" << endl; out.reset_color(); - if (cv->item_count || cv->item_inuse) + if (cv->item_count || cv->item_inuse_count) out << prefix << " items: amount " << cv->item_amount << "; " << cv->item_count << " stacks available, " - << cv->item_inuse << " in use." << endl; + << cv->item_inuse_count << " in use." << endl; if (no_job) return; diff --git a/plugins/zone.cpp b/plugins/zone.cpp index 15d310dc1..dff37be0f 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -1,12 +1,12 @@ // Intention: help with activity zone management (auto-pasture animals, auto-pit goblins, ...) -// +// // the following things would be nice: // - dump info about pastures, pastured animals, count non-pastured tame animals, print gender info // - help finding caged dwarves? (maybe even allow to build their cages for fast release) // - dump info about caged goblins, animals, ... // - count grass tiles on pastures, move grazers to new pasture if old pasture is empty // move hungry unpastured grazers to pasture with grass -// +// // What is working so far: // - print detailed info about activity zone and units under cursor (mostly for checking refs and stuff) // - mark a zone which is used for future assignment commands @@ -287,7 +287,7 @@ static PersistentDataItem config_autonestbox; DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { - switch (event) + switch (event) { case DFHack::SC_MAP_LOADED: // initialize from the world just loaded @@ -320,7 +320,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) autoNestbox(out, false); } } - + if(enable_autobutcher) { if(++ticks_autobutcher >= sleep_autobutcher) @@ -630,7 +630,7 @@ bool isTamable(df::unit* unit) { df::caste_raw *caste = raw->caste[j]; if(caste->flags.is_set(caste_raw_flags::PET) || - caste->flags.is_set(caste_raw_flags::PET_EXOTIC)) + caste->flags.is_set(caste_raw_flags::PET_EXOTIC)) return true; } return false; @@ -652,8 +652,8 @@ bool isFemale(df::unit* unit) bool hasValidMapPos(df::unit* unit) { if( unit->pos.x >=0 && unit->pos.y >= 0 && unit->pos.z >= 0 - && unit->pos.x < world->map.x_count - && unit->pos.y < world->map.y_count + && unit->pos.x < world->map.x_count + && unit->pos.y < world->map.y_count && unit->pos.z < world->map.z_count) return true; else @@ -662,7 +662,7 @@ bool hasValidMapPos(df::unit* unit) bool isNaked(df::unit* unit) { - return (unit->inventory.empty()); + return (unit->inventory.empty()); } @@ -694,7 +694,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << " '" << unit->name.nickname << "'"; out << ", "; } - + if(isAdult(unit)) out << "adult"; else if(isBaby(unit)) @@ -702,7 +702,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) else if(isChild(unit)) out << "child"; out << " "; - // sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca) + // sometimes empty even in vanilla RAWS, sometimes contains full race name (e.g. baby alpaca) // all animals I looked at don't have babies anyways, their offspring starts as CHILD //out << getRaceBabyName(unit); //out << getRaceChildName(unit); @@ -725,7 +725,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", age: " << getUnitAge(unit); if(isTame(unit)) - out << ", tame"; + out << ", tame"; if(isOwnCiv(unit)) out << ", owned"; if(isWar(unit)) @@ -742,7 +742,7 @@ void unitInfo(color_ostream & out, df::unit* unit, bool verbose = false) out << ", grazer"; if(isMilkable(unit)) out << ", milkable"; - + if(verbose) { out << ". Pos: ("<pos.x << "/"<< unit->pos.y << "/" << unit->pos.z << ") " << endl; @@ -820,7 +820,7 @@ bool isPitPond(df::building * building) if(civ->zone_flags.bits.pit_pond) // && civ->pit_flags==0) return true; - else + else return false; } @@ -879,7 +879,7 @@ df::unit* findUnitById(int32_t id) int32_t findPenPitAtCursor() { int32_t foundID = -1; - + if(cursor->x == -30000) return -1; @@ -906,7 +906,7 @@ int32_t findPenPitAtCursor() int32_t findCageAtCursor() { int32_t foundID = -1; - + if(cursor->x == -30000) return -1; @@ -935,7 +935,7 @@ int32_t findCageAtCursor() int32_t findChainAtCursor() { int32_t foundID = -1; - + if(cursor->x == -30000) return -1; @@ -974,7 +974,7 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef() // being called for the first time, need to initialize the vtable for(size_t i = 0; i < world->units.all.size(); i++) { - df::unit * creature = world->units.all[i]; + df::unit * creature = world->units.all[i]; for(size_t r = 0; rgeneral_refs.size(); r++) { df::general_ref* ref; @@ -984,7 +984,7 @@ df::general_ref_building_civzone_assignedst * createCivzoneRef() if (strict_virtual_cast(ref)) { // !! calling new() doesn't work, need _identity.instantiate() instead !! - newref = (df::general_ref_building_civzone_assignedst*) + newref = (df::general_ref_building_civzone_assignedst*) df::general_ref_building_civzone_assignedst::_identity.instantiate(); vt_initialized = true; break; @@ -1090,13 +1090,13 @@ bool isInBuiltCageRoom(df::unit* unit) for (size_t b=0; b < world->buildings.all.size(); b++) { df::building* building = world->buildings.all[b]; - + // !!! building->isRoom() returns true if the building can be made a room but currently isn't // !!! except for coffins/tombs which always return false // !!! using the bool is_room however gives the correct state/value if(!building->is_room) continue; - + if(building->getType() == building_type::Cage) { df::building_cagest* cage = (df::building_cagest*) building; @@ -1118,7 +1118,7 @@ bool isInBuiltCageRoom(df::unit* unit) // check a map position for a built cage // animals in cages are CONTAINED_IN_ITEM, no matter if they are on a stockpile or inside a built cage // if they are on animal stockpiles they should count as unassigned to allow pasturing them -// if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever +// if they are inside built cages they should be ignored in case the cage is a zoo or linked to a lever or whatever bool isBuiltCageAtPos(df::coord pos) { bool cage = false; @@ -1232,7 +1232,7 @@ bool isFreeEgglayer(df::unit * unit) { if( !isDead(unit) && !isUndead(unit) && isFemale(unit) - && isTame(unit) + && isTame(unit) && isOwnCiv(unit) && isEggLayer(unit) && !isAssigned(unit) @@ -1306,7 +1306,7 @@ bool unassignUnitFromBuilding(df::unit* unit) { // game does not erase the ref until creature gets removed from cage //unit->general_refs.erase(unit->general_refs.begin() + idx); - + // walk through buildings, check cages for inhabitants, compare ids for (size_t b=0; b < world->buildings.all.size(); b++) { @@ -1379,7 +1379,7 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building << "before using 'assign' for the first time." << endl; return CR_WRONG_USAGE; } - + // check if unit is already pastured, remove that ref from unit and old pasture // testing showed that removing the ref from the unit only seems to be necessary for pastured creatures // if they are in cages on stockpiles the game unassigns them automatically @@ -1401,8 +1401,8 @@ command_result assignUnitToZone(color_ostream& out, df::unit* unit, df::building df::building_civzonest * civz = (df::building_civzonest *) building; civz->assigned_creature.push_back(unit->id); - out << "Unit " << unit->id - << "(" << getRaceName(unit) << ")" + out << "Unit " << unit->id + << "(" << getRaceName(unit) << ")" << " assigned to zone " << building->id; if(isPitPond(building)) out << " (pit/pond)."; @@ -1425,7 +1425,7 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building // don't assign owned pets to a cage. the owner will release them, resulting into infinite hauling (df bug) if(unit->relations.pet_owner_id != -1) return CR_OK; - + // check if unit is already pastured or caged, remove refs where necessary bool cleared_old = unassignUnitFromBuilding(unit); if(verbose) @@ -1442,8 +1442,8 @@ command_result assignUnitToCage(color_ostream& out, df::unit* unit, df::building df::building_cagest* civz = (df::building_cagest*) building; civz->assigned_creature.push_back(unit->id); - out << "Unit " << unit->id - << "(" << getRaceName(unit) << ")" + out << "Unit " << unit->id + << "(" << getRaceName(unit) << ")" << " assigned to cage " << building->id; out << endl; @@ -1631,7 +1631,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) out << " (pit flags:" << civ->pit_flags.whole << ")"; if(civ->pit_flags.bits.is_pond) out << ", pond"; - else + else out << ", pit"; } out << endl; @@ -1654,7 +1654,7 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) df::unit * creature = world->units.all[i]; if(creature->id != cindex) continue; - + unitInfo(out, creature, verbose); } } @@ -1694,7 +1694,7 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose) df::unit * creature = world->units.all[i]; if(creature->id != cindex) continue; - + unitInfo(out, creature, verbose); } } @@ -1737,7 +1737,7 @@ command_result df_zone (color_ostream &out, vector & parameters) bool zone_info = false; //bool cage_info = false; //bool chain_info = false; - + bool invert_filter = false; bool find_unassigned = false; bool find_caged = false; @@ -1763,17 +1763,17 @@ command_result df_zone (color_ostream &out, vector & parameters) bool find_female = false; bool find_not_female = false; bool find_egglayer = false; - bool find_not_egglayer = false; + bool find_not_egglayer = false; bool find_grazer = false; bool find_not_grazer = false; bool find_milkable = false; bool find_not_milkable = false; bool find_named = false; bool find_not_named = false; - bool find_naked = false; - bool find_not_naked = false; - bool find_tamable = false; - bool find_not_tamable = false; + bool find_naked = false; + bool find_not_naked = false; + bool find_tamable = false; + bool find_not_tamable = false; bool find_agemin = false; bool find_agemax = false; @@ -1801,7 +1801,7 @@ command_result df_zone (color_ostream &out, vector & parameters) for (size_t i = 0; i < parameters.size(); i++) { string & p = parameters[i]; - + if (p == "help" || p == "?") { out << zone_help << endl; @@ -2388,7 +2388,7 @@ command_result df_zone (color_ostream &out, vector & parameters) df::unit *unit = world->units.all[c]; // ignore dead and undead units - if (isDead(unit) || isUndead(unit)) + if (isDead(unit) || isUndead(unit)) continue; // ignore merchant units by default @@ -2432,7 +2432,7 @@ command_result df_zone (color_ostream &out, vector & parameters) || (find_grazer && !isGrazer(unit)) || (find_not_grazer && isGrazer(unit)) || (find_egglayer && !isEggLayer(unit)) - || (find_not_egglayer && isEggLayer(unit)) + || (find_not_egglayer && isEggLayer(unit)) || (find_milkable && !isMilkable(unit)) || (find_not_milkable && isMilkable(unit)) || (find_male && !isMale(unit)) @@ -2441,10 +2441,10 @@ command_result df_zone (color_ostream &out, vector & parameters) || (find_not_female && isFemale(unit)) || (find_named && !unit->name.has_name) || (find_not_named && unit->name.has_name) - || (find_naked && !isNaked(unit)) - || (find_not_naked && isNaked(unit)) - || (find_tamable && !isTamable(unit)) - || (find_not_tamable && isTamable(unit)) + || (find_naked && !isNaked(unit)) + || (find_not_naked && isNaked(unit)) + || (find_tamable && !isTamable(unit)) + || (find_not_tamable && isTamable(unit)) || (find_trainable_war && (isWar(unit) || isHunter(unit) || !isTrainableWar(unit))) || (find_not_trainable_war && isTrainableWar(unit)) // hm, is this check enough? || (find_trainable_hunting && (isWar(unit) || isHunter(unit) || !isTrainableHunting(unit))) @@ -2471,7 +2471,7 @@ command_result df_zone (color_ostream &out, vector & parameters) unitInfo(out, unit, verbose); continue; } - + if(nick_set) { Units::setNickname(unit, target_nick); @@ -2540,8 +2540,8 @@ command_result df_zone (color_ostream &out, vector & parameters) } // de-assign from pen or pit - // using the zone tool to free creatures from cages or chains - // is pointless imo since that is already quite easy using the ingame UI. + // using the zone tool to free creatures from cages or chains + // is pointless imo since that is already quite easy using the ingame UI. // but it's easy to implement so I might as well add it later if(building_unassign) { @@ -2555,7 +2555,7 @@ command_result df_zone (color_ostream &out, vector & parameters) out << "Unit unassigned." << endl; else out << "Unit is not assigned to an activity zone!" << endl; - + return CR_OK; } @@ -2574,7 +2574,7 @@ command_result df_autonestbox(color_ostream &out, vector & parameters) for (size_t i = 0; i < parameters.size(); i++) { string & p = parameters[i]; - + if (p == "help" || p == "?") { out << autonestbox_help << endl; @@ -2687,7 +2687,7 @@ command_result autoNestbox( color_ostream &out, bool verbose = false ) Gui::showAnnouncement(announce, 2, false); out << announce << endl; // can complain again - // (might lead to spamming the same message twice, but catches the case + // (might lead to spamming the same message twice, but catches the case // where for example 2 new egglayers hatched right after 2 zones were created and assigned) autonestbox_did_complain = false; } @@ -2699,7 +2699,7 @@ command_result autoNestbox( color_ostream &out, bool verbose = false ) // getUnitAge() returns 0 if born in current year, therefore the look at birth_time in that case // (assuming that the value from there indicates in which tick of the current year the unit was born) -bool compareUnitAgesYounger(df::unit* i, df::unit* j) +bool compareUnitAgesYounger(df::unit* i, df::unit* j) { int32_t age_i = getUnitAge(i); int32_t age_j = getUnitAge(j); @@ -2708,10 +2708,10 @@ bool compareUnitAgesYounger(df::unit* i, df::unit* j) age_i = i->relations.birth_time; age_j = j->relations.birth_time; } - return (age_i < age_j); + return (age_i < age_j); } -bool compareUnitAgesOlder(df::unit* i, df::unit* j) -{ +bool compareUnitAgesOlder(df::unit* i, df::unit* j) +{ int32_t age_i = getUnitAge(i); int32_t age_j = getUnitAge(j); if(age_i == 0 && age_j == 0) @@ -2719,7 +2719,7 @@ bool compareUnitAgesOlder(df::unit* i, df::unit* j) age_i = i->relations.birth_time; age_j = j->relations.birth_time; } - return (age_i > age_j); + return (age_i > age_j); } //enum WatchedRaceSubtypes @@ -2748,7 +2748,7 @@ public: vector mk_ptr; vector fa_ptr; vector ma_ptr; - + WatchedRace(bool watch, int id, int _fk, int _mk, int _fa, int _ma) { isWatched = watch; @@ -2786,8 +2786,8 @@ public: string keyname = "autobutcher/watchlist/" + getRaceName(raceId); out << "Something failed, could not find/create config key " << keyname << "!" << endl; } - } - + } + void RemoveConfig(color_ostream & out) { if(!rconfig.isValid()) @@ -2802,7 +2802,7 @@ public: sort(fa_ptr.begin(), fa_ptr.end(), compareUnitAgesYounger); sort(ma_ptr.begin(), ma_ptr.end(), compareUnitAgesYounger); } - + void PushUnit(df::unit * unit) { if(isFemale(unit)) @@ -2894,7 +2894,7 @@ public: } }; // vector of races handled by autobutcher -// the name is a bit misleading since entries can be set to 'unwatched' +// the name is a bit misleading since entries can be set to 'unwatched' // to ignore them for a while but still keep the target count settings std::vector watched_races; @@ -2933,7 +2933,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) } // parse main command - string & p = parameters[0]; + string & p = parameters[0]; if (p == "help" || p == "?") { out << autobutcher_help << endl; @@ -3058,19 +3058,19 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) if(list_watched) { out << "Autobutcher status: "; - + if(enable_autobutcher) out << "enabled,"; else out << "not enabled,"; - + if (enable_autobutcher_autowatch) out << " autowatch,"; else out << " noautowatch,"; - + out << " sleep: " << sleep_autobutcher << endl; - + out << "Default setting for new races:" << " fk=" << default_fk << " mk=" << default_mk @@ -3094,7 +3094,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) out << "watched: "; else out << "not watched: "; - out << name + out << name << " fk=" << w->fk << " mk=" << w->mk << " fa=" << w->fa @@ -3114,7 +3114,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) out << run << "start" << endl; if(!enable_autobutcher) - out << run << "stop" << endl; + out << run << "stop" << endl; if (enable_autobutcher_autowatch) out << run << "autowatch" << endl; @@ -3147,8 +3147,8 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) } // parse rest of parameters for commands followed by a list of races - if( watch_race - || unwatch_race + if( watch_race + || unwatch_race || forget_race || change_target ) { @@ -3226,7 +3226,7 @@ command_result df_autobutcher(color_ostream &out, vector & parameters) else { // map race names from parameter list to ids - size_t num_races = df::global::world->raws.creatures.all.size(); + size_t num_races = df::global::world->raws.creatures.all.size(); while(target_racenames.size()) { bool found_race = false; @@ -3345,7 +3345,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) || isHunter(unit) // ignore hunting dogs etc // ignore creatures in built cages which are defined as rooms to leave zoos alone // (TODO: better solution would be to allow some kind of slaughter cages which you can place near the butcher) - || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() + || (isContainedInItem(unit) && isInBuiltCageRoom(unit)) // !!! see comments in isBuiltCageRoom() || unit->name.has_name ) continue; @@ -3372,7 +3372,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) string announce; announce = "New race added to autobutcher watchlist: " + getRaceName(w->raceId); Gui::showAnnouncement(announce, 2, false); - //out << announce << endl; + //out << announce << endl; } } @@ -3389,7 +3389,7 @@ command_result autoButcher( color_ostream &out, bool verbose = false ) string announce; announce = getRaceName(w->raceId) + " marked for slaughter: " + ss.str(); Gui::showAnnouncement(announce, 2, false); - //out << announce << endl; + //out << announce << endl; } } //out << slaughter_count << " units total marked for slaughter." << endl; @@ -3425,13 +3425,13 @@ command_result start_autobutcher(color_ostream &out) config_autobutcher.ival(0) = enable_autobutcher; out << "Starting autobutcher." << endl; - init_autobutcher(out); + init_autobutcher(out); return CR_OK; } command_result init_autobutcher(color_ostream &out) { - cleanup_autobutcher(out); + cleanup_autobutcher(out); config_autobutcher = World::GetPersistentData("autobutcher/config"); if(config_autobutcher.isValid()) @@ -3466,9 +3466,9 @@ command_result init_autobutcher(color_ostream &out) std::vector items; World::GetPersistentData(&items, "autobutcher/watchlist/", true); - for (auto p = items.begin(); p != items.end(); p++) - { - string key = p->key(); + for (auto p = items.begin(); p != items.end(); p++) + { + string key = p->key(); out << "Reading from save: " << key << endl; //out << " raceid: " << p->ival(0) << endl; //out << " watched: " << p->ival(1) << endl; @@ -3476,7 +3476,7 @@ command_result init_autobutcher(color_ostream &out) //out << " mk: " << p->ival(3) << endl; //out << " fa: " << p->ival(4) << endl; //out << " ma: " << p->ival(5) << endl; - + WatchedRace * w = new WatchedRace(p->ival(1), p->ival(0), p->ival(2), p->ival(3),p->ival(4),p->ival(5)); w->rconfig = *p; watched_races.push_back(w); @@ -3514,13 +3514,13 @@ command_result start_autonestbox(color_ostream &out) config_autonestbox.ival(0) = enable_autonestbox; out << "Starting autonestbox." << endl; - init_autonestbox(out); + init_autonestbox(out); return CR_OK; } command_result init_autonestbox(color_ostream &out) { - cleanup_autonestbox(out); + cleanup_autonestbox(out); config_autonestbox = World::GetPersistentData("autonestbox/config"); if(config_autonestbox.isValid()) diff --git a/scripts/autofarm.rb b/scripts/autofarm.rb new file mode 100644 index 000000000..cd381089e --- /dev/null +++ b/scripts/autofarm.rb @@ -0,0 +1,152 @@ +class AutoFarm + + def initialize + @thresholds = Hash.new(50) + @lastcounts = Hash.new(0) + end + + def setthreshold(id, v) + if df.world.raws.plants.all.find { |r| r.id == id } + @thresholds[id] = v.to_i + else + puts "No plant with id #{id}" + end + end + + def setdefault(v) + @thresholds.default = v.to_i + end + + def is_plantable(plant) + season = df.cur_season + harvest = df.cur_season_tick + plant.growdur * 10 + will_finish = harvest < 10080 + can_plant = plant.flags[season] + can_plant = can_plant && (will_finish || plant.flags[(season+1)%4]) + can_plant + end + + def find_plantable_plants + plantable = {} + for i in 0..df.ui.tasks.known_plants.length-1 + if df.ui.tasks.known_plants[i] + plant = df.world.raws.plants.all[i] + if is_plantable(plant) + plantable[i] = :Surface if (plant.underground_depth_min == 0 || plant.underground_depth_max == 0) + plantable[i] = :Underground if (plant.underground_depth_min > 0 || plant.underground_depth_max > 0) + end + end + end + return plantable + end + + def set_farms( plants, farms) + return if farms.length == 0 + if plants.length == 0 + plants = [-1] + end + + season = df.cur_season + + idx = 0 + + farms.each { |f| + f.plant_id[season] = plants[idx] + idx = (idx + 1) % plants.length + } + end + + def process + return false unless @running + + plantable = find_plantable_plants + counts = Hash.new(0) + + df.world.items.other[:PLANT].each { |i| + 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.artifact && plantable.has_key?(i.mat_index)) + counts[i.mat_index] = counts[i.mat_index] + i.stack_size + end + } + + plants_s = [] + plants_u = [] + + @lastcounts.clear + + plantable.each_key { |k| + plant = df.world.raws.plants.all[k] + if (counts[k] < @thresholds[plant.id]) + plants_s.push(k) if plantable[k] == :Surface + plants_u.push(k) if plantable[k] == :Underground + end + @lastcounts[plant.id] = counts[k] + } + + farms_s = [] + farms_u = [] + df.world.buildings.other[:FARM_PLOT].each { |f| + if (f.flags.exists) + outside = df.map_designation_at(f.centerx,f.centery,f.z).outside + farms_s.push(f) if outside + farms_u.push(f) unless outside + end + } + + set_farms(plants_s, farms_s) + set_farms(plants_u, farms_u) + + end + + def start + @onupdate = df.onupdate_register('autofarm', 100) { process } + @running = true + end + + def stop + df.onupdate_unregister(@onupdate) + @running = false + end + + def status + stat = @running ? "Running." : "Stopped." + @thresholds.each { |k,v| + stat += "\n#{k} limit #{v} current #{@lastcounts[k]}" + } + stat += "\nDefault: #{@thresholds.default}" + stat + end + +end + +$AutoFarm = AutoFarm.new unless $AutoFarm + +case $script_args[0] +when 'start' + $AutoFarm.start + +when 'end', 'stop' + $AutoFarm.stop + +when 'default' + $AutoFarm.setdefault($script_args[1]) + +when 'threshold' + t = $script_args[1] + $script_args[2..-1].each {|i| + $AutoFarm.setthreshold(i, t) + } + +when 'delete' + $AutoFarm.stop + $AutoFarm = nil + +else + if $AutoFarm + puts $AutoFarm.status + else + puts "AI not started" + end +end diff --git a/scripts/autounsuspend.rb b/scripts/autounsuspend.rb new file mode 100644 index 000000000..c7fe20748 --- /dev/null +++ b/scripts/autounsuspend.rb @@ -0,0 +1,58 @@ +class AutoUnsuspend + + def initialize + end + + def process + return false unless @running + joblist = df.world.job_list.next + count = 0 + + while joblist + job = joblist.item + joblist = joblist.next + + if job.job_type == :ConstructBuilding + if (job.flags.suspend) + item = job.items[0].item + job.flags.suspend = false + count += 1 + end + end + end + + puts "Unsuspended #{count} job(s)." unless count == 0 + + end + + def start + @onupdate = df.onupdate_register('autounsuspend', 5) { process } + @running = true + end + + def stop + df.onupdate_unregister(@onupdate) + @running = false + end + + def status + @running ? 'Running.' : 'Stopped.' + end + +end + +case $script_args[0] +when 'start' + $AutoUnsuspend = AutoUnsuspend.new unless $AutoUnsuspend + $AutoUnsuspend.start + +when 'end', 'stop' + $AutoUnsuspend.stop + +else + if $AutoUnsuspend + puts $AutoUnsuspend.status + else + puts 'Not loaded.' + end +end diff --git a/scripts/gui/workflow.lua b/scripts/gui/workflow.lua index 84540b5ca..80c05d296 100644 --- a/scripts/gui/workflow.lua +++ b/scripts/gui/workflow.lua @@ -43,9 +43,356 @@ function check_repeat(job, cb) end end +function describe_item_type(iobj) + local itemline = 'any item' + if iobj.is_craft then + itemline = 'any craft' + elseif iobj.item_type >= 0 then + itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type + local subtype = iobj.item_subtype or -1 + local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype) + local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype) + if def then + itemline = def.name + elseif count >= 0 then + itemline = 'any '..itemline + end + end + return itemline +end + +function is_caste_mat(iobj) + return dfhack.items.isCasteMaterial(iobj.item_type or -1) +end + +function describe_material(iobj) + local matline = 'any material' + if is_caste_mat(iobj) then + matline = 'no material' + elseif (iobj.mat_type or -1) >= 0 then + local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) + if info then + matline = info:toString() + else + matline = iobj.mat_type..':'..iobj.mat_index + end + end + return matline +end + +local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } + +RangeEditor = defclass(RangeEditor, widgets.Label) + +RangeEditor.ATTRS { + get_cb = DEFAULT_NIL, + save_cb = DEFAULT_NIL +} + +function RangeEditor:init(args) + self:setText{ + { key = 'BUILDING_TRIGGER_ENABLE_CREATURE', + text = function() + local cons = self.get_cb() or null_cons + if cons.goal_by_count then + return ': Count stacks ' + else + return ': Count items ' + end + end, + on_activate = self:callback('onChangeUnit') }, + { key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify', + on_activate = self:callback('onEditRange') }, + NEWLINE, ' ', + { key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', + on_activate = self:callback('onIncRange', 'goal_gap', 5) }, + { key = 'BUILDING_TRIGGER_MIN_SIZE_UP', + on_activate = self:callback('onIncRange', 'goal_gap', -1) }, + { text = function() + local cons = self.get_cb() or null_cons + return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) + end }, + { key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', + on_activate = self:callback('onIncRange', 'goal_value', -1) }, + { key = 'BUILDING_TRIGGER_MAX_SIZE_UP', + on_activate = self:callback('onIncRange', 'goal_value', 5) }, + { text = function() + local cons = self.get_cb() or null_cons + return string.format(': Max %-4d', cons.goal_value) + end }, + } +end + +function RangeEditor:onChangeUnit() + local cons = self.get_cb() + cons.goal_by_count = not cons.goal_by_count + self.save_cb(cons) +end + +function RangeEditor:onEditRange() + local cons = self.get_cb() + dlg.showInputPrompt( + 'Input Range', + 'Enter the new constraint range:', + COLOR_WHITE, + (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value, + function(text) + local maxv = string.match(text, '^%s*(%d+)%s*$') + if maxv then + cons.goal_value = maxv + return self.save_cb(cons) + end + local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$') + if minv and maxv and minv ~= maxv then + cons.goal_value = math.max(minv,maxv) + cons.goal_gap = math.abs(maxv-minv) + return self.save_cb(cons) + end + dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED) + end + ) +end + +function RangeEditor:onIncRange(field, delta) + local cons = self.get_cb() + if not cons.goal_by_count then + delta = delta * 5 + end + cons[field] = math.max(1, cons[field] + delta) + self.save_cb(cons) +end + +NewConstraint = defclass(NewConstraint, gui.FramedScreen) + +NewConstraint.focus_path = 'workflow/new' + +NewConstraint.ATTRS { + frame_style = gui.GREY_LINE_FRAME, + frame_title = 'New workflow constraint', + frame_width = 39, + frame_height = 20, + frame_inset = 1, + constraint = DEFAULT_NIL, + on_submit = DEFAULT_NIL, +} + +function NewConstraint:init(args) + self.constraint = args.constraint or {} + rawset_default(self.constraint, { goal_value = 10, goal_gap = 5, goal_by_count = false }) + + local matlist = {} + local matsel = 1 + local matmask = self.constraint.mat_mask + + for i,v in ipairs(df.dfhack_material_category) do + if v and v ~= 'wood2' then + table.insert(matlist, { icon = self:callback('isMatSelected', v), text = v }) + if matmask and matmask[v] and matsel == 1 then + matsel = #matlist + end + end + end + + self:addviews{ + widgets.Label{ + frame = { l = 0, t = 0 }, + text = 'Items matching:' + }, + widgets.Label{ + frame = { l = 1, t = 2, w = 26 }, + text = { + 'Type: ', + { pen = COLOR_LIGHTCYAN, + text = function() return describe_item_type(self.constraint) end }, + NEWLINE, ' ', + { key = 'CUSTOM_T', text = ': Select, ', + on_activate = self:callback('chooseType') }, + { key = 'CUSTOM_SHIFT_C', text = ': Crafts', + on_activate = self:callback('chooseCrafts') }, + NEWLINE, NEWLINE, + 'Material: ', + { pen = COLOR_LIGHTCYAN, + text = function() return describe_material(self.constraint) end }, + NEWLINE, ' ', + { key = 'CUSTOM_P', text = ': Specific', + on_activate = self:callback('chooseMaterial') }, + NEWLINE, NEWLINE, + 'Other:', + NEWLINE, ' ', + { key = 'D_MILITARY_SUPPLIES_WATER_DOWN', + on_activate = self:callback('incQuality', -1) }, + { key = 'D_MILITARY_SUPPLIES_WATER_UP', key_sep = ': ', + text = function() + return df.item_quality[self.constraint.min_quality or 0]..' quality' + end, + on_activate = self:callback('incQuality', 1) }, + NEWLINE, ' ', + { key = 'CUSTOM_L', key_sep = ': ', + text = function() + if self.constraint.is_local then + return 'Locally made only' + else + return 'Include foreign' + end + end, + on_activate = self:callback('toggleLocal') }, + } + }, + widgets.Label{ + frame = { l = 0, t = 13 }, + text = { + 'Desired range: ', + { pen = COLOR_LIGHTCYAN, + text = function() + local cons = self.constraint + local goal = (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value + if cons.goal_by_count then + return goal .. ' stacks' + else + return goal .. ' items' + end + end }, + } + }, + RangeEditor{ + frame = { l = 1, t = 15 }, + get_cb = self:cb_getfield('constraint'), + save_cb = self:callback('onRangeChange'), + }, + widgets.Label{ + frame = { l = 30, t = 0 }, + text = 'Mat class' + }, + widgets.List{ + view_id = 'matlist', + frame = { l = 30, t = 2, w = 9, h = 18 }, + scroll_keys = widgets.STANDARDSCROLL, + choices = matlist, + selected = matsel, + on_submit = self:callback('onToggleMatclass') + }, + widgets.Label{ + frame = { l = 0, b = 0, w = 29 }, + text = { + { key = 'LEAVESCREEN', text = ': Cancel, ', + on_activate = self:callback('dismiss') }, + { key = 'MENU_CONFIRM', key_sep = ': ', + text = function() + if self.is_existing then return 'Update' else return 'Create new' end + end, + on_activate = function() + self:dismiss() + if self.on_submit then + self.on_submit(self.constraint) + end + end }, + } + }, + } +end + +function NewConstraint:postinit() + self:onChange() +end + +function NewConstraint:onChange() + local token = workflow.constraintToToken(self.constraint) + local out = workflow.findConstraint(token) + + if out then + self.constraint = out + self.is_existing = true + else + self.constraint.token = token + self.is_existing = false + end +end + +function NewConstraint:chooseType() + guimat.ItemTypeDialog{ + prompt = 'Please select a new item type', + hide_none = true, + on_select = function(itype,isub) + local cons = self.constraint + cons.item_type = itype + cons.item_subtype = isub + cons.is_craft = nil + self:onChange() + end + }:show() +end + +function NewConstraint:chooseCrafts() + local cons = self.constraint + cons.item_type = -1 + cons.item_subtype = -1 + cons.is_craft = true + self:onChange() +end + +function NewConstraint:chooseMaterial() + local cons = self.constraint + guimat.MaterialDialog{ + prompt = 'Please select a new material', + none_caption = 'any material', + frame_width = 37, + on_select = function(mat_type, mat_index) + local cons = self.constraint + cons.mat_type = mat_type + cons.mat_index = mat_index + cons.mat_mask = nil + self:onChange() + end + }:show() +end + +function NewConstraint:incQuality(diff) + local cons = self.constraint + local nq = (cons.min_quality or 0) + diff + if nq < 0 then + nq = df.item_quality.Masterful + elseif nq > df.item_quality.Masterful then + nq = 0 + end + cons.min_quality = nq + self:onChange() +end + +function NewConstraint:toggleLocal() + local cons = self.constraint + cons.is_local = not cons.is_local + self:onChange() +end + +function NewConstraint:isMatSelected(token) + if self.constraint.mat_mask and self.constraint.mat_mask[token] then + return { ch = '\xfb', fg = COLOR_LIGHTGREEN } + else + return nil + end +end + +function NewConstraint:onToggleMatclass(idx,obj) + local cons = self.constraint + if cons.mat_mask and cons.mat_mask[obj.text] then + cons.mat_mask[obj.text] = false + else + cons.mat_mask = cons.mat_mask or {} + cons.mat_mask[obj.text] = true + cons.mat_type = -1 + cons.mat_index = -1 + end + self:onChange() +end + +function NewConstraint:onRangeChange() + local cons = self.constraint + cons.goal_gap = math.max(1, math.min(cons.goal_gap, cons.goal_value-1)) +end + JobConstraints = defclass(JobConstraints, guidm.MenuOverlay) -JobConstraints.focus_path = 'workflow-job' +JobConstraints.focus_path = 'workflow/job' JobConstraints.ATTRS { job = DEFAULT_NIL, @@ -53,8 +400,6 @@ JobConstraints.ATTRS { frame_background = COLOR_BLACK, } -local null_cons = { goal_value = 0, goal_gap = 0, goal_by_count = false } - function JobConstraints:init(args) self.building = dfhack.job.getHolder(self.job) @@ -71,40 +416,11 @@ function JobConstraints:init(args) row_height = 4, scroll_keys = widgets.SECONDSCROLL, }, - widgets.Label{ + RangeEditor{ frame = { l = 0, b = 3 }, enabled = self:callback('isAnySelected'), - text = { - { key = 'BUILDING_TRIGGER_ENABLE_CREATURE', - text = function() - local cons = self:getCurConstraint() or null_cons - if cons.goal_by_count then - return ': Count stacks ' - else - return ': Count items ' - end - end, - on_activate = self:callback('onChangeUnit') }, - { key = 'BUILDING_TRIGGER_ENABLE_MAGMA', text = ': Modify', - on_activate = self:callback('onEditRange') }, - NEWLINE, ' ', - { key = 'BUILDING_TRIGGER_MIN_SIZE_DOWN', - on_activate = self:callback('onIncRange', 'goal_gap', 5) }, - { key = 'BUILDING_TRIGGER_MIN_SIZE_UP', - on_activate = self:callback('onIncRange', 'goal_gap', -1) }, - { text = function() - local cons = self:getCurConstraint() or null_cons - return string.format(': Min %-4d ', cons.goal_value - cons.goal_gap) - end }, - { key = 'BUILDING_TRIGGER_MAX_SIZE_DOWN', - on_activate = self:callback('onIncRange', 'goal_value', -1) }, - { key = 'BUILDING_TRIGGER_MAX_SIZE_UP', - on_activate = self:callback('onIncRange', 'goal_value', 5) }, - { text = function() - local cons = self:getCurConstraint() or null_cons - return string.format(': Max %-4d', cons.goal_value) - end }, - } + get_cb = self:callback('getCurConstraint'), + save_cb = self:callback('saveConstraint'), }, widgets.Label{ frame = { l = 0, b = 0 }, @@ -132,43 +448,6 @@ function JobConstraints:onGetSelectedJob() return self.job end -function describe_item_type(iobj) - local itemline = 'any item' - if iobj.is_craft then - itemline = 'any craft' - elseif iobj.item_type >= 0 then - itemline = df.item_type.attrs[iobj.item_type].caption or iobj.item_type - local subtype = iobj.item_subtype or -1 - local def = dfhack.items.getSubtypeDef(iobj.item_type, subtype) - local count = dfhack.items.getSubtypeCount(iobj.item_type, subtype) - if def then - itemline = def.name - elseif count >= 0 then - itemline = 'any '..itemline - end - end - return itemline -end - -function is_caste_mat(iobj) - return dfhack.items.isCasteMaterial(iobj.item_type or -1) -end - -function describe_material(iobj) - local matline = 'any material' - if is_caste_mat(iobj) then - matline = 'no material' - elseif (iobj.mat_type or -1) >= 0 then - local info = dfhack.matinfo.decode(iobj.mat_type, iobj.mat_index) - if info then - matline = info:toString() - else - matline = iobj.mat_type..':'..iobj.mat_index - end - end - return matline -end - function JobConstraints:initListChoices(clist, sel_token) clist = clist or workflow.listConstraints(self.job) @@ -247,45 +526,6 @@ function JobConstraints:saveConstraint(cons) self:initListChoices(nil, out.token) end -function JobConstraints:onChangeUnit() - local cons = self:getCurConstraint() - cons.goal_by_count = not cons.goal_by_count - self:saveConstraint(cons) -end - -function JobConstraints:onEditRange() - local cons = self:getCurConstraint() - dlg.showInputPrompt( - 'Input Range', - 'Enter the new constraint range:', - COLOR_WHITE, - (cons.goal_value-cons.goal_gap)..'-'..cons.goal_value, - function(text) - local maxv = string.match(text, '^%s*(%d+)%s*$') - if maxv then - cons.goal_value = maxv - return self:saveConstraint(cons) - end - local minv,maxv = string.match(text, '^%s*(%d+)-(%d+)%s*$') - if minv and maxv and minv ~= maxv then - cons.goal_value = math.max(minv,maxv) - cons.goal_gap = math.abs(maxv-minv) - return self:saveConstraint(cons) - end - dlg.showMessage('Invalid Range', 'This range is invalid: '..text, COLOR_LIGHTRED) - end - ) -end - -function JobConstraints:onIncRange(field, delta) - local cons = self:getCurConstraint() - if not cons.goal_by_count then - delta = delta * 5 - end - cons[field] = math.max(1, cons[field] + delta) - self:saveConstraint(cons) -end - function JobConstraints:onNewConstraint() local outputs = workflow.listJobOutputs(self.job) if #outputs == 0 then @@ -318,7 +558,10 @@ function JobConstraints:onNewConstraint() COLOR_WHITE, choices, function(idx,item) - self:saveConstraint(item.obj) + NewConstraint{ + constraint = item.obj, + on_submit = self:callback('saveConstraint') + }:show() end ) end diff --git a/scripts/lever.rb b/scripts/lever.rb new file mode 100644 index 000000000..59196f7d2 --- /dev/null +++ b/scripts/lever.rb @@ -0,0 +1,117 @@ +# 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 = tg.gate_flags.closed ? 'closed' : 'opened' + state << ', closing' if tg.gate_flags.closing + state << ', opening' if tg.gate_flags.opening + + ret << (descr + " linked to #{tg._rtti_classname} ##{tg.id} @[#{tg.centerx}, #{tg.centery}, #{tg.z}] #{state}") + + # indent other links + descr = descr.gsub(/./, ' ') + } + + ret << descr if ret.empty? + + ret +end + +def lever_list + @lever_list = [] + df.world.buildings.other[:TRAP].find_all { |bld| + bld.trap_type == :Lever + }.sort_by { |bld| bld.id }.each { |bld| + puts lever_descr(bld, @lever_list.length) + @lever_list << bld.id + } +end + +case $script_args[0] +when 'pull' + cheat = $script_args.delete('--cheat') || $script_args.delete('--now') + + id = $script_args[1].to_i + id = @lever_list[id] || id + bld = df.building_find(id) + raise 'invalid lever id' if not bld + + if cheat + lever_pull_cheat(bld) + else + lever_pull_job(bld) + end + +when 'list' + lever_list + +when /^\d+$/ + id = $script_args[0].to_i + id = @lever_list[id] || id + bld = df.building_find(id) + raise 'invalid lever id' if not bld + + puts lever_descr(bld) + +else + puts < Same as above, but match using a pattern acceptable to list. + region-pops incr + Augment (or diminish) all populations of TOKEN by factor (additive). + region-pops incr-all + Same as above, but match using a pattern acceptable to list. ]]) end diff --git a/scripts/slayrace.rb b/scripts/slayrace.rb index 749d0189b..ca50020f7 100644 --- a/scripts/slayrace.rb +++ b/scripts/slayrace.rb @@ -21,7 +21,7 @@ slayit = lambda { |u| else # it's getting hot around here # !!WARNING!! do not call on a magma-safe creature - ouh = df.onupdate_register(1) { + ouh = df.onupdate_register("slayrace ensure #{u.id}", 1) { if u.flags1.dead df.onupdate_unregister(ouh) else diff --git a/scripts/stripcaged.rb b/scripts/stripcaged.rb new file mode 100644 index 000000000..07694f711 --- /dev/null +++ b/scripts/stripcaged.rb @@ -0,0 +1,200 @@ +# mark stuff inside of cages for dumping. + +def plural(nr, name) + # '1 cage' / '4 cages' + "#{nr} #{name}#{'s' if nr > 1}" +end + +def cage_dump_items(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsItemst) + next if ref.item_tg.flags.dump + count += 1 + ref.item_tg.flags.dump = true + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_armor(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) + ref.unit_tg.inventory.each { |it| + next if it.mode != :Worn + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'armor piece')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_weapons(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + next unless ref.kind_of?(DFHack::GeneralRefContainsUnitst) + ref.unit_tg.inventory.each { |it| + next if it.mode != :Weapon + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'weapon')} in #{plural(count_cage, 'cage')}" +end + +def cage_dump_all(list) + count = 0 + count_cage = 0 + list.each { |cage| + pre_count = count + cage.general_refs.each { |ref| + case ref + when DFHack::GeneralRefContainsItemst + next if ref.item_tg.flags.dump + count += 1 + ref.item_tg.flags.dump = true + when DFHack::GeneralRefContainsUnitst + ref.unit_tg.inventory.each { |it| + next if it.item.flags.dump + count += 1 + it.item.flags.dump = true + } + end + } + count_cage += 1 if pre_count != count + } + + puts "Dumped #{plural(count, 'item')} in #{plural(count_cage, 'cage')}" +end + + +def cage_dump_list(list) + count_total = Hash.new(0) + 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 <