diff --git a/data/init/dfhack.tools.init b/data/init/dfhack.tools.init index 8087c2af1c..440fcac3c9 100644 --- a/data/init/dfhack.tools.init +++ b/data/init/dfhack.tools.init @@ -10,6 +10,7 @@ enable burrow enable faststart enable logistics enable overlay +enable preserve-rooms # aliases alias add autounsuspend suspendmanager diff --git a/docs/about/History.rst b/docs/about/History.rst index a295e056f2..4bd4ec6429 100644 --- a/docs/about/History.rst +++ b/docs/about/History.rst @@ -133,7 +133,7 @@ API Internals --------- -- Constructions module: ``findAtTile`` now uses a binary search intead of a linear search +- Constructions module: ``findAtTile`` now uses a binary search instead of a linear search - MSVC warning level upped to /W3, and /WX added to make warnings cause compilations to fail. Lua @@ -231,7 +231,7 @@ Misc Improvements - `prospect`: add new ``--show`` option to give the player control over which report sections are shown. e.g. ``prospect all --show ores`` will just show information on ores. - `quickfort`: - `Dreamfort ` blueprint set improvements: set traffic designations to encourage dwarves to eat cooked food instead of raw ingredients - - library blueprints are now included by default in ``quickfort list`` output. Use the new ``--useronly`` (or just ``-u``) option to filter out library bluerpints. + - library blueprints are now included by default in ``quickfort list`` output. Use the new ``--useronly`` (or just ``-u``) option to filter out library blueprints. - better error message when the blueprints directory cannot be found - `seedwatch`: ``seedwatch all`` now adds all plants with seeds to the watchlist, not just the "basic" crops. - ``materials.ItemTraitsDialog``: added a default ``on_select``-handler which toggles the traits. @@ -338,7 +338,7 @@ Misc Improvements - allow players to pause the confirmation dialog until they exit the current screen - `deteriorate`: new ``now`` command immediately deteriorates items of the specified types - `dfhack-examples-guide`: - - refine food preparation orders so meal types are chosen intelligently according to the amount of meals that exist and the number of aviailable items to cook with + - refine food preparation orders so meal types are chosen intelligently according to the amount of meals that exist and the number of available items to cook with - reduce required stock of dye for "Dye cloth" orders - fix material conditions for making jugs and pots - make wooden jugs by default to differentiate them from other stone tools. this allows players to more easily select jugs out with a properly-configured stockpile (i.e. the new ``woodentools`` alias) @@ -351,7 +351,7 @@ Misc Improvements - `quickfort`: - `Dreamfort ` blueprint set improvements: automatically create tavern, library, and temple locations (restricted to residents only by default), automatically associate the rented rooms with the tavern - `Dreamfort ` blueprint set improvements: new design for the services level, including were-bitten hospital recovery rooms and an appropriately-themed interrogation room next to the jail! Also fits better in a 1x1 embark for minimalist players. -- `workorder`: a manager is no longer required for orders to be created (matching bevavior in the game itself) +- `workorder`: a manager is no longer required for orders to be created (matching behavior in the game itself) Removed ------- @@ -407,7 +407,7 @@ Fixes - fixed a crash when trying to iterate over linked lists - `gui/advfort`: encrust and stud jobs no longer consume reagents without actually improving the target item - `luasocket`: return correct status code when closing socket connections so clients can know when to retry -- `quickfort`: contructions and bridges are now properly placed over natural ramps +- `quickfort`: constructions and bridges are now properly placed over natural ramps - `setfps`: keep internal ratio of processing FPS to graphics FPS in sync when updating FPS Misc Improvements @@ -559,7 +559,7 @@ API Lua --- - ``argparse.processArgsGetopt()``: you can now have long form parameters that are not an alias for a short form parameter. For example, you can now have a parameter like ``--longparam`` without needing to have an equivalent one-letter ``-l`` param. -- ``dwarfmode.enterSidebarMode()``: ``df.ui_sidebar_mode.DesignateMine`` is now a suported target sidebar mode +- ``dwarfmode.enterSidebarMode()``: ``df.ui_sidebar_mode.DesignateMine`` is now a supported target sidebar mode Structures ---------- @@ -619,10 +619,10 @@ Misc Improvements - `gui/gm-editor`: made search case-insensitive - `orders`: - support importing and exporting reaction-specific item conditions, like "lye-containing" for soap production orders - - new ``sort`` command. sorts orders according to their repeat frequency. this prevents daily orders from blocking other orders for simlar items from ever getting completed. + - new ``sort`` command. sorts orders according to their repeat frequency. this prevents daily orders from blocking other orders for similar items from ever getting completed. - `quickfort`: - Dreamfort blueprint set improvements: extensive revision based on playtesting and feedback. includes updated ``onMapLoad_dreamfort.init`` settings file, enhanced automation orders, and premade profession definitions. see full changelog at https://github.com/DFHack/dfhack/pull/1921 and https://github.com/DFHack/dfhack/pull/1925 - - accept multiple commands, list numbers, and/or blueprint lables on a single commandline + - accept multiple commands, list numbers, and/or blueprint labels on a single commandline - `tailor`: allow user to specify which materials to be used, and in what order - `tiletypes-here`, `tiletypes-here-point`: add ``--cursor`` and ``--quiet`` options to support non-interactive use cases - `unretire-anyone`: replaced the 'undead' descriptor with 'reanimated' to make it more mod-friendly @@ -743,7 +743,7 @@ Lua - ``string:wrap(width)`` wraps a string at space-separated word boundaries - ``string:trim()`` removes whitespace characters from the beginning and end of the string - ``string:split(delimiter, plain)`` splits a string with the given delimiter and returns a table of substrings. if ``plain`` is specified and set to ``true``, ``delimiter`` is interpreted as a literal string instead of as a pattern (the default) -- new utility function: ``utils.normalizePath()``: normalizes directory slashes across platoforms to ``/`` and coaleses adjacent directory separators +- new utility function: ``utils.normalizePath()``: normalizes directory slashes across platforms to ``/`` and coalesces adjacent directory separators - `reveal`: now exposes ``unhideFlood(pos)`` functionality to Lua - `xlsxreader`: added Lua class wrappers for the xlsxreader plugin API - ``argparse.processArgsGetopt()`` (previously ``utils.processArgsGetopt()``): @@ -1502,7 +1502,7 @@ Misc Improvements - `modtools/reaction-trigger`: - added ``-ignoreWorker``: ignores the worker when selecting the targets - changed the default behavior to skip inactive/dead units; added ``-dontSkipInactive`` to include creatures that are inactive - - added ``-range``: controls how far elligible targets can be from the workshop + - added ``-range``: controls how far eligible targets can be from the workshop - syndromes now are applied before commands are run, not after - if both a command and a syndrome are given, the command only runs if the syndrome could be applied - `mousequery`: made it more clear when features are enabled @@ -3574,7 +3574,7 @@ Internals New Plugins ----------- -- `hotkeys`: Shows ingame viewscreen with all dfhack keybindings active in current mode. +- `hotkeys`: Shows in-game viewscreen with all dfhack keybindings active in current mode. - `automelt`: allows marking stockpiles so any items placed in them will be designated for melting Fixes @@ -3585,7 +3585,7 @@ Fixes Misc Improvements ----------------- -- now you can use ``@`` to print things in interactive Lua with subtley different semantics +- now you can use ``@`` to print things in interactive Lua with subtly different semantics - optimizations for stockpiles for `autotrade` and `stockflow` - updated `exportlegends` to work with new maps, dfhack 40.11 r1+ @@ -3706,7 +3706,7 @@ New scripts New plugins ----------- - `rendermax`: replace the renderer with something else, eg ``rendermax light``- a lighting engine -- `automelt`: allows marking stockpiles for automelt (i.e. any items placed in stocpile will be designated for melting) +- `automelt`: allows marking stockpiles for automelt (i.e. any items placed in stockpile will be designated for melting) - `embark-tools`: implementations of Embark Anywhere, Nano Embark, and a few other embark-related utilities - `building-hacks`: Allows to add custom functionality and/or animations to buildings. - `petcapRemover`: triggers pregnancies in creatures so that you can effectively raise the default pet population cap @@ -3829,7 +3829,7 @@ Misc improvements - `superdwarf`: work in adventure mode too - `tweak` stable-cursor: carries cursor location from/to Build menu. - `deathcause`: allow selection from the unitlist screen -- slayrace: allow targetting undeads +- slayrace: allow targeting undeads - `workflow` plugin: - properly considers minecarts assigned to routes busy. @@ -3939,7 +3939,7 @@ New tweaks - tweak fast-heat: speeds up item heating & cooling, thus making stable-temp act faster. - tweak fix-dimensions: fixes subtracting small amounts from stacked liquids etc. - tweak advmode-contained: fixes UI bug in custom reactions with container inputs in advmode. -- tweak fast-trade: Shift-Enter for selecting items quckly in Trade and Move to Depot screens. +- tweak fast-trade: Shift-Enter for selecting items quickly in Trade and Move to Depot screens. - tweak military-stable-assign: Stop rightmost list of military->Positions from jumping to top. - tweak military-color-assigned: In same list, color already assigned units in brown & green. diff --git a/docs/changelog.txt b/docs/changelog.txt index 8fb35ee219..5bf0697ef9 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -52,6 +52,7 @@ Template for new versions: # Future ## New Tools +- `preserve-rooms`: manage room assignments for off-map units and noble roles. reserves rooms owned by traveling units and reinstates their ownership when they return to the site. also allows you to assign rooms to noble/administrator roles, and the rooms will be automatically assigned whenever the holder of the role changes ## New Features @@ -62,6 +63,7 @@ Template for new versions: - `exterminate`: don't kill friendly undead (unless ``--include-friendly`` is passed) when specifying ``undead`` as the target - `gui/settings-manager`: work details overlay no longer disappears when you click on a unit in the unit list - `buildingplan`: fixed type confusion when using quick filter slot '0' +- DFHack screens that allow keyboard cursor and camera movement while active now also allow diagonal and Z-change keyboard cursor keys ## Misc Improvements - `orders`: more space efficient workshop overlay @@ -73,11 +75,14 @@ Template for new versions: ## API - ``Units``: new ``isWildlife`` and ``isAgitated`` property checks +- ``Items::createItem``: removed growth_print parameter, now determined automatically ## Lua - ``dfhack.units``: ``isWildlife`` and ``isAgitated`` property checks - ``dfhack.units.isDanger``: no longer returns true for intelligent undead - Overlay widgets can now assume their ``active`` and ``visible`` functions will only execute in a context that matches their ``viewscreens`` associations +- ``gui.simulateInput``: do not generate spurious keycode from ``_STRING`` +- ``dfhack.items.createItem``: removed growth_print parameter to match C++ API ## Removed - ``quickfortress.csv``: remove old sample blueprints for "The Quick Fortress", which were unmaintained and non-functional in DF v50+. Online blueprints are available at https://docs.google.com/spreadsheets/d/1WuLYZBM6S2nt-XsPS30kpDnngpOQCuIdlw4zjrcITdY if anyone is interested in giving these blueprints some love @@ -141,7 +146,7 @@ Template for new versions: - `tweak`: ``named-codices``: display book titles instead of a material description in the stocks/trade screens - `logistics`: automatically forbid or claim items brought to a stockpile - `plant`: can now ``remove`` shrubs and saplings; ``list`` all valid shrub/sapling raw IDs; ``grow`` can make mature trees older; many new command options -- Locale-senstive number formatting: select your preferred format in `gui/control-panel`. prices and other large numbers in DFHack UIs can be displayed with commas (English formatting), the number formatting used by your system locale, in SI units (e.g. ``12.3k``), or even in scientific notation +- Locale-sensitive number formatting: select your preferred format in `gui/control-panel`. prices and other large numbers in DFHack UIs can be displayed with commas (English formatting), the number formatting used by your system locale, in SI units (e.g. ``12.3k``), or even in scientific notation ## Fixes - ``Gui::makeAnnouncement``, ``Gui::autoDFAnnouncement``: fix case where a new announcement is created instead of adding to the count of an existing announcement if the existing announcement was the first one in the reports vector @@ -167,7 +172,7 @@ Template for new versions: - `tiletypes`: new ``autocorrect`` property for autocorrecting adjacent tiles when making changes (e.g. adding ramp tops when you add a ramp) - Dreamfort: add a full complement of beds and chests to both barracks - Dreamfort: redesign guildhall/temple/library level for better accessibility -- Dreamfort: walkthough documentation refresh +- Dreamfort: walkthrough documentation refresh - Dreamfort: add milking/shearing station in surface grazing pasture - Dreamfort: integrate building prioritization into the blueprints and remove `prioritize` checklist steps - Dreamfort: add plumbing template for filling cisterns with running water @@ -217,7 +222,7 @@ Template for new versions: ## Removed - `plants`: renamed to `plant` - ``gui.FramedScreen``: this class is now deprecated; please use ``gui.ZScreen`` and ``widgets.Window`` instead -- ``dfhack.HIDE_CONSOLE_ON_STARTUP`` and ``dfhack.HIDE_ARMOK_TOOLS`` are no longer directly accessible. Please use `control-panel` or `gui/control-panel` to interact wtih those settings. +- ``dfhack.HIDE_CONSOLE_ON_STARTUP`` and ``dfhack.HIDE_ARMOK_TOOLS`` are no longer directly accessible. Please use `control-panel` or `gui/control-panel` to interact with those settings. # 50.13-r2.1 @@ -240,7 +245,7 @@ Template for new versions: - `dig`: don't affect already-revealed tiles when marking z-level for warm/damp dig - `zone`: refresh values in distance column when switching selected pastures when the assign animals dialog is open - `logistics`: include semi-wild pets when autoretrain is enabled -- `suspendmanager`: fully suspend unbuildable dead ends (e.g. buildling second level of a wall when the wall top is only accessible via ramp, causing the planned wall to be pathable but not buildable) +- `suspendmanager`: fully suspend unbuildable dead ends (e.g. building second level of a wall when the wall top is only accessible via ramp, causing the planned wall to be pathable but not buildable) - `prospect`: don't use scientific notation for representing large numbers ## Misc Improvements @@ -360,7 +365,7 @@ Template for new versions: - `suspendmanager`: improve performance when there are many active jobs - `clean`: protect farm plots when cleaning mud - `tweak`: add ``quiet`` option for silent enablement and disablement of tweaks -- `gui/teleport`: add global Ctrl-Shift-T keybinding (only avaiable when DFHack mortal mode is disabled) +- `gui/teleport`: add global Ctrl-Shift-T keybinding (only available when DFHack mortal mode is disabled) - Dreamfort: the four Craftsdwarf's workshops on the industry level are now specialized for Stonecrafting, Woodcrafting, Bone Carving, and miscellaneous tasks, respectively - `dwarfvet`: automatically unassign animals from pastures when they need treatment so they can make their way to the hospital. reassign them to their original pasture when treatment is complete. - `dwarfvet`: ignore animals assigned to cages or restraints @@ -398,7 +403,7 @@ Template for new versions: # 50.12-r1 ## Misc Improvements -- `sort`: squad assignment overlay rewritten for compatiblity with new vanilla data structures and screen layouts +- `sort`: squad assignment overlay rewritten for compatibility with new vanilla data structures and screen layouts ## Fixes - `gui/design`: no longer comes up when Ctrl-D is pressed but other DFHack windows have focus @@ -427,7 +432,7 @@ Template for new versions: - `tweak`: Add "flask-contents", makes flasks/vials/waterskins be named according to their contents ## Fixes -- `dig`: overlay that shows damp designations in ASCII mode now propertly highlights tiles that are damp because of an aquifer in the layer above +- `dig`: overlay that shows damp designations in ASCII mode now properly highlights tiles that are damp because of an aquifer in the layer above - `dig-now`: fix digging stairs in the surface sometimes creating underworld gates. - ``Units::getVisibleName``: don't reveal the true identities of units that are impersonating other historical figures - ``Gui::revealInDwarfmodeMap``: properly center the zoom even when the target tile is near the edge of the map @@ -555,7 +560,7 @@ Template for new versions: - ``Persistence``: persistent keys are now namespaced by an entity_id (e.g. a player fort site ID) - ``Persistence``: data is now stored one file per entity ID (plus one for the global world) in the DF savegame directory - ``Units.isDanger``: now returns true for agitated wildlife -- ``Constructions::designateRemove``: no longer designates the non-removable "pseudo" construtions that represent the top of walls +- ``Constructions::designateRemove``: no longer designates the non-removable "pseudo" constructions that represent the top of walls ## Lua - ``dfhack.capitalizeStringWords``: new function, returns string with all words capitalized @@ -607,7 +612,7 @@ Template for new versions: - `buildingplan`: make it easier to build single-tile staircases of any shape (up, down, or up/down) - `sort`: allow searching by profession on the squad assignment page - `sort`: add search for places screens -- `sort`: add search for work animal assignment screen; allow filtering by miltary/squad/civilian/burrow +- `sort`: add search for work animal assignment screen; allow filtering by military/squad/civilian/burrow - `sort`: on the squad assignment screen, make effectiveness and potential ratings use the same scale so effectiveness is always less than or equal to potential for a given unit. this way you can also tell when units are approaching their maximum potential - `sort`: new overlay on the animal assignment screen that shows how many work animals each visible unit already has assigned to them - `dreamfort`: Inside+ and Clearcutting burrows now automatically created and managed @@ -662,7 +667,7 @@ Template for new versions: - `zone`: animals trained for war or hunting are now labeled as such in animal assignment screens - `buildingplan`: support filtering cages by whether they are occupied - `buildingplan`: show how many items you need to make when planning buildings -- `tailor`: now adds to existing orders if possilbe instead of creating new ones +- `tailor`: now adds to existing orders if possible instead of creating new ones ## Documentation - unavailable tools are no longer listed in the tag indices in the online docs @@ -760,8 +765,8 @@ Template for new versions: ## API - ``Items::getValue()``: remove ``caravan_buying`` parameter since the identity of the selling party doesn't actually affect the item value - `RemoteFortressReader`: add a ``force_reload`` option to the GetBlockList RPC API to return blocks regardless of whether they have changed since the last request -- ``Units``: new animal propery check functions ``isMarkedForTraining(unit)``, ``isMarkedForTaming(unit)``, ``isMarkedForWarTraining(unit)``, and ``isMarkedForHuntTraining(unit)`` -- ``Gui``: ``getAnyStockpile`` and ``getAnyCivzone`` (along with their ``getSelected`` variants) now work through layers of ZScreens. This means that they will still return valid results even if a DFHack tool window is in the foereground. +- ``Units``: new animal property check functions ``isMarkedForTraining(unit)``, ``isMarkedForTaming(unit)``, ``isMarkedForWarTraining(unit)``, and ``isMarkedForHuntTraining(unit)`` +- ``Gui``: ``getAnyStockpile`` and ``getAnyCivzone`` (along with their ``getSelected`` variants) now work through layers of ZScreens. This means that they will still return valid results even if a DFHack tool window is in the foreground. ## Lua - ``new()``: improved error handling so that certain errors that were previously uncatchable (creating objects with members with unknown vtables) are now catchable with ``pcall()`` @@ -769,7 +774,7 @@ Template for new versions: - ``widgets.BannerPanel``: panel with distinctive border for marking DFHack UI elements on otherwise vanilla screens - ``widgets.Panel``: new functions to override instead of setting corresponding properties (useful when subclassing instead of just setting attributes): ``onDragBegin``, ``onDragEnd``, ``onResizeBegin``, ``onResizeEnd`` - ``dfhack.screen.readTile()``: now populates extended tile property fields (like ``top_of_text``) in the returned ``Pen`` object -- ``dfhack.units``: new animal propery check functions ``isMarkedForTraining(unit)``, ``isMarkedForTaming(unit)``, ``isMarkedForWarTraining(unit)``, and ``isMarkedForHuntTraining(unit)`` +- ``dfhack.units``: new animal property check functions ``isMarkedForTraining(unit)``, ``isMarkedForTaming(unit)``, ``isMarkedForWarTraining(unit)``, and ``isMarkedForHuntTraining(unit)`` - ``dfhack.gui``: new ``getAnyCivZone`` and ``getAnyStockpile`` functions; also behavior of ``getSelectedCivZone`` and ``getSelectedStockpile`` functions has changes as per the related API notes # 50.09-r2 @@ -969,7 +974,7 @@ Template for new versions: ## Misc Improvements - `buildingplan`: filters and global settings are now ignored when manually choosing items for a building, allowing you to make custom choices independently of the filters that would otherwise be used -- `buildingplan`: if `suspendmanager` is running, then planned buildings will be left suspended when their items are all attached. `suspendmanager` will unsuspsend them for construction when it is safe to do so. +- `buildingplan`: if `suspendmanager` is running, then planned buildings will be left suspended when their items are all attached. `suspendmanager` will unsuspend them for construction when it is safe to do so. - `buildingplan`: add option for autoselecting the last manually chosen item (like `automaterial` used to do) - `confirm`: adds confirmation for removing burrows via the repaint menu - `stockpiles`: support applying stockpile configurations with fully enabled categories to stockpiles in worlds other than the one where the configuration was exported from @@ -985,7 +990,7 @@ Template for new versions: ## Documentation - `modding-guide`: guide updated to include information for 3rd party script developers -- the ``untested`` tag has been renamed to ``unavailable`` to better reflect the status of the remaining unavaialable tools. most of the simply "untested" tools have now been tested and marked as working. the remaining tools are known to need development work before they are available again. +- the ``untested`` tag has been renamed to ``unavailable`` to better reflect the status of the remaining unavailable tools. most of the simply "untested" tools have now been tested and marked as working. the remaining tools are known to need development work before they are available again. ## Lua - ``widgets.Label``: tokens can now specify a ``htile`` property to indicate the tile that should be shown when the Label is hovered over with the mouse @@ -1115,7 +1120,7 @@ Template for new versions: - ``widgets.CycleHotkeyLabel``: Added ``key_back`` optional parameter to cycle backwards. - ``widgets.HotkeyLabel``: Added ``setLabel`` method to allow easily updating the label text without mangling the keyboard shortcut. - ``widgets.HotkeyLabel``: Added ``setOnActivate`` method to allow easily updating the ``on_activate`` callback. -- ``widgets.FilteredList``: Added ``case_sensitive`` optional paramter to determine if filtering is case sensitive. +- ``widgets.FilteredList``: Added ``case_sensitive`` optional parameter to determine if filtering is case sensitive. # 50.05-alpha3.1 diff --git a/docs/dev/Contributing.rst b/docs/dev/Contributing.rst index 9695027a19..7b34dc20ce 100644 --- a/docs/dev/Contributing.rst +++ b/docs/dev/Contributing.rst @@ -44,7 +44,7 @@ General contribution guidelines * Update ``docs/changelog.txt`` and ``docs/about/Authors.rst`` when applicable. See `build-changelog` for more information on the changelog format. * Submit ideas and bug reports as :issue:`issues on GitHub <>`. - Posts in the forum thread or on Disord can easily get missed or forgotten. + Posts in the forum thread or on Discord can easily get missed or forgotten. * Work on :issue:`reported problems ` will take priority over ideas or suggestions. diff --git a/docs/dev/Documentation.rst b/docs/dev/Documentation.rst index 07242d698c..b7618074a5 100644 --- a/docs/dev/Documentation.rst +++ b/docs/dev/Documentation.rst @@ -268,7 +268,7 @@ examples:: Show help text. ``-l``, ``--quality `` Set the quality of the architecture for built architected - builtings. + buildings. ``-q``, ``--quiet`` Suppress informational output (error messages are still printed). diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index b17758f88d..3b288ddc10 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -736,14 +736,14 @@ arbitrary Lua tables. local state = dfhack.persistent.getSiteData('my-script-name', {somedata={}}) -* ``dfhack.peristent.getSiteDataString(key)`` +* ``dfhack.persistent.getSiteDataString(key)`` Retrieves the underlying serialized string associated with the current site and the given string ``key``. Returns *nil* if the key isn't found in the current site's persistent data. Most scripts will want to use ``getSiteData`` instead. -* ``dfhack.peristent.saveSiteData(key, data)`` +* ``dfhack.persistent.saveSiteData(key, data)`` Persists the given ``data`` (usually a table; can be of arbitrary complexity and depth) in the world save, associated with the current site and the given ``key``. @@ -759,8 +759,8 @@ arbitrary Lua tables. ``key``. Returns *true* if succeeded. * ``dfhack.persistent.getWorldData(key[, default])`` -* ``dfhack.peristent.getWorldDataString(key)`` -* ``dfhack.peristent.saveWorldData(key, data)`` +* ``dfhack.persistent.getWorldDataString(key)`` +* ``dfhack.persistent.saveWorldData(key, data)`` * ``dfhack.persistent.saveWorldDataString(key, data_str)`` * ``dfhack.persistent.deleteWorldData(key)`` @@ -1047,7 +1047,7 @@ Screens The strings have a "screen/foo/bar/baz..." format e.g.:: [1] = "dwarfmode/Info/CREATURES/CITIZEN" - [2] = "dwardmode/Squads" + [2] = "dwarfmode/Squads" * ``dfhack.gui.matchFocusString(focus_string[, viewscreen])`` @@ -1099,7 +1099,7 @@ General-purpose selections associated with the selected in-game object. For example, Calling ``getSelectedJob`` when a building is selected will return the job associated with the building (e.g., the ``ConstructBuilding`` job). If ``silent`` is - ommitted or set to ``false`` and a selected object cannot be found, then an + omitted or set to ``false`` and a selected object cannot be found, then an error is printed to the console. * ``dfhack.gui.getAnyWorkshopJob(screen)`` @@ -1244,7 +1244,7 @@ Announcements ``subid`` is only used for ``AB`` and ``ART_IMAGE``. ``[/LPAGE]`` ends the link text. ``[C:`` screenf ``:`` screenb ``:`` screenbright ``]``: Color text. Sets the - repective values in ``df.global.gps`` and then sets text color. + respective values in ``df.global.gps`` and then sets text color. ``color`` = ``screenf``, ``bright`` = ``screenbright``, ``screenb`` does nothing since popup backgrounds are always black. Example: ``"Light gray, [C:4:0:0]red, [C:4:0:1]orange, [C:7:0:0]light gray."`` @@ -1691,7 +1691,7 @@ Units module * ``dfhack.units.assignTrainer(unit[,trainer_id])`` * ``dfhack.units.unassignTrainer(unit)`` - Assignes (or unassigns) a trainer for the specified trainable unit. The + Assigns (or unassigns) a trainer for the specified trainable unit. The trainer ID can be omitted if "any trainer" is desired. Returns a boolean indicating whether the operation was successful. @@ -3672,7 +3672,7 @@ functions. These are invoked just like standard string functions, e.g.:: Lines are split at space-separated word boundaries. Any existing newlines are kept in place. If a single word is longer than width, it is split over multiple lines. If ``width`` is not specified, 72 is used. The ``opts`` - parameter can be a table with the following boolean fields specified:: + parameter can be a table with the following boolean fields specified: :return_as_table: if ``true``, then the function will return a table of strings, with each string representing one wrapped line. Otherwise, a @@ -4025,7 +4025,7 @@ parameters. optionActions: list of option specifications and returns a list of positional parameters -- that is, all strings that are - neither options nor argruments to options. Options and positional parameters + neither options nor arguments to options. Options and positional parameters can appear in any order on the commandline, as long as arguments to options immediately follow the option itself. @@ -4512,7 +4512,10 @@ Misc Every argument after the initial screen may be *nil*, a numeric keycode, a string keycode, a sequence of numeric or string keycodes, or a mapping of keycodes to *true* or *false*. For instance, it is possible to use the - table passed as argument to ``onInput``. + table passed as argument to ``onInput``. The ``_STRING`` convenience field of + an ``onInput`` keys table will be ignored; the presence (or absence) of a + ``STRING_A???`` keycode will determine the text content of the simulated + input. You can send mouse clicks as well by setting the ``_MOUSE_L`` key or other mouse-related pseudo-keys documented with the ``screen:onInput(keys)`` @@ -5213,7 +5216,7 @@ Has attributes: ``true`` if the drag was "successful" (i.e., not canceled) and ``false`` otherwise. Dragging can be canceled by right clicking while dragging with the mouse, hitting :kbd:`Esc` (while dragging with the mouse or keyboard), or by - calling ``Panel:setKeyboaredDragEnabled(false)`` (while dragging with the + calling ``Panel:setKeyboardDragEnabled(false)`` (while dragging with the keyboard). If it is more convenient to do so, you can choose to override the ``panel:onDragBegin`` and/or the ``panel:onDragEnd`` methods instead of setting the ``on_drag_begin`` and/or ``on_drag_end`` attributes. @@ -5303,7 +5306,7 @@ Has functions: If called with ``true`` and the panel is not already in keyboard drag mode, then any current drag or resize operations are halted where they are (not - canceled), the panel siezes input focus (see `View class`_ above for + canceled), the panel seizes input focus (see `View class`_ above for information on the DFHack focus subsystem), and further keyboard cursor keys move the window as if it were being dragged. Shift-cursor keys move by larger amounts. Hit :kbd:`Enter` to commit the new window position or :kbd:`Esc` to @@ -5314,7 +5317,7 @@ Has functions: If called with ``true`` and the panel is not already in keyboard resize mode, then any current drag or resize operations are halted where they are (not - canceled), the panel siezes input focus (see `View class`_ above for + canceled), the panel seizes input focus (see `View class`_ above for information on the DFHack focus subsystem), and further keyboard cursor keys resize the window as if it were being dragged from the lower right corner. If neither the bottom or right edge is a valid anchor, an appropriate corner will @@ -5526,7 +5529,7 @@ The Scrollbar widget implements the following methods: The ``top_elem`` param is the (one-based) index of the first visible element. The ``elems_per_page`` param is the maximum number of elements that can be shown at one time. The ``num_elems`` param is the total number of elements - that the paried widget can scroll through. If ``elems_per_page`` or + that the paired widget can scroll through. If ``elems_per_page`` or ``num_elems`` is not specified, the most recently specified value for these parameters is used. The scrollbar will adjust its scrollbar size and position according to the values passed to this function. @@ -6484,7 +6487,7 @@ Native functions (exported to Lua) - ``rollNormal(rngID, avg, stddev)`` - generates random normal[gaus.] + generates random double drawn from a normal (Gaussian) distribution - ``rollBool(rngID, chance)`` diff --git a/docs/dev/overlay-dev-guide.rst b/docs/dev/overlay-dev-guide.rst index 0f3d345968..7760ca2460 100644 --- a/docs/dev/overlay-dev-guide.rst +++ b/docs/dev/overlay-dev-guide.rst @@ -379,7 +379,7 @@ screen (by default, but the player can move it wherever). function MenuScreen:init() self.mouseover = false - -- derrive the menu frame from the hotspot frame so it + -- derive the menu frame from the hotspot frame so it -- can appear in a nearby location local frame = copyall(self.hotspot_frame) -- ... diff --git a/docs/guides/quickfort-user-guide.rst b/docs/guides/quickfort-user-guide.rst index a140100270..96d24b8d49 100644 --- a/docs/guides/quickfort-user-guide.rst +++ b/docs/guides/quickfort-user-guide.rst @@ -1728,7 +1728,7 @@ The industry_ level: advanced linking :target: https://drive.google.com/file/d/1c8YTHxTgJY5tUII-BOWdLhmDFAHwIOEs :align: center -The industry level is densely packed and has more intracate stockpile and +The industry level is densely packed and has more intricate stockpile and hauling route configuration. .. topic:: Tip @@ -1854,7 +1854,7 @@ With option 2, if you need a "better" bedroom, you'd just expand the zone to cover the neighboring "unit". Satisfying the monarch is also simple: plop down a new suites level and assign each block of 4 rooms to one zone. four units for the bedroom, four for the office, four for the dining hall, and four for the -tomb. Smooth and engrave and you're done. Of course, more asthetic-minded +tomb. Smooth and engrave and you're done. Of course, more aesthetic-minded players are always free to design custom areas too. These blueprints are designed to be functional more than beautiful. @@ -2054,7 +2054,7 @@ Property Description ``residents``, ``citizens``, or ``members``. defaults to ``visitors``. ``profession`` (only if ``location=guildhall``) sets the profession of the - guildhall. See possilbe values with ``:lua @df.profession``. + guildhall. See possible values with ``:lua @df.profession``. For example: ``profession=metalsmith``. ``desired_*`` (only if the location is set to the relevant type) sets the desired number of stocked items for the attached location. diff --git a/docs/plugins/autoclothing.rst b/docs/plugins/autoclothing.rst index 4bb0f35742..1ea1a9094f 100644 --- a/docs/plugins/autoclothing.rst +++ b/docs/plugins/autoclothing.rst @@ -12,7 +12,7 @@ generate manager orders to manufacture more when your stock drops below a configured per-citizen threshold. If you are wondering whether you should enable `tailor` instead, see the -comparsion in the last section below. +comparison in the last section below. Usage ----- @@ -62,6 +62,6 @@ Enable `tailor` when: - you want a tool that can run effectively with a default configuration You can even enable both tools if you only want to set configuration for a few -specific clothing types (e.g. if you'd prefer that most pople wear dresses). +specific clothing types (e.g. if you'd prefer that most people wear dresses). You can set the configuration for those types in ``autoclothing`` and let `tailor` automatically manage the rest. diff --git a/docs/plugins/buildingplan.rst b/docs/plugins/buildingplan.rst index a056a8933c..c00569365c 100644 --- a/docs/plugins/buildingplan.rst +++ b/docs/plugins/buildingplan.rst @@ -35,7 +35,7 @@ building to be fire- or magma-proof in the placement UI screen. This makes it very easy to ensure that your pump stacks and floodgates, for example, are magma-safe. -Buildingplan works well in conjuction with other design tools like +Buildingplan works well in conjunction with other design tools like `gui/quickfort`, which allow you to apply a building layout from a blueprint. You can apply very large, complicated layouts, and the buildings will simply be built when your dwarves get around to producing the needed materials. If you diff --git a/docs/plugins/burrow.rst b/docs/plugins/burrow.rst index 922e3f01e1..ccac6c7cd7 100644 --- a/docs/plugins/burrow.rst +++ b/docs/plugins/burrow.rst @@ -97,7 +97,7 @@ If you are auto-expanding a burrow (whose name ends in a ``+``) and the miner who is digging to expand the burrow is assigned to that burrow, then 1-wide corridors that expand the burrow will have very slow progress. This is because the burrow is expanded to include the next dig job only after the miner has -chosen a next tile to dig, which may be far away. 2-wide cooridors are much +chosen a next tile to dig, which may be far away. 2-wide corridors are much more efficient when expanding a burrow since the "next" tile to dig will still be nearby. diff --git a/docs/plugins/fastdwarf.rst b/docs/plugins/fastdwarf.rst index cc0ebb5052..aaf706d6a8 100644 --- a/docs/plugins/fastdwarf.rst +++ b/docs/plugins/fastdwarf.rst @@ -51,5 +51,5 @@ Note that a dwarf will only teleport when: So you may still see dwarves walking normally or just standing still if they do not have a job to teleport to. Since jobs get done so much faster, you will likely see groups of dwarves just standing around, with individual dwarves -periodically disappaearing and reappearing moments later when they have a job +periodically disappearing and reappearing moments later when they have a job to do. diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 62ff635905..b37e3d85f0 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -94,7 +94,7 @@ armor, or just metalsmithing. Then, you can assign appropriate legendary masters to each forge, and they will only receive orders for appropriate products. -Simiarly, you can set up Craftsdwarf's workshops to specialize in stone, wood, +Similarly, you can set up Craftsdwarf's workshops to specialize in stone, wood, or bone. Regardless of the labor restriction settings, you can manually assign any task diff --git a/docs/plugins/preserve-rooms.rst b/docs/plugins/preserve-rooms.rst new file mode 100644 index 0000000000..831e6f6c29 --- /dev/null +++ b/docs/plugins/preserve-rooms.rst @@ -0,0 +1,82 @@ +preserve-rooms +============== + +.. dfhack-tool:: + :summary: Manage room assignments for off-map units and noble roles. + :tags: fort bugfix interface + +When a citizen leaves the map for any reason, e.g. when going off on a raid, +they lose all their zone assignments. Any bedrooms, offices, dining rooms, or +tombs assigned to the citizen will become unassigned and may be claimed by +another unit while they are away. + +A related issue occurs when a noble or administrative role changes hands, +either because you have reassigned them or because of an internal fort +election. Rooms you have assigned to the previous noble stay assigned to them, +and the new noble complains because their room requirements are not being met. + +This tool mitigates both issues. It records when units leave the map and +reserves their assigned bedrooms, offices, etc. for them. The zones will be +disabled in their absence (so other units don't steal them), and will be +re-enabled and reassigned to them when they appear back on the map. If they die +away from the fort, the zone will become unreserved and available for reuse. + +When you click on an assignable zone, you will also now have the option to +associate the room with a noble or administrative role. The room will be +automatically reassigned to whoever currently holds that position. If multiple +rooms of the same type are assigned to a position, then only one room of that +type will be assigned to each holder of that position (e.g. one room per baron +or militia captain). + +Usage +----- + +:: + + preserve-rooms [status] + preserve-rooms now + preserve-rooms enable|disable + preserve-rooms reset + +Examples +-------- + +``preserve-rooms`` + List the types of rooms that are assigned to each noble role and which of + those rooms are being automatically assigned to new holders of the + respective office. +``preserve-rooms now`` + Do an immediate update of room assignments. The plugin does this routinely + in the background, but you can run it manually to update now. +``preserve-rooms disable track-missions`` + Disable the ``track-missions`` feature for this fort. +``preserve-rooms reset track-roles`` + Clear all configuration related to the ``track-roles`` feature (currently + assigned rooms are not unassigned). + +Features +-------- + +``track-missions`` + Reserve the rooms assigned to units that leave the map and reassign them + upon their return. This feature is enabled by default. +``track-roles`` + Allow rooms to be associated with noble or adminstrative roles. Associated + rooms will be automatically assigned to the current holder of the specified + role. This feature is enabled by default. + +Overlay +------- + +The ``preserve-rooms.reserved`` overlay indicates whether a zone is disabled +because it is being reserved for a unit that left the map and is expected to +return. For unreserved rooms, it provides widgets to mark the zone as +associated with a specific noble or administrative role. + +Notes +----- + +This tool fixes rooms being unassigned when a unit leaves the map. This is a +different bug from the one fixed by `preserve-tombs`, which handles the case +where a tomb is unassigned upon a unit's death, preventing them from ever being +buried in their own tomb. diff --git a/docs/plugins/regrass.rst b/docs/plugins/regrass.rst index 525a20c6a1..dd9ebc5eba 100644 --- a/docs/plugins/regrass.rst +++ b/docs/plugins/regrass.rst @@ -76,7 +76,7 @@ Options begin with. Will still fail in incompatible biomes. ``-f``, ``--force`` Force a grass type on tiles with no compatible grass types. The ``--new`` - option takes precidence for compatible biomes, otherwise such tiles will be + option takes precedence for compatible biomes, otherwise such tiles will be forced instead. By default, a single random grass type is selected from the world's raws. The ``--plant`` option allows a specific grass type to be specified. diff --git a/docs/plugins/reveal.rst b/docs/plugins/reveal.rst index 31b455c9cf..5f17583b8e 100644 --- a/docs/plugins/reveal.rst +++ b/docs/plugins/reveal.rst @@ -53,7 +53,7 @@ Usage ``revflood`` Hide everything, then reveal tiles with a path to the keyboard cursor (if enabled) or the selected unit (if a unit is selected) or else a random - citizen. This allows reparing maps that you accidentally saved while they + citizen. This allows repairing maps that you accidentally saved while they were revealed. Note that tiles behind constructed walls are also revealed as a workaround for :bug:`1871`. diff --git a/docs/plugins/sort.rst b/docs/plugins/sort.rst index 8e036fd02a..70a11a3bcf 100644 --- a/docs/plugins/sort.rst +++ b/docs/plugins/sort.rst @@ -45,7 +45,7 @@ weapon they have the most skill in. The effectiveness rating also takes into account physical and mental attributes as well as general fighting (non-weapon) skills. -Simiarly, the "ranged effectiveness" annotation indicates expected +Similarly, the "ranged effectiveness" annotation indicates expected effectiveness with a crossbow. This rating also takes into account relevant physical and mental attributes. @@ -150,7 +150,7 @@ be involved in plots, such as criminals, necromancers, necromancer experiments, and intelligent undead. On the interrogations screen, you can also filter units by whether they have -already been interviewed in realation to the current crime. +already been interviewed in relation to the current crime. Candidates overlay ------------------ diff --git a/docs/plugins/tailor.rst b/docs/plugins/tailor.rst index 897bccc257..c1732a741d 100644 --- a/docs/plugins/tailor.rst +++ b/docs/plugins/tailor.rst @@ -16,7 +16,7 @@ materials that you have on hand in the fort. For example, if you have lots of silk, but no cloth, then ``tailor`` will order only silk clothing to be made. If you are wondering whether you should enable `autoclothing` instead, see the -head-to-head comparsion in the `autoclothing` docs. +head-to-head comparison in the `autoclothing` docs. Usage ----- diff --git a/docs/plugins/zone.rst b/docs/plugins/zone.rst index 72d0ae5ceb..e78f29a1c9 100644 --- a/docs/plugins/zone.rst +++ b/docs/plugins/zone.rst @@ -178,7 +178,7 @@ In the window that pops up when you click the hotkey hint or hit the hotkey on y - filter by whether the unit lays eggs - filter by whether the unit needs a grazing area -The window is fully navigatable via keyboard or mouse. Hit Enter or click on a +The window is fully navigable via keyboard or mouse. Hit Enter or click on a unit to assign/unassign it to the currently selected zone or building. Shift click to assign/unassign a range of units. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index e4c1a87099..b3b56c974a 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -2414,10 +2414,9 @@ static int items_createItem(lua_State *state) auto item_subtype = lua_tointeger(state, 3); auto mat_type = lua_tointeger(state, 4); auto mat_index = lua_tointeger(state, 5); - int growth_print = luaL_optint(state, 6, -1); - bool no_floor = lua_toboolean(state, 7); + bool no_floor = lua_toboolean(state, 6); vector out_items; - Items::createItem(out_items, unit, item_type, item_subtype, mat_type, mat_index, growth_print, no_floor); + Items::createItem(out_items, unit, item_type, item_subtype, mat_type, mat_index, no_floor); Lua::PushVector(state, out_items); return 1; } diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index fbc99b4b37..79aed92c36 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -173,7 +173,7 @@ DFHACK_EXPORT int getItemBaseValue(int16_t item_type, int16_t item_subtype, int1 DFHACK_EXPORT int getValue(df::item *item, df::caravan_state *caravan = NULL); DFHACK_EXPORT bool createItem(std::vector &out_items, df::unit *creator, df::item_type type, - int16_t item_subtype, int16_t mat_type, int32_t mat_index, int32_t growth_print = -1, bool no_floor = false); + int16_t item_subtype, int16_t mat_type, int32_t mat_index, bool no_floor = false); // Returns true if the item is free from mandates, or false if mandates prevent trading the item. DFHACK_EXPORT bool checkMandates(df::item *item); diff --git a/library/lua/gui.lua b/library/lua/gui.lua index 65a3a913a6..8b40c34012 100644 --- a/library/lua/gui.lua +++ b/library/lua/gui.lua @@ -81,7 +81,7 @@ function simulateInput(screen,...) for k,v in pairs(arg) do if v == true then push_key(k) - else + elseif k ~= '_STRING' then push_key(v) end end diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index ed38df9402..793acbd520 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -209,8 +209,14 @@ end MOVEMENT_KEYS = { KEYBOARD_CURSOR_UP = { 0, -1, 0 }, KEYBOARD_CURSOR_DOWN = { 0, 1, 0 }, KEYBOARD_CURSOR_LEFT = { -1, 0, 0 }, KEYBOARD_CURSOR_RIGHT = { 1, 0, 0 }, + KEYBOARD_CURSOR_UPLEFT = {-1, -1, 0}, KEYBOARD_CURSOR_UPRIGHT = {1, -1, 0}, + KEYBOARD_CURSOR_DOWNLEFT = {-1, 1, 0}, KEYBOARD_CURSOR_DOWNRIGHT = {1, 1, 0}, KEYBOARD_CURSOR_UP_FAST = { 0, -1, 0, true }, KEYBOARD_CURSOR_DOWN_FAST = { 0, 1, 0, true }, KEYBOARD_CURSOR_LEFT_FAST = { -1, 0, 0, true }, KEYBOARD_CURSOR_RIGHT_FAST = { 1, 0, 0, true }, + KEYBOARD_CURSOR_UPLEFT_FAST = {-1, -1, 0, true}, KEYBOARD_CURSOR_UPRIGHT_FAST = {1, -1, 0, true}, + KEYBOARD_CURSOR_DOWNLEFT_FAST = {-1, 1, 0, true}, KEYBOARD_CURSOR_DOWNRIGHT_FAST = {1, 1, 0, true}, + KEYBOARD_CURSOR_UP_Z = {0, 0, 1}, KEYBOARD_CURSOR_DOWN_Z = {0, 0, -1}, + KEYBOARD_CURSOR_UP_Z_AUX = {0, 0, 1}, KEYBOARD_CURSOR_DOWN_Z_AUX = {0, 0, -1}, CURSOR_UP = { 0, -1, 0 }, CURSOR_DOWN = { 0, 1, 0 }, CURSOR_LEFT = { -1, 0, 0 }, CURSOR_RIGHT = { 1, 0, 0 }, CURSOR_UPLEFT = { -1, -1, 0 }, CURSOR_UPRIGHT = { 1, -1, 0 }, diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index ff7e0cfef2..e83ad80722 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -725,19 +725,20 @@ static void manageNewUnitActiveEvent(color_ostream& out) { multimap copy(handlers[EventType::UNIT_NEW_ACTIVE].begin(), handlers[EventType::UNIT_NEW_ACTIVE].end()); // iterate event handler callbacks - vector new_active_unit_ids; + vector newly_active_unit_ids; for (df::unit* unit : df::global::world->units.active) { - if (!activeUnits.count(unit->id)) { - activeUnits.emplace(unit->id); - new_active_unit_ids.emplace_back(unit->id); - } + if (!activeUnits.count(unit->id)) + newly_active_unit_ids.emplace_back(unit->id); } - for (int32_t unit_id : new_active_unit_ids) { + for (int32_t unit_id : newly_active_unit_ids) { for (auto &[_,handle] : copy) { DEBUG(log,out).print("calling handler for new unit event\n"); run_handler(out, EventType::UNIT_NEW_ACTIVE, handle, (void*) intptr_t(unit_id)); // intptr_t() avoids cast from smaller type warning } } + activeUnits.clear(); + std::transform(df::global::world->units.active.begin(), df::global::world->units.active.end(), + std::inserter(activeUnits, activeUnits.end()), [](auto & unit){ return unit->id; }); } diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 51797fdbcf..e87dcdab1b 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -87,6 +87,9 @@ distribution. #include "df/mandate.h" #include "df/map_block.h" #include "df/material.h" +#include "df/plant_raw.h" +#include "df/plant_growth.h" +#include "df/plant_growth_print.h" #include "df/proj_itemst.h" #include "df/proj_list_link.h" #include "df/reaction_product_itemst.h" @@ -1707,7 +1710,7 @@ int Items::getValue(df::item *item, df::caravan_state *caravan) { } bool Items::createItem(vector &out_items, df::unit *unit, df::item_type item_type, - int16_t item_subtype, int16_t mat_type, int32_t mat_index, int32_t growth_print, bool no_floor) + int16_t item_subtype, int16_t mat_type, int32_t mat_index, bool no_floor) { // Based on Quietust's plugins/createitem.cpp CHECK_NULL_POINTER(unit); auto pos = Units::getPosition(unit); @@ -1756,7 +1759,34 @@ bool Items::createItem(vector &out_items, df::unit *unit, df::item_t for (auto out_item : out_items) { // Plant growths need a valid "growth print", otherwise they behave oddly if (auto growth = virtual_cast(out_item)) + { + int growth_print = -1; + // Make sure it's made of a valid plant material, then grab its definition + if (growth->mat_type >= 419 && growth->mat_type <= 618 && growth->mat_index >= 0 && (unsigned)growth->mat_index < world->raws.plants.all.size()) + { + auto plant_def = world->raws.plants.all[growth->mat_index]; + // Make sure it subtype is also valid + if (growth->subtype >= 0 && (unsigned)growth->subtype < plant_def->growths.size()) + { + auto growth_def = plant_def->growths[growth->subtype]; + // Try and find a growth print matching the current time + // (in practice, only tree leaves use this for autumn color changes) + for (size_t i = 0; i < growth_def->prints.size(); i++) + { + auto print_def = growth_def->prints[i]; + if (print_def->timing_start <= *df::global::cur_year_tick && *df::global::cur_year_tick <= print_def->timing_end) + { + growth_print = i; + break; + } + } + // If we didn't find one, then pick the first one (if it exists) + if (growth_print == -1 && !growth_def->prints.empty()) + growth_print = 0; + } + } growth->growth_print = growth_print; + } if (!no_floor) out_item->moveToGround(pos.x, pos.y, pos.z); } diff --git a/library/xml b/library/xml index 5bbf9bff95..cdfc947a23 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5bbf9bff957d6ca755c57d8661eff557401690d1 +Subproject commit cdfc947a230bd49eba208324c012e4ee74281a30 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a76bd79332..f0b35ea9ef 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -142,6 +142,7 @@ if(BUILD_SUPPORTED) dfhack_plugin(pathable pathable.cpp LINK_LIBRARIES lua) dfhack_plugin(pet-uncapper pet-uncapper.cpp) dfhack_plugin(plant plant.cpp LINK_LIBRARIES lua) + dfhack_plugin(preserve-rooms preserve-rooms.cpp LINK_LIBRARIES lua) dfhack_plugin(preserve-tombs preserve-tombs.cpp) dfhack_plugin(probe probe.cpp LINK_LIBRARIES lua) dfhack_plugin(prospector prospector.cpp LINK_LIBRARIES lua) diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index c870b528b7..0b669258ce 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -20,7 +20,6 @@ #include "df/game_type.h" #include "df/item.h" #include "df/plant_growth.h" -#include "df/plant_growth_print.h" #include "df/plant_raw.h" #include "df/tool_uses.h" #include "df/unit.h" @@ -54,7 +53,7 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out) { } bool makeItem(df::unit *unit, df::item_type type, int16_t subtype, int16_t mat_type, int32_t mat_index, - int32_t growth_print = -1, bool move_to_cursor = false, bool second_item = false) + bool move_to_cursor = false, bool second_item = false) { // Special logic for making Gloves and Shoes in pairs bool is_gloves = (type == item_type::GLOVES); bool is_shoes = (type == item_type::SHOES); @@ -69,7 +68,7 @@ bool makeItem(df::unit *unit, df::item_type type, int16_t subtype, int16_t mat_t bool on_floor = (container == NULL) && (building == NULL) && !move_to_cursor; vector out_items; - if (!Items::createItem(out_items, unit, type, subtype, mat_type, mat_index, growth_print, !on_floor)) + if (!Items::createItem(out_items, unit, type, subtype, mat_type, mat_index, !on_floor)) return false; for (size_t i = 0; i < out_items.size(); i++) { @@ -105,7 +104,7 @@ bool makeItem(df::unit *unit, df::item_type type, int16_t subtype, int16_t mat_t is_shoes = false; // If we asked for gloves/shoes and only got one (and we're making the first one), make another if ((is_gloves || is_shoes) && !second_item) - return makeItem(unit, type, subtype, mat_type, mat_index, growth_print, move_to_cursor, true); + return makeItem(unit, type, subtype, mat_type, mat_index, move_to_cursor, true); return true; } @@ -157,7 +156,7 @@ static inline bool select_caste_mat(color_ostream &out, vector &tokens, } static inline bool select_plant_growth(color_ostream &out, vector &tokens, df::item_type &item_type, - int16_t &item_subtype, int16_t &mat_type, int32_t &mat_index, int32_t &growth_print, const string &material_str) + int16_t &item_subtype, int16_t &mat_type, int32_t &mat_index, const string &material_str) { split_string(&tokens, material_str, ":"); if (tokens.size() == 1) @@ -191,21 +190,6 @@ static inline bool select_plant_growth(color_ostream &out, vector &token item_subtype = growth->item_subtype; mat_type = growth->mat_type; mat_index = growth->mat_index; - - // Try and find a growth print matching the current time - // (in practice, only tree leaves use this for autumn color changes) - for (size_t k = 0; k < growth->prints.size(); k++) - { - auto print = growth->prints[k]; - if (print->timing_start <= *cur_year_tick && *cur_year_tick <= print->timing_end) - { - growth_print = k; - break; - } - } - // If we didn't find one, then pick the first one (if it exists) - if (growth_print == -1 && !growth->prints.empty()) - growth_print = 0; break; } if (mat_type == -1) { @@ -230,7 +214,6 @@ command_result df_createitem (color_ostream &out, vector ¶meters) { int16_t item_subtype = -1; int16_t mat_type = -1; int32_t mat_index = -1; - int32_t growth_print = -1; int count = 1; bool move_to_cursor = false; @@ -372,7 +355,7 @@ command_result df_createitem (color_ostream &out, vector ¶meters) { break; case PLANT_GROWTH: if (!select_plant_growth(out, tokens, item_type, item_subtype, - mat_type, mat_index, growth_print, material_str) + mat_type, mat_index, material_str) ) return CR_FAILURE; break; @@ -452,7 +435,7 @@ command_result df_createitem (color_ostream &out, vector ¶meters) { } for (int i = 0; i < count; i++) { - if (!makeItem(unit, item_type, item_subtype, mat_type, mat_index, growth_print, move_to_cursor, false)) + if (!makeItem(unit, item_type, item_subtype, mat_type, mat_index, move_to_cursor, false)) { out.printerr("Failed to create item!\n"); return CR_FAILURE; diff --git a/plugins/logistics.cpp b/plugins/logistics.cpp index 724f0d00e3..5c9f1fdac8 100644 --- a/plugins/logistics.cpp +++ b/plugins/logistics.cpp @@ -605,7 +605,7 @@ static int logistics_getStockpileData(lua_State *L) { ProcessorStats melt_stats, trade_stats, dump_stats, train_stats, forbid_stats, claim_stats; - for (auto bld : df::global::world->buildings.other.STOCKPILE) { + for (auto bld : world->buildings.other.STOCKPILE) { int32_t stockpile_number = bld->stockpile_number; MeltStockProcessor melt_stock_processor(stockpile_number, false, melt_stats, false); TradeStockProcessor trade_stock_processor(stockpile_number, false, trade_stats); @@ -796,7 +796,7 @@ static int logistics_getGlobalCounts(lua_State *L) { out = &Core::getInstance().getConsole(); DEBUG(control,*out).print("entering logistics_getGlobalCounts\n"); - size_t num_melt = df::global::world->items.other.ANY_MELT_DESIGNATED.size(); + size_t num_melt = world->items.other.ANY_MELT_DESIGNATED.size(); size_t num_trade = 0; for (auto link = world->jobs.list.next; link; link = link->next) { diff --git a/plugins/lua/preserve-rooms.lua b/plugins/lua/preserve-rooms.lua new file mode 100644 index 0000000000..59415499f8 --- /dev/null +++ b/plugins/lua/preserve-rooms.lua @@ -0,0 +1,258 @@ +local _ENV = mkmodule('plugins.preserve-rooms') + +local argparse = require('argparse') +local gui = require('gui') +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') + +local GLOBAL_KEY = 'preserve-rooms' + +------------------ +-- command line +-- + +local function print_status() + local features = preserve_rooms_getState() + print('Features:') + for feature,enabled in pairs(features) do + print((' %20s: %s'):format(feature, enabled)) + end +end + +local function do_set_feature(enabled, feature) + if not preserve_rooms_setFeature(enabled, feature) then + qerror(('unknown feature: "%s"'):format(feature)) + end +end + +local function do_reset_feature(feature) + if not preserve_rooms_resetFeatureState(feature) then + qerror(('unknown feature: "%s"'):format(feature)) + end +end + +function parse_commandline(args) + local opts = {} + local positionals = argparse.processArgsGetopt(args, { + {'h', 'help', handler=function() opts.help = true end}, + }) + + if opts.help or not positionals or positionals[1] == 'help' then + return false + end + + local command = table.remove(positionals, 1) + if not command or command == 'status' then + print_status() + elseif command == 'now' then + preserve_rooms_cycle() + elseif command == 'enable' or command == 'disable' then + do_set_feature(command == 'enable', positionals[1]) + elseif command == 'reset' then + do_reset_feature(positionals[1]) + else + return false + end + + return true +end + +---------------------- +-- ReservedWidget +-- + +ReservedWidget = defclass(ReservedWidget, overlay.OverlayWidget) +ReservedWidget.ATTRS{ + desc='Shows whether a zone has been reserved for a unit or role.', + default_enabled=true, + default_pos={x=37, y=9}, + viewscreens={ + 'dwarfmode/Zone/Some/Bedroom', + 'dwarfmode/Zone/Some/DiningHall', + 'dwarfmode/Zone/Some/Office', + 'dwarfmode/Zone/Some/Tomb', + }, + frame={w=44, h=15}, +} + +local new_world_loaded = true + +function ReservedWidget:init() + self.code_to_idx = {} + + self:addviews{ + widgets.Panel{ + view_id='pause_mask', + frame={t=0, l=0, w=4, h=3}, + }, + widgets.Panel{ + view_id='add_mask', + frame={t=3, l=4, w=4, h=3}, + }, + widgets.Panel{ + frame={t=0, l=9}, + visible=function() + local scr = dfhack.gui.getDFViewscreen(true) + return not dfhack.gui.matchFocusString('dwarfmode/UnitSelector', scr) and + not dfhack.gui.matchFocusString('dwarfmode/LocationSelector', scr) + end, + subviews={ + widgets.Panel{ + visible=function() + return not preserve_rooms_isReserved() and preserve_rooms_getFeature('track-roles') + end, + subviews={ + widgets.CycleHotkeyLabel{ + view_id='role', + frame={t=1, l=1, r=1, h=2}, + frame_background=gui.CLEAR_PEN, + label='Autoassign to holder of role:', + label_below=true, + key='CUSTOM_SHIFT_S', + options={{label='None', value='', pen=COLOR_YELLOW}}, + on_change=function(code) + self.subviews.list:setSelected(self.code_to_idx[code] or 1) + preserve_rooms_assignToRole(code) + end, + }, + widgets.Panel{ + view_id='hover_trigger', + frame={t=0, l=0, r=0, h=4}, + frame_style=gui.FRAME_MEDIUM, + visible=true, + }, + widgets.Panel{ + view_id='hover_expansion', + frame_style=gui.FRAME_MEDIUM, + visible=false, + subviews={ + widgets.Panel{ + frame={t=2}, + frame_background=gui.CLEAR_PEN, + }, + widgets.List{ + view_id='list', + frame={t=3, l=1}, + frame_background=gui.CLEAR_PEN, + on_submit=function(idx) + self.subviews.role:setOption(idx, true) + end, + choices={'None'}, + }, + }, + }, + }, + }, + widgets.Panel{ + frame={t=0, h=5}, + frame_style=gui.FRAME_MEDIUM, + frame_background=gui.CLEAR_PEN, + visible=preserve_rooms_isReserved, + subviews={ + widgets.Label{ + frame={t=0, l=0}, + text={ + 'Reserved for traveling unit:', NEWLINE, + {gap=1, text=preserve_rooms_getReservationName, pen=COLOR_YELLOW}, + }, + }, + widgets.HotkeyLabel{ + frame={t=2, l=0}, + key='CUSTOM_SHIFT_R', + label='Clear reservation', + on_activate=preserve_rooms_clearReservation, + }, + }, + }, + widgets.HelpButton{ + command='preserve-rooms', + visible=function() + return preserve_rooms_isReserved() or preserve_rooms_getFeature('track-roles') + end, + }, + }, + }, + } +end + +function ReservedWidget:onInput(keys) + if ReservedWidget.super.onInput(self, keys) then + return true + end + if keys._MOUSE_L and preserve_rooms_isReserved() then + if self.subviews.pause_mask:getMousePos() then return true end + if self.subviews.add_mask:getMousePos() then return true end + end +end + +function ReservedWidget:render(dc) + if new_world_loaded then + self:refresh_role_list() + new_world_loaded = false + end + + local code = preserve_rooms_getRoleAssignment() + local role = self.subviews.role + if code ~= role:getOptionValue() then + role:setOption(code) + self.subviews.list:setSelected(self.code_to_idx[code] or 1) + end + + local hover_expansion = self.subviews.hover_expansion + if hover_expansion.visible and not hover_expansion:getMouseFramePos() then + hover_expansion.visible = false + elseif self.subviews.hover_trigger:getMousePos() then + hover_expansion.visible = true + end + + ReservedWidget.super.render(self, dc) +end + +local function to_title_case(str) + return dfhack.capitalizeStringWords(dfhack.lowerCp437(str:gsub('_', ' '))) +end + +local function add_codes(codes, entity) + if not entity then return end + for _,role in ipairs(entity.positions.own) do + table.insert(codes, role.code) + end +end + +local function add_options(options, choices, codes) + for _,code in ipairs(codes) do + local name = to_title_case(code) + table.insert(options, {label=name, value=code, pen=COLOR_YELLOW}) + table.insert(choices, name) + end +end + +function ReservedWidget:refresh_role_list() + local codes, options, choices = {}, {{label='None', value='', pen=COLOR_YELLOW}}, {'None'} + add_codes(codes, df.historical_entity.find(df.global.plotinfo.civ_id)); + add_codes(codes, df.historical_entity.find(df.global.plotinfo.group_id)); + table.sort(codes) + add_options(options, choices, codes) + + self.code_to_idx = {['']=1} + for idx,code in ipairs(codes) do + self.code_to_idx[code] = idx + 1 -- offset for None option + end + + self.subviews.role.options = options + self.subviews.role:setOption(1) + self.subviews.list:setChoices(choices, 1) +end + +OVERLAY_WIDGETS = { + reserved=ReservedWidget, +} + +dfhack.onStateChange[GLOBAL_KEY] = function(sc) + if sc ~= SC_MAP_LOADED or not dfhack.world.isFortressMode() then + return + end + new_world_loaded = true +end + +return _ENV diff --git a/plugins/preserve-rooms.cpp b/plugins/preserve-rooms.cpp new file mode 100644 index 0000000000..e8a1383261 --- /dev/null +++ b/plugins/preserve-rooms.cpp @@ -0,0 +1,530 @@ +#include "Core.h" +#include "Debug.h" +#include "LuaTools.h" +#include "PluginManager.h" + +#include "modules/Buildings.h" +#include "modules/EventManager.h" +#include "modules/Gui.h" +#include "modules/Units.h" +#include "modules/World.h" + +#include "df/building_civzonest.h" +#include "df/historical_figure.h" +#include "df/histfig_hf_link.h" +#include "df/unit.h" +#include "df/world.h" + +#include +#include +#include + +using std::pair; +using std::string; +using std::unordered_map; +using std::unordered_set; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("preserve-rooms"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +namespace DFHack { + DBG_DECLARE(preserverooms, control, DebugCategory::LINFO); + DBG_DECLARE(preserverooms, cycle, DebugCategory::LINFO); + DBG_DECLARE(preserverooms, event, DebugCategory::LINFO); +} + +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static PersistentDataItem config; + +// zone id, hfids (main, spouse) +typedef vector>> ZoneAssignments; +static ZoneAssignments last_known_assignments_bedroom; +static ZoneAssignments last_known_assignments_office; +static ZoneAssignments last_known_assignments_dining; +static ZoneAssignments last_known_assignments_tomb; +// hfid -> zone ids +static unordered_map> pending_reassignment; +// zone id -> hfids +static unordered_map> reserved_zones; + +// zone id -> noble/administrative position code +static unordered_map noble_zones; + +// as a "system" plugin, we do not persist plugin enabled state, just feature enabled state +enum ConfigValues { + CONFIG_TRACK_MISSIONS = 0, + CONFIG_TRACK_ROLES = 1, +}; + +static const int32_t CYCLE_TICKS = 109; +static int32_t cycle_timestamp = 0; // world->frame_counter at last cycle + +static command_result do_command(color_ostream &out, vector ¶meters); +static void on_new_active_unit(color_ostream& out, void* data); +static void do_cycle(color_ostream &out); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(control,out).print("initializing %s\n", plugin_name); + commands.push_back(PluginCommand( + plugin_name, + "Manage room assignments for off-map units and noble roles.", + do_command)); + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + static EventManager::EventHandler new_unit_handler(plugin_self, on_new_active_unit, CYCLE_TICKS); + + if (enable != is_enabled) { + is_enabled = enable; + if (enable) + EventManager::registerListener(EventManager::EventType::UNIT_NEW_ACTIVE, new_unit_handler); + else + EventManager::unregisterAll(plugin_self); + } + + DEBUG(control, out).print("now %s\n", is_enabled ? "enabled" : "disabled"); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown(color_ostream &out) { + DEBUG(control,out).print("shutting down %s\n", plugin_name); + return CR_OK; +} + +static void clear_track_missions_state() { + last_known_assignments_bedroom.clear(); + last_known_assignments_office.clear(); + last_known_assignments_dining.clear(); + last_known_assignments_tomb.clear(); + pending_reassignment.clear(); + reserved_zones.clear(); +} + +static void clear_track_roles_state() { + noble_zones.clear(); +} + +DFhackCExport command_result plugin_load_site_data(color_ostream &out) { + cycle_timestamp = 0; + config = World::GetPersistentSiteData(CONFIG_KEY); + + if (!config.isValid()) { + DEBUG(control,out).print("no config found in this save; initializing\n"); + config = World::AddPersistentSiteData(CONFIG_KEY); + config.set_bool(CONFIG_TRACK_MISSIONS, true); + config.set_bool(CONFIG_TRACK_ROLES, true); + } + + clear_track_missions_state(); + clear_track_roles_state(); + + // TODO: restore vectors/maps from serialized state + + return CR_OK; +} + +DFhackCExport command_result plugin_save_site_data (color_ostream &out) { + // TODO: serialize vectors/maps + + return CR_OK; +} + +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) { + if (event == DFHack::SC_WORLD_UNLOADED) { + if (is_enabled) { + DEBUG(control,out).print("world unloaded; disabling %s\n", + plugin_name); + is_enabled = false; + } + } + return CR_OK; +} + +DFhackCExport command_result plugin_onupdate(color_ostream &out) { + if (!Core::getInstance().isMapLoaded() || !World::isFortressMode()) + return CR_OK; + if (world->frame_counter - cycle_timestamp >= CYCLE_TICKS) + do_cycle(out); + return CR_OK; +} + +static command_result do_command(color_ostream &out, vector ¶meters) { + CoreSuspender suspend; + + if (!World::isFortressMode() || !Core::getInstance().isMapLoaded()) { + out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + return CR_FAILURE; + } + + bool show_help = false; + if (!Lua::CallLuaModuleFunction(out, "plugins.preserve-rooms", "parse_commandline", std::make_tuple(parameters), + 1, [&](lua_State *L) { + show_help = !lua_toboolean(L, -1); + })) { + return CR_FAILURE; + } + + return show_help ? CR_WRONG_USAGE : CR_OK; +} + +///////////////////////////////////////////////////// +// cycle logic +// + +static bool is_noble_zone(int32_t zone_id, const string & code) { + auto it = noble_zones.find(zone_id); + return it != noble_zones.end() && it->second == code; +} + +static void assign_nobles(color_ostream &out) { + for (auto &[zone_id, code] : noble_zones) { + auto zone = virtual_cast(df::building::find(zone_id)); + if (!zone || !zone->spec_sub_flag.bits.active) + continue; + vector units; + Units::getUnitsByNobleRole(units, code); + // if zone is already assigned to a proper unit, skip + if (linear_index(units, zone->assigned_unit) >= 0) + continue; + // assign to a relevant noble that does not already have a registered zone of this type assigned + for (auto unit : units) { + if (!Units::isCitizen(unit, true) && !Units::isResident(unit, true)) + continue; + bool found = false; + for (auto owned_zone : unit->owned_buildings) { + if (owned_zone->type == zone->type && is_noble_zone(owned_zone->id, code)) { + found = true; + break; + } + } + if (found) + continue; + Buildings::setOwner(zone, unit); + INFO(cycle,out).print("assigning %s to a %s-associated %s\n", + Units::getReadableName(unit).c_str(), code.c_str(), + ENUM_KEY_STR(civzone_type, zone->type).c_str()); + break; + } + } +} + +static void clear_reservation(color_ostream &out, int32_t zone_id, df::building_civzonest * zone = NULL) { + auto it = reserved_zones.find(zone_id); + if (it == reserved_zones.end()) + return; + for (auto hfid : it->second) { + auto pending_it = pending_reassignment.find(hfid); + if (pending_it != pending_reassignment.end()) { + auto & zone_ids = pending_it->second; + vector_erase_at(zone_ids, linear_index(zone_ids, zone_id)); + if (zone_ids.empty()) + pending_reassignment.erase(pending_it); + } + } + reserved_zones.erase(zone_id); + if (!zone) + zone = virtual_cast(df::building::find(zone_id)); + if (zone) + zone->spec_sub_flag.bits.active = true; +} + +static int32_t get_spouse_hfid(color_ostream &out, df::historical_figure *hf) { + for (auto link : hf->histfig_links) { + if (link->getType() == df::histfig_hf_link_type::SPOUSE) + return link->target_hf; + } + return -1; +} + +// handles when units disappear from their assignments compared to the last scan +static void handle_missing_assignments(color_ostream &out, + const unordered_set & active_unit_ids, + ZoneAssignments::iterator *pit, + const ZoneAssignments::iterator & it_end, + bool share_with_spouse, + int32_t next_zone_id) +{ + for (auto & it = *pit; it != it_end && (next_zone_id == -1 || it->first <= next_zone_id); ++it) { + int32_t zone_id = it->first; + int32_t hfid = it->second.first; + int32_t spouse_hfid = it->second.second; + auto hf = df::historical_figure::find(hfid); + auto unit = df::unit::find(hf->unit_id); + if (!unit) { + // if unit data is completely gone, then they're not likely to come back + continue; + } + if (Units::isActive(unit) && !Units::isDead(unit) && active_unit_ids.contains(unit->id)) { + // unit is still alive on the map; assume the unassigment was intentional/expected + continue; + } + if (!Units::isCitizen(unit, true) && !Units::isResident(unit, true)) + continue; + auto zone = virtual_cast(df::building::find(zone_id)); + if (!zone) + continue; + // unit is off-map or is dead; if we can assign room to spouse then we don't need to reserve the room + if (auto spouse_hf = df::historical_figure::find(spouse_hfid); spouse_hf && share_with_spouse) { + if (auto spouse = df::unit::find(spouse_hf->unit_id); + spouse && Units::isActive(spouse) && !Units::isDead(spouse) && active_unit_ids.contains(spouse->id)) + { + DEBUG(cycle,out).print("assigning zone %d (%s) to spouse %s\n", + zone_id, ENUM_KEY_STR(civzone_type, zone->type).c_str(), + Units::getReadableName(spouse).c_str()); + Buildings::setOwner(zone, spouse); + continue; + } + } + if (Units::isDead(unit)) + continue; + // register the hf ids for reassignment and reserve the room + DEBUG(cycle,out).print("registering primary unit for reassignment to zone %d (%s): %d %s\n", + zone_id, ENUM_KEY_STR(civzone_type, zone->type).c_str(), unit->id, + Units::getReadableName(unit).c_str()); + pending_reassignment[hfid].push_back(zone_id); + reserved_zones[zone_id].push_back(hfid); + if (share_with_spouse && spouse_hfid > -1) { + DEBUG(cycle,out).print("registering spouse unit for reassignment to zone %d (%s): hfid=%d\n", + zone_id, ENUM_KEY_STR(civzone_type, zone->type).c_str(), spouse_hfid); + pending_reassignment[spouse_hfid].push_back(zone_id); + reserved_zones[zone_id].push_back(spouse_hfid); + } + zone->spec_sub_flag.bits.active = false; + } +} + +static void process_rooms(color_ostream &out, + const unordered_set & active_unit_ids, + ZoneAssignments & last_known, + const vector & vec, + bool share_with_spouse=true) +{ + ZoneAssignments assignments; + auto it = last_known.begin(); + auto it_end = last_known.end(); + for (auto zone : vec) { + if (!zone->assigned_unit) { + handle_missing_assignments(out, active_unit_ids, &it, it_end, share_with_spouse, zone->id); + continue; + } + auto hf = df::historical_figure::find(zone->assigned_unit->hist_figure_id); + if (!hf) + continue; + int32_t spouse_hfid = share_with_spouse ? get_spouse_hfid(out, hf) : -1; + assignments.emplace_back(zone->id, std::make_pair(hf->id, spouse_hfid)); + } + handle_missing_assignments(out, active_unit_ids, &it, it_end, share_with_spouse, -1); + + last_known = assignments; +} + +static void do_cycle(color_ostream &out) { + cycle_timestamp = world->frame_counter; + + TRACE(cycle,out).print("running %s cycle\n", plugin_name); + + assign_nobles(out); + + unordered_set active_unit_ids; + std::transform(world->units.active.begin(), world->units.active.end(), + std::inserter(active_unit_ids, active_unit_ids.end()), [](auto & unit){ return unit->id; }); + + process_rooms(out, active_unit_ids, last_known_assignments_bedroom, world->buildings.other.ZONE_BEDROOM); + process_rooms(out, active_unit_ids, last_known_assignments_office, world->buildings.other.ZONE_OFFICE); + process_rooms(out, active_unit_ids, last_known_assignments_dining, world->buildings.other.ZONE_DINING_HALL); + process_rooms(out, active_unit_ids, last_known_assignments_tomb, world->buildings.other.ZONE_TOMB, false); + + DEBUG(cycle,out).print("tracking zone assignments: bedrooms: %zd, offices: %zd, dining halls: %zd, tombs: %zd\n", + last_known_assignments_bedroom.size(), last_known_assignments_office.size(), + last_known_assignments_dining.size(), last_known_assignments_tomb.size()); +} + +///////////////////////////////////////////////////// +// Event logic +// + +static bool spouse_has_sharable_room(color_ostream& out, int32_t hfid, df::civzone_type ztype) { + if (ztype == df::civzone_type::Tomb) + return false; + auto hf = df::historical_figure::find(hfid); + if (!hf) + return false; + auto spouse_hf = df::historical_figure::find(get_spouse_hfid(out, hf)); + if (!spouse_hf) + return false; + auto spouse = df::unit::find(spouse_hf->unit_id); + if (!spouse) + return false; + for (auto owned_zone : spouse->owned_buildings) { + if (owned_zone->type == ztype) { + DEBUG(event,out).print("spouse had sharable room; no need to set ownership\n"); + return true; + } + } + return false; +} + +static void on_new_active_unit(color_ostream& out, void* data) { + int32_t unit_id = reinterpret_cast(data); + auto unit = df::unit::find(unit_id); + if (!unit || unit->hist_figure_id < 0) + return; + TRACE(event,out).print("unit %d (%s) arrived on map (hfid: %d, in pending: %d)\n", + unit->id, Units::getReadableName(unit).c_str(), unit->hist_figure_id, + pending_reassignment.contains(unit->hist_figure_id)); + auto hfid = unit->hist_figure_id; + auto it = pending_reassignment.find(hfid); + if (it == pending_reassignment.end()) + return; + DEBUG(event,out).print("restoring zone ownership for unit %d (%s)\n", + unit->id, Units::getReadableName(unit).c_str()); + for (auto zone_id : it->second) { + reserved_zones.erase(zone_id); + auto zone = virtual_cast(df::building::find(zone_id)); + if (!zone || zone->assigned_unit || spouse_has_sharable_room(out, hfid, zone->type)) + continue; + DEBUG(event,out).print("assigning and activating zone %d\n", zone->id); + Buildings::setOwner(zone, unit); + zone->spec_sub_flag.bits.active = true; + } + pending_reassignment.erase(it); +} + +///////////////////////////////////////////////////// +// Lua API +// + +static void preserve_rooms_cycle(color_ostream &out) { + DEBUG(control,out).print("preserve_rooms_cycle\n"); + do_cycle(out); +} + +static bool preserve_rooms_setFeature(color_ostream &out, bool enabled, string feature) { + DEBUG(control,out).print("preserve_rooms_setFeature: enabled=%d, feature=%s\n", + enabled, feature.c_str()); + if (feature == "track-missions") { + config.set_bool(CONFIG_TRACK_MISSIONS, enabled); + if (is_enabled && enabled) + do_cycle(out); + } else if (feature == "track-roles") { + config.set_bool(CONFIG_TRACK_ROLES, enabled); + } else { + return false; + } + + return true; +} + +static bool preserve_rooms_getFeature(color_ostream &out, string feature) { + TRACE(control,out).print("preserve_rooms_getFeature: feature=%s\n", feature.c_str()); + if (feature == "track-missions") + return config.get_bool(CONFIG_TRACK_MISSIONS); + if (feature == "track-roles") + return config.get_bool(CONFIG_TRACK_ROLES); + return false; +} + +static bool preserve_rooms_resetFeatureState(color_ostream &out, string feature) { + DEBUG(control,out).print("preserve_rooms_resetFeatureState: feature=%s\n", feature.c_str()); + if (feature == "track-missions") { + vector zone_ids; + std::transform(reserved_zones.begin(), reserved_zones.end(), std::back_inserter(zone_ids), [](auto & elem){ return elem.first; }); + for (auto zone_id : zone_ids) + clear_reservation(out, zone_id); + clear_track_missions_state(); + } else if (feature == "track-roles") { + clear_track_roles_state(); + } else { + return false; + } + + return true; +} + +static void preserve_rooms_assignToRole(color_ostream &out, string code) { + auto zone = Gui::getSelectedCivZone(out, true); + if (!zone) + return; + DEBUG(control,out).print("preserve_rooms_assignToRole: zone_id=%d, code=%s\n", zone->id, code.c_str()); + noble_zones[zone->id] = code; + do_cycle(out); +} + +static string preserve_rooms_getRoleAssignment(color_ostream &out) { + auto zone = Gui::getSelectedCivZone(out, true); + if (!zone) + return ""; + TRACE(control,out).print("preserve_rooms_getRoleAssignment: zone_id=%d\n", zone->id); + auto it = noble_zones.find(zone->id); + if (it == noble_zones.end()) + return ""; + return it->second; +} + +static bool preserve_rooms_isReserved(color_ostream &out) { + auto zone = Gui::getSelectedCivZone(out, true); + if (!zone) + return false; + TRACE(control,out).print("preserve_rooms_isReserved: zone_id=%d\n", zone->id); + auto it = reserved_zones.find(zone->id); + return it != reserved_zones.end() && it->second.size() > 0; +} + +static string preserve_rooms_getReservationName(color_ostream &out) { + auto zone = Gui::getSelectedCivZone(out, true); + if (!zone) + return ""; + TRACE(control,out).print("preserve_rooms_getReservationName: zone_id=%d\n", zone->id); + auto it = reserved_zones.find(zone->id); + if (it != reserved_zones.end() && it->second.size() > 0) { + if (auto hf = df::historical_figure::find(it->second.front())) { + return Units::getReadableName(hf); + } + } + return ""; +} + +static bool preserve_rooms_clearReservation(color_ostream &out) { + auto zone = Gui::getSelectedCivZone(out, true); + if (!zone) + return false; + DEBUG(control,out).print("preserve_rooms_clearReservation: zone_id=%d\n", zone->id); + clear_reservation(out, zone->id, zone); + return true; +} + +static int preserve_rooms_getState(lua_State *L) { + color_ostream *out = Lua::GetOutput(L); + if (!out) + out = &Core::getInstance().getConsole(); + DEBUG(control,*out).print("preserve_rooms_getState\n"); + + unordered_map features; + features.emplace("track-missions", config.get_bool(CONFIG_TRACK_MISSIONS)); + features.emplace("track-roles", config.get_bool(CONFIG_TRACK_ROLES)); + Lua::Push(L, features); + + return 1; +} + +DFHACK_PLUGIN_LUA_FUNCTIONS{ + DFHACK_LUA_FUNCTION(preserve_rooms_cycle), + DFHACK_LUA_FUNCTION(preserve_rooms_setFeature), + DFHACK_LUA_FUNCTION(preserve_rooms_getFeature), + DFHACK_LUA_FUNCTION(preserve_rooms_resetFeatureState), + DFHACK_LUA_FUNCTION(preserve_rooms_assignToRole), + DFHACK_LUA_FUNCTION(preserve_rooms_getRoleAssignment), + DFHACK_LUA_FUNCTION(preserve_rooms_isReserved), + DFHACK_LUA_FUNCTION(preserve_rooms_getReservationName), + DFHACK_LUA_FUNCTION(preserve_rooms_clearReservation), + DFHACK_LUA_END}; + +DFHACK_PLUGIN_LUA_COMMANDS{ + DFHACK_LUA_COMMAND(preserve_rooms_getState), + DFHACK_LUA_END}; diff --git a/plugins/preserve-tombs.cpp b/plugins/preserve-tombs.cpp index a610d81afd..b7e4abd5ec 100644 --- a/plugins/preserve-tombs.cpp +++ b/plugins/preserve-tombs.cpp @@ -1,6 +1,5 @@ #include "Debug.h" #include "PluginManager.h" -#include "MiscUtils.h" #include "modules/Units.h" #include "modules/Buildings.h" diff --git a/scripts b/scripts index c760fe161f..b994c8ab14 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit c760fe161f4a17e989a6e781db0c8e44002f67ee +Subproject commit b994c8ab14634de3b0e9c5fe69639fff3b884547