From e3e589dc65ab77aa6408d6d0d55ca7bfce0f5188 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Sat, 10 Aug 2024 06:06:48 -0500 Subject: [PATCH 01/18] Lua API.rst: unintended literal block --- docs/dev/Lua API.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index b17758f88d..8cb728d112 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -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 From 756a382d75458d940d375e1236078615114b2b99 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Thu, 15 Aug 2024 05:20:25 -0500 Subject: [PATCH 02/18] docs spelling fixes --- docs/about/History.rst | 30 +++++++++++++------------- docs/changelog.txt | 32 ++++++++++++++-------------- docs/dev/Contributing.rst | 2 +- docs/dev/Documentation.rst | 2 +- docs/dev/Lua API.rst | 28 ++++++++++++------------ docs/dev/overlay-dev-guide.rst | 2 +- docs/guides/quickfort-user-guide.rst | 6 +++--- docs/plugins/autoclothing.rst | 4 ++-- docs/plugins/buildingplan.rst | 2 +- docs/plugins/burrow.rst | 2 +- docs/plugins/fastdwarf.rst | 2 +- docs/plugins/orders.rst | 2 +- docs/plugins/regrass.rst | 2 +- docs/plugins/reveal.rst | 2 +- docs/plugins/sort.rst | 4 ++-- docs/plugins/tailor.rst | 2 +- docs/plugins/zone.rst | 2 +- 17 files changed, 63 insertions(+), 63 deletions(-) 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..6971876597 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -141,7 +141,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 +167,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 +217,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 +240,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 +360,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 +398,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 +427,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 +555,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 +607,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 +662,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 +760,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 +769,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 +969,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 +985,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 +1115,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 8cb728d112..6e57a31dd4 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. @@ -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. @@ -5213,7 +5213,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 +5303,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 +5314,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 +5526,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 +6484,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/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. From 362c3fafcda7c3982d32e97cfdda65001281c739 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Mon, 26 Aug 2024 07:16:10 +0000 Subject: [PATCH 03/18] Auto-update submodules library/xml: master scripts: master --- library/xml | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/xml b/library/xml index 50e00be41d..f001aeef01 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 50e00be41d0debebf81252a5ee553aa08b3bd84e +Subproject commit f001aeef01759a8ad52be7499b13a9a1a9861904 diff --git a/scripts b/scripts index c760fe161f..20a01478de 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit c760fe161f4a17e989a6e781db0c8e44002f67ee +Subproject commit 20a01478de34f2d6a0fcad6f0557991a100a6a83 From 0345dc96acb6bee703179aeab83e4e98e9b393b0 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Sat, 10 Aug 2024 06:08:45 -0500 Subject: [PATCH 04/18] gui/dwarfmode: add missing MOVEMENT_KEYS fields --- docs/changelog.txt | 1 + library/lua/gui/dwarfmode.lua | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 6971876597..781695462d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -62,6 +62,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 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 }, From b9ff1578218887cd475a1827fddf1e2245d827c4 Mon Sep 17 00:00:00 2001 From: Chris Johnsen Date: Fri, 9 Aug 2024 05:20:05 -0500 Subject: [PATCH 05/18] gui.simulateInput: do not treat _STRING value as a keycode _STRING's value is a byte representing a CP-437 character, so it should not be interpreted as a df::interface_key integer keycode. Ignore _STRING since it is a convenience value. The "text content" of the key-set is transmitted via a STRING_A??? keycode. --- docs/changelog.txt | 1 + docs/dev/Lua API.rst | 5 ++++- library/lua/gui.lua | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 781695462d..16278a9a93 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -79,6 +79,7 @@ Template for new versions: - ``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`` ## 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 diff --git a/docs/dev/Lua API.rst b/docs/dev/Lua API.rst index 6e57a31dd4..3b288ddc10 100644 --- a/docs/dev/Lua API.rst +++ b/docs/dev/Lua API.rst @@ -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)`` 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 From a8076bd468191436c3e4cca8fd7f150ccf6916c5 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Tue, 27 Aug 2024 07:15:46 +0000 Subject: [PATCH 06/18] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 20a01478de..b994c8ab14 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 20a01478de34f2d6a0fcad6f0557991a100a6a83 +Subproject commit b994c8ab14634de3b0e9c5fe69639fff3b884547 From f99512d60cc3bea261ab5ed04a97908ec5e2dbcd Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 7 Jun 2024 23:51:05 -0700 Subject: [PATCH 07/18] initial skeleton for preserve-rooms --- plugins/CMakeLists.txt | 2 +- plugins/lua/preserve-tombs.lua | 72 ++++++++++++++++++++++++++++++++++ plugins/preserve-tombs.cpp | 1 - 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 plugins/lua/preserve-tombs.lua diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index a76bd79332..56682033a0 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -142,7 +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-tombs preserve-tombs.cpp) + dfhack_plugin(preserve-tombs preserve-tombs.cpp LINK_LIBRARIES lua) dfhack_plugin(probe probe.cpp LINK_LIBRARIES lua) dfhack_plugin(prospector prospector.cpp LINK_LIBRARIES lua) #dfhack_plugin(power-meter power-meter.cpp LINK_LIBRARIES lua) diff --git a/plugins/lua/preserve-tombs.lua b/plugins/lua/preserve-tombs.lua new file mode 100644 index 0000000000..2113788556 --- /dev/null +++ b/plugins/lua/preserve-tombs.lua @@ -0,0 +1,72 @@ +local _ENV = mkmodule('plugins.preserve-tombs') + +local gui = require('gui') +local overlay = require('plugins.overlay') +local widgets = require('gui.widgets') + +---------------------- +-- BadgeWidget +-- + +BadgeWidget = defclass(BadgeWidget, overlay.OverlayWidget) +BadgeWidget.ATTRS{ + desc='Shows an indicator that a zone is managed by preserve-rooms.', + default_enabled=true, + default_pos={x=30, y=10}, + viewscreens={ + 'dwarfmode/Zone/Some/Office', + }, + frame={w=40, h=3}, + frame_style=gui.FRAME_MEDIUM, +} + +function BadgeWidget:init() + self:addviews{ + widgets.Label{ + frame={l=0, t=0}, + text={ + 'Autoassigned to role:', + {gap=1, text=get_current_role_assignment, pen=COLOR_MAGENTA}, + }, + }, + } +end + +function BadgeWidget:render(dc) + if not has_current_role_assignment() then + return + end + BadgetWidget.super.render(self, dc) +end + +---------------------- +-- RoleAssignWidget +-- + +RoleAssignWidget = defclass(RoleAssignWidget, overlay.OverlayWidget) +RoleAssignWidget.ATTRS{ + desc='Adds a configuration dropdown for autoassigning rooms to noble roles.', + default_enabled=true, + default_pos={x=40, y=8}, + viewscreens={ + 'dwarfmode/UnitSelector/ZONE_OFFICE_ASSIGNMENT', + }, + frame={w=20, h=1}, +} + +function RoleAssignWidget:init() + self:addviews{ + widgets.TextLabel{ + frame={l=0, t=0, w=10}, + label='Assign to role', + key='CUSTOM_SHIFT_S', + }, + } +end + +OVERLAY_WIDGETS = { + badge=BadgeWidget, + roleassign=RoleAssignWidget, +} + +return _ENV 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" From f13466861bf3d8a3640b741074abe1cfdfe57bdf Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 23 Aug 2024 17:18:21 -0700 Subject: [PATCH 08/18] more skeleton --- plugins/CMakeLists.txt | 1 + plugins/preserve-rooms.cpp | 139 +++++++++++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 plugins/preserve-rooms.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 56682033a0..2ece3630a7 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 LINK_LIBRARIES lua) dfhack_plugin(probe probe.cpp LINK_LIBRARIES lua) dfhack_plugin(prospector prospector.cpp LINK_LIBRARIES lua) diff --git a/plugins/preserve-rooms.cpp b/plugins/preserve-rooms.cpp new file mode 100644 index 0000000000..949b4abdd7 --- /dev/null +++ b/plugins/preserve-rooms.cpp @@ -0,0 +1,139 @@ +#include "Core.h" +#include "Debug.h" +#include "PluginManager.h" + +#include "modules/World.h" + +#include "df/world.h" + +#include +#include +#include + +using std::string; +using std::unordered_map; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("preserve-rooms"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); + +REQUIRE_GLOBAL(world); + +namespace DFHack { + DBG_DECLARE(persistent_per_save_example, control, DebugCategory::LINFO); + DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO); +} + +static const string CONFIG_KEY = string(plugin_name) + "/config"; +static PersistentDataItem config; + +static std::map last_known_assignments; +static std::map> pending_reassignment; + +enum ConfigValues { + CONFIG_IS_ENABLED = 0, +}; + +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 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, + "Prevent room assignments from being lost.", + do_command)); + return CR_OK; +} + +DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { + if (!World::isFortressMode() || !Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) { + out.printerr("Cannot enable %s without a loaded fort.\n", plugin_name); + return CR_FAILURE; + } + + if (enable != is_enabled) { + is_enabled = enable; + DEBUG(control,out).print("%s from the API; persisting\n", + is_enabled ? "enabled" : "disabled"); + config.set_bool(CONFIG_IS_ENABLED, is_enabled); + if (enable) + do_cycle(out); + } else { + DEBUG(control,out).print("%s from the API, but already %s; no action\n", + is_enabled ? "enabled" : "disabled", + 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; +} + +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_IS_ENABLED, is_enabled); + } + + is_enabled = config.get_bool(CONFIG_IS_ENABLED); + DEBUG(control,out).print("loading persisted enabled state: %s\n", + is_enabled ? "true" : "false"); + + 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 (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() || !World::IsSiteLoaded()) { + out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); + return CR_FAILURE; + } + + // TODO: configuration logic + // simple commandline parsing can be done in C++, but there are lua libraries + // that can easily handle more complex commandlines. see the seedwatch plugin + // for a simple example. + + return CR_OK; +} + +///////////////////////////////////////////////////// +// cycle logic +// + +static void do_cycle(color_ostream &out) { + cycle_timestamp = world->frame_counter; + + DEBUG(cycle,out).print("running %s cycle\n", plugin_name); +} From 92a979f3dd3ac57ca09f3f0ee4fa9d80dda95f6e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Sun, 25 Aug 2024 14:50:52 -0700 Subject: [PATCH 09/18] move logic to preserve-rooms --- plugins/CMakeLists.txt | 2 +- plugins/lua/{preserve-tombs.lua => preserve-rooms.lua} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename plugins/lua/{preserve-tombs.lua => preserve-rooms.lua} (100%) diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 2ece3630a7..f0b35ea9ef 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -143,7 +143,7 @@ if(BUILD_SUPPORTED) 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 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) #dfhack_plugin(power-meter power-meter.cpp LINK_LIBRARIES lua) diff --git a/plugins/lua/preserve-tombs.lua b/plugins/lua/preserve-rooms.lua similarity index 100% rename from plugins/lua/preserve-tombs.lua rename to plugins/lua/preserve-rooms.lua From a1be28eceeec92827266fb6ae4d4037a95fdbb7e Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Aug 2024 02:42:58 -0700 Subject: [PATCH 10/18] finish skeleton --- data/init/dfhack.tools.init | 1 + docs/plugins/preserve-rooms.rst | 73 +++++++++++++++++++ plugins/lua/preserve-rooms.lua | 70 ++++++++++++++++--- plugins/preserve-rooms.cpp | 120 +++++++++++++++++++++++--------- 4 files changed, 224 insertions(+), 40 deletions(-) create mode 100644 docs/plugins/preserve-rooms.rst 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/plugins/preserve-rooms.rst b/docs/plugins/preserve-rooms.rst new file mode 100644 index 0000000000..5ef4e48e60 --- /dev/null +++ b/docs/plugins/preserve-rooms.rst @@ -0,0 +1,73 @@ +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, and +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 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 solves 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, 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 role. The room will be automatically reassigned +to whoever currently holds that noble position. If multiple rooms of the same +type are assigned to a position that can be filled by multiple citizens (e.g. +you can have many barons), then only one room of that type will be assigned to +each holder of that position. + +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-raids`` + Disable the ``track-raids`` feature for this fort. +``preserve-rooms reset noble-roles`` + Clear all configuration related to the ``noble-roles`` feature. + +Features +-------- + +``track-raids`` + Reserve the rooms assigned to units that leave the map and reassign them + upon their return. This feature is automatically enabled for new forts + unless disabled in `gui/control-panel` ("Bugfixes" tab). +``noble-roles`` + Allow rooms to be associated with noble roles. Associated rooms will be + automatically assigned to the current holder of the specified role. This + feature is enabled by default for all forts. + +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. It also provides widgets to mark the zone as associated with a role. diff --git a/plugins/lua/preserve-rooms.lua b/plugins/lua/preserve-rooms.lua index 2113788556..910bb88d8f 100644 --- a/plugins/lua/preserve-rooms.lua +++ b/plugins/lua/preserve-rooms.lua @@ -1,26 +1,78 @@ -local _ENV = mkmodule('plugins.preserve-tombs') +local _ENV = mkmodule('plugins.preserve-rooms') local gui = require('gui') local overlay = require('plugins.overlay') local widgets = require('gui.widgets') +------------------ +-- 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 + ---------------------- --- BadgeWidget +-- ReservedWidget -- -BadgeWidget = defclass(BadgeWidget, overlay.OverlayWidget) -BadgeWidget.ATTRS{ - desc='Shows an indicator that a zone is managed by preserve-rooms.', +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=30, y=10}, viewscreens={ + 'dwarfmode/Zone/Some/Bedroom', + 'dwarfmode/Zone/Some/DiningHall', 'dwarfmode/Zone/Some/Office', + 'dwarfmode/Zone/Some/Tomb', }, frame={w=40, h=3}, - frame_style=gui.FRAME_MEDIUM, } -function BadgeWidget:init() +function ReservedWidget:init() self:addviews{ widgets.Label{ frame={l=0, t=0}, @@ -32,7 +84,7 @@ function BadgeWidget:init() } end -function BadgeWidget:render(dc) +function ReservedWidget:render(dc) if not has_current_role_assignment() then return end @@ -65,7 +117,7 @@ function RoleAssignWidget:init() end OVERLAY_WIDGETS = { - badge=BadgeWidget, + badge=ReservedWidget, roleassign=RoleAssignWidget, } diff --git a/plugins/preserve-rooms.cpp b/plugins/preserve-rooms.cpp index 949b4abdd7..ef1d6ec5b8 100644 --- a/plugins/preserve-rooms.cpp +++ b/plugins/preserve-rooms.cpp @@ -1,5 +1,6 @@ #include "Core.h" #include "Debug.h" +#include "LuaTools.h" #include "PluginManager.h" #include "modules/World.h" @@ -29,11 +30,15 @@ namespace DFHack { static const string CONFIG_KEY = string(plugin_name) + "/config"; static PersistentDataItem config; -static std::map last_known_assignments; +// zone id -> unit ids (includes spouses) +static std::map> last_known_assignments; +// unit id -> zone ids static std::map> pending_reassignment; +// as a "system" plugin, we do not persist plugin enabled state enum ConfigValues { - CONFIG_IS_ENABLED = 0, + CONFIG_TRACK_RAIDS = 0, + CONFIG_NOBLE_ROLES = 1, }; static const int32_t CYCLE_TICKS = 109; @@ -46,35 +51,19 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector frame_counter - cycle_timestamp >= CYCLE_TICKS) do_cycle(out); return CR_OK; @@ -115,17 +106,20 @@ DFhackCExport command_result plugin_onupdate(color_ostream &out) { static command_result do_command(color_ostream &out, vector ¶meters) { CoreSuspender suspend; - if (!World::isFortressMode() || !Core::getInstance().isMapLoaded() || !World::IsSiteLoaded()) { + if (!World::isFortressMode() || !Core::getInstance().isMapLoaded()) { out.printerr("Cannot run %s without a loaded fort.\n", plugin_name); return CR_FAILURE; } - // TODO: configuration logic - // simple commandline parsing can be done in C++, but there are lua libraries - // that can easily handle more complex commandlines. see the seedwatch plugin - // for a simple example. + 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 CR_OK; + return show_help ? CR_WRONG_USAGE : CR_OK; } ///////////////////////////////////////////////////// @@ -137,3 +131,67 @@ static void do_cycle(color_ostream &out) { DEBUG(cycle,out).print("running %s cycle\n", plugin_name); } + +///////////////////////////////////////////////////// +// Lua API +// + +static void preserve_rooms_cycle(color_ostream &out) { + DEBUG(control,out).print("entering preserve_rooms_cycle\n"); + do_cycle(out); +} + +static bool preserve_rooms_setFeature(color_ostream &out, bool enabled, string feature) { + DEBUG(control,out).print("entering preserve_rooms_setFeature (enabled=%d, feature=%s)\n", + enabled, feature.c_str()); + if (feature == "track-raids") { + config.set_bool(CONFIG_TRACK_RAIDS, enabled); + if (is_enabled && enabled) + do_cycle(out); + } else if (feature == "noble-roles") { + config.set_bool(CONFIG_NOBLE_ROLES, enabled); + } else { + return false; + } + + return true; +} + +static bool preserve_rooms_resetFeatureState(color_ostream &out, string feature) { + DEBUG(control,out).print("entering preserve_rooms_resetFeatureState (feature=%s)\n", feature.c_str()); + if (feature == "track-raids") { + // TODO: unpause reserved rooms + last_known_assignments.clear(); + pending_reassignment.clear(); + } else if (feature == "noble-roles") { + // TODO: reset state + } else { + return false; + } + + 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("entering preserve_rooms_getState\n"); + + unordered_map features; + features.emplace("track-raids", config.get_bool(CONFIG_TRACK_RAIDS)); + features.emplace("noble-roles", config.get_bool(CONFIG_NOBLE_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_resetFeatureState), + DFHACK_LUA_END}; + +DFHACK_PLUGIN_LUA_COMMANDS{ + DFHACK_LUA_COMMAND(preserve_rooms_getState), + DFHACK_LUA_END}; From d1a7d9c35841e119e22abe4383077c800d5465a0 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Aug 2024 02:43:11 -0700 Subject: [PATCH 11/18] minor cleanup --- plugins/logistics.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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) { From 734821c7c3ee1ffaa8b6421493c42e736e363d1c Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Aug 2024 13:26:25 -0700 Subject: [PATCH 12/18] basic functionality for role assignment --- docs/plugins/preserve-rooms.rst | 53 ++++---- plugins/lua/preserve-rooms.lua | 195 +++++++++++++++++++++++----- plugins/preserve-rooms.cpp | 220 ++++++++++++++++++++++++++++---- 3 files changed, 384 insertions(+), 84 deletions(-) diff --git a/docs/plugins/preserve-rooms.rst b/docs/plugins/preserve-rooms.rst index 5ef4e48e60..af03a8d796 100644 --- a/docs/plugins/preserve-rooms.rst +++ b/docs/plugins/preserve-rooms.rst @@ -6,27 +6,27 @@ preserve-rooms :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, and +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 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. +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 solves 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, 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. +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 role. The room will be automatically reassigned -to whoever currently holds that noble position. If multiple rooms of the same -type are assigned to a position that can be filled by multiple citizens (e.g. -you can have many barons), then only one room of that type will be assigned to -each holder of that position. +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 ----- @@ -48,26 +48,27 @@ Examples ``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-raids`` - Disable the ``track-raids`` feature for this fort. -``preserve-rooms reset noble-roles`` - Clear all configuration related to the ``noble-roles`` feature. +``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. Features -------- -``track-raids`` +``track-missions`` Reserve the rooms assigned to units that leave the map and reassign them upon their return. This feature is automatically enabled for new forts - unless disabled in `gui/control-panel` ("Bugfixes" tab). -``noble-roles`` - Allow rooms to be associated with noble roles. Associated rooms will be - automatically assigned to the current holder of the specified role. This - feature is enabled by default for all forts. + unless disabled in `gui/control-panel` ("Bugfixes" / "Autostart" tab). +``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 for all forts. 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. It also provides widgets to mark the zone as associated with a role. +return. For unreserved rooms, it provides widgets to mark the zone as +associated with a noble or administrative role. diff --git a/plugins/lua/preserve-rooms.lua b/plugins/lua/preserve-rooms.lua index 910bb88d8f..16efb079b5 100644 --- a/plugins/lua/preserve-rooms.lua +++ b/plugins/lua/preserve-rooms.lua @@ -1,9 +1,12 @@ 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 -- @@ -62,63 +65,189 @@ 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=30, y=10}, + 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=40, h=3}, + frame={w=44, h=15}, } +local new_world_loaded = true + function ReservedWidget:init() + self.code_to_idx = {} + self:addviews{ - widgets.Label{ - frame={l=0, t=0}, - text={ - 'Autoassigned to role:', - {gap=1, text=get_current_role_assignment, pen=COLOR_MAGENTA}, + 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() 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={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, + {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', + }, }, }, } 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 not has_current_role_assignment() then - return + if new_world_loaded then + self:refresh_role_list() + new_world_loaded = false end - BadgetWidget.super.render(self, dc) + + 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 ----------------------- --- RoleAssignWidget --- +local function to_title_case(str) + return dfhack.capitalizeStringWords(dfhack.lowerCp437(str:gsub('_', ' '))) +end -RoleAssignWidget = defclass(RoleAssignWidget, overlay.OverlayWidget) -RoleAssignWidget.ATTRS{ - desc='Adds a configuration dropdown for autoassigning rooms to noble roles.', - default_enabled=true, - default_pos={x=40, y=8}, - viewscreens={ - 'dwarfmode/UnitSelector/ZONE_OFFICE_ASSIGNMENT', - }, - frame={w=20, h=1}, -} +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 -function RoleAssignWidget:init() - self:addviews{ - widgets.TextLabel{ - frame={l=0, t=0, w=10}, - label='Assign to role', - key='CUSTOM_SHIFT_S', - }, - } +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 = { - badge=ReservedWidget, - roleassign=RoleAssignWidget, + 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 index ef1d6ec5b8..db579fa998 100644 --- a/plugins/preserve-rooms.cpp +++ b/plugins/preserve-rooms.cpp @@ -3,14 +3,22 @@ #include "LuaTools.h" #include "PluginManager.h" +#include "modules/Buildings.h" +#include "modules/Gui.h" +#include "modules/Translation.h" +#include "modules/Units.h" #include "modules/World.h" +#include "df/building_civzonest.h" +#include "df/historical_figure.h" +#include "df/unit.h" #include "df/world.h" #include #include #include +using std::pair; using std::string; using std::unordered_map; using std::vector; @@ -30,15 +38,23 @@ namespace DFHack { static const string CONFIG_KEY = string(plugin_name) + "/config"; static PersistentDataItem config; -// zone id -> unit ids (includes spouses) -static std::map> last_known_assignments; -// unit id -> zone ids -static std::map> pending_reassignment; +// zone id -> hfids (includes spouses) +static vector>> last_known_assignments_bedroom; +static vector>> last_known_assignments_office; +static vector>> last_known_assignments_dining; +static vector>> last_known_assignments_tomb; +// hfid -> zone ids +static unordered_map> pending_reassignment; +// zone id -> hfids +static unordered_map> reserved_zones; -// as a "system" plugin, we do not persist plugin enabled state +// 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_RAIDS = 0, - CONFIG_NOBLE_ROLES = 1, + CONFIG_TRACK_MISSIONS = 0, + CONFIG_TRACK_ROLES = 1, }; static const int32_t CYCLE_TICKS = 109; @@ -62,24 +78,45 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable) { return CR_OK; } -DFhackCExport command_result plugin_shutdown (color_ostream &out) { +DFhackCExport command_result plugin_shutdown(color_ostream &out) { DEBUG(control,out).print("shutting down %s\n", plugin_name); return CR_OK; } -DFhackCExport command_result plugin_load_site_data (color_ostream &out) { +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_RAIDS, false); - config.set_bool(CONFIG_NOBLE_ROLES, true); + config.set_bool(CONFIG_TRACK_MISSIONS, false); + config.set_bool(CONFIG_TRACK_ROLES, true); } - last_known_assignments.clear(); - pending_reassignment.clear(); + 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; } @@ -126,10 +163,89 @@ static command_result do_command(color_ostream &out, vector ¶meters) // 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) + 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", + Translation::TranslateName(&unit->name, false).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 void scan_assignments(color_ostream &out, + vector>> & last_known, + const vector & vec, + bool exclude_spouse = false) +{ + // auto it = last_known.begin(); + + vector>> assignments; + // for (auto zone : vec) { + + // } + + last_known = assignments; +} + static void do_cycle(color_ostream &out) { cycle_timestamp = world->frame_counter; DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + + assign_nobles(out); + + scan_assignments(out, last_known_assignments_bedroom, world->buildings.other.ZONE_BEDROOM); + scan_assignments(out, last_known_assignments_office, world->buildings.other.ZONE_OFFICE); + scan_assignments(out, last_known_assignments_dining, world->buildings.other.ZONE_DINING_HALL); + scan_assignments(out, last_known_assignments_tomb, world->buildings.other.ZONE_TOMB, true); } ///////////////////////////////////////////////////// @@ -144,12 +260,12 @@ static void preserve_rooms_cycle(color_ostream &out) { static bool preserve_rooms_setFeature(color_ostream &out, bool enabled, string feature) { DEBUG(control,out).print("entering preserve_rooms_setFeature (enabled=%d, feature=%s)\n", enabled, feature.c_str()); - if (feature == "track-raids") { - config.set_bool(CONFIG_TRACK_RAIDS, enabled); + if (feature == "track-missions") { + config.set_bool(CONFIG_TRACK_MISSIONS, enabled); if (is_enabled && enabled) do_cycle(out); - } else if (feature == "noble-roles") { - config.set_bool(CONFIG_NOBLE_ROLES, enabled); + } else if (feature == "track-roles") { + config.set_bool(CONFIG_TRACK_ROLES, enabled); } else { return false; } @@ -159,12 +275,14 @@ static bool preserve_rooms_setFeature(color_ostream &out, bool enabled, string f static bool preserve_rooms_resetFeatureState(color_ostream &out, string feature) { DEBUG(control,out).print("entering preserve_rooms_resetFeatureState (feature=%s)\n", feature.c_str()); - if (feature == "track-raids") { - // TODO: unpause reserved rooms - last_known_assignments.clear(); - pending_reassignment.clear(); - } else if (feature == "noble-roles") { - // TODO: reset state + 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; } @@ -172,6 +290,53 @@ static bool preserve_rooms_resetFeatureState(color_ostream &out, string feature) return true; } +static void preserve_rooms_assignToRole(color_ostream &out, string code) { + auto zone = Gui::getSelectedCivZone(out, true); + if (!zone) + return; + 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 ""; + 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; + 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 ""; + 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 Translation::TranslateName(&hf->name, false); + } + } + return ""; +} + +static bool preserve_rooms_clearReservation(color_ostream &out) { + auto zone = Gui::getSelectedCivZone(out, true); + if (!zone) + return false; + clear_reservation(out, zone->id, zone); + return true; +} + static int preserve_rooms_getState(lua_State *L) { color_ostream *out = Lua::GetOutput(L); if (!out) @@ -179,8 +344,8 @@ static int preserve_rooms_getState(lua_State *L) { DEBUG(control,*out).print("entering preserve_rooms_getState\n"); unordered_map features; - features.emplace("track-raids", config.get_bool(CONFIG_TRACK_RAIDS)); - features.emplace("noble-roles", config.get_bool(CONFIG_NOBLE_ROLES)); + 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; @@ -190,6 +355,11 @@ DFHACK_PLUGIN_LUA_FUNCTIONS{ DFHACK_LUA_FUNCTION(preserve_rooms_cycle), DFHACK_LUA_FUNCTION(preserve_rooms_setFeature), 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{ From 8ed34a59414532c5a79876615afb6c9bdcb01f40 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Aug 2024 18:37:34 -0700 Subject: [PATCH 13/18] first draft of track-missions --- docs/plugins/preserve-rooms.rst | 18 +++- plugins/lua/preserve-rooms.lua | 7 +- plugins/preserve-rooms.cpp | 167 ++++++++++++++++++++++++++++---- 3 files changed, 165 insertions(+), 27 deletions(-) diff --git a/docs/plugins/preserve-rooms.rst b/docs/plugins/preserve-rooms.rst index af03a8d796..831e6f6c29 100644 --- a/docs/plugins/preserve-rooms.rst +++ b/docs/plugins/preserve-rooms.rst @@ -51,19 +51,19 @@ Examples ``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. + 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 automatically enabled for new forts - unless disabled in `gui/control-panel` ("Bugfixes" / "Autostart" tab). + 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 for all forts. + role. This feature is enabled by default. Overlay ------- @@ -71,4 +71,12 @@ 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 noble or administrative role. +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/plugins/lua/preserve-rooms.lua b/plugins/lua/preserve-rooms.lua index 16efb079b5..c83e7f9a3c 100644 --- a/plugins/lua/preserve-rooms.lua +++ b/plugins/lua/preserve-rooms.lua @@ -98,7 +98,9 @@ function ReservedWidget:init() end, subviews={ widgets.Panel{ - visible=function() return not preserve_rooms_isReserved() end, + visible=function() + return not preserve_rooms_isReserved() and preserve_rooms_getFeature('track-roles') + end, subviews={ widgets.CycleHotkeyLabel{ view_id='role', @@ -164,6 +166,9 @@ function ReservedWidget:init() }, widgets.HelpButton{ command='preserve-rooms', + visible=function() + return preserve_rooms_isReserved() or preserve_rooms_getFeature('track-roles') + end, }, }, }, diff --git a/plugins/preserve-rooms.cpp b/plugins/preserve-rooms.cpp index db579fa998..d69b099f7c 100644 --- a/plugins/preserve-rooms.cpp +++ b/plugins/preserve-rooms.cpp @@ -4,6 +4,7 @@ #include "PluginManager.h" #include "modules/Buildings.h" +#include "modules/EventManager.h" #include "modules/Gui.h" #include "modules/Translation.h" #include "modules/Units.h" @@ -11,6 +12,7 @@ #include "df/building_civzonest.h" #include "df/historical_figure.h" +#include "df/histfig_hf_link.h" #include "df/unit.h" #include "df/world.h" @@ -33,16 +35,18 @@ REQUIRE_GLOBAL(world); namespace DFHack { DBG_DECLARE(persistent_per_save_example, control, DebugCategory::LINFO); DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO); + DBG_DECLARE(persistent_per_save_example, event, DebugCategory::LINFO); } static const string CONFIG_KEY = string(plugin_name) + "/config"; static PersistentDataItem config; -// zone id -> hfids (includes spouses) -static vector>> last_known_assignments_bedroom; -static vector>> last_known_assignments_office; -static vector>> last_known_assignments_dining; -static vector>> last_known_assignments_tomb; +// 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 @@ -61,6 +65,7 @@ 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) { @@ -73,7 +78,16 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector (df::building::find(zone_id)); - if (!zone) + if (!zone || !zone->spec_sub_flag.bits.active) continue; vector units; Units::getUnitsByNobleRole(units, code); @@ -220,17 +234,76 @@ static void clear_reservation(color_ostream &out, int32_t zone_id, df::building_ zone->spec_sub_flag.bits.active = true; } -static void scan_assignments(color_ostream &out, - vector>> & last_known, - const vector & vec, - bool exclude_spouse = false) -{ - // auto it = last_known.begin(); +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; +} - vector>> assignments; - // for (auto zone : vec) { +// handles when units disappear from their assignments compared to the last scan +static void handle_missing_assignments(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)) { + // unit is still alive on the map; assume the unassigment was intentional/expected + continue; + } + auto zone = virtual_cast(df::building::find(zone_id)); + if (!zone) + continue; + // unit is off-screen 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)) + { + Buildings::setOwner(zone, spouse); + continue; + } + } + // register the hf ids for reassignment and reserve the room + // when the unit with the registered hfid returns, they will be assigned back to the zone + pending_reassignment[hfid].push_back(zone_id); + if (share_with_spouse) + pending_reassignment[spouse_hfid].push_back(zone_id); + reserved_zones[zone_id].push_back(hfid); + zone->spec_sub_flag.bits.active = false; + } +} - // } +static void process_rooms(color_ostream &out, + 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(&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(&it, it_end, share_with_spouse, -1); last_known = assignments; } @@ -242,10 +315,52 @@ static void do_cycle(color_ostream &out) { assign_nobles(out); - scan_assignments(out, last_known_assignments_bedroom, world->buildings.other.ZONE_BEDROOM); - scan_assignments(out, last_known_assignments_office, world->buildings.other.ZONE_OFFICE); - scan_assignments(out, last_known_assignments_dining, world->buildings.other.ZONE_DINING_HALL); - scan_assignments(out, last_known_assignments_tomb, world->buildings.other.ZONE_TOMB, true); + process_rooms(out, last_known_assignments_bedroom, world->buildings.other.ZONE_BEDROOM); + process_rooms(out, last_known_assignments_office, world->buildings.other.ZONE_OFFICE); + process_rooms(out, last_known_assignments_dining, world->buildings.other.ZONE_DINING_HALL); + process_rooms(out, last_known_assignments_tomb, world->buildings.other.ZONE_TOMB, false); +} + +///////////////////////////////////////////////////// +// 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) + 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) + return; + auto hfid = unit->hist_figure_id; + auto it = pending_reassignment.find(hfid); + if (it == pending_reassignment.end()) + return; + for (auto zone_id : it->second) { + auto zone = virtual_cast(df::building::find(zone_id)); + if (!zone || zone->assigned_unit || spouse_has_sharable_room(out, hfid, zone->type)) + continue; + Buildings::setOwner(zone, unit); + zone->spec_sub_flag.bits.active = true; + } + pending_reassignment.erase(it); } ///////////////////////////////////////////////////// @@ -273,6 +388,15 @@ static bool preserve_rooms_setFeature(color_ostream &out, bool enabled, string f return true; } +static bool preserve_rooms_getFeature(color_ostream &out, string feature) { + DEBUG(control,out).print("entering 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("entering preserve_rooms_resetFeatureState (feature=%s)\n", feature.c_str()); if (feature == "track-missions") { @@ -354,6 +478,7 @@ static int preserve_rooms_getState(lua_State *L) { 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), From 1a740f346b821b6786790ef1d696c36cdfed10cb Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 27 Aug 2024 20:00:27 -0600 Subject: [PATCH 14/18] Items::createItem - auto-select growth prints instead of requiring the caller to figure it out on their own --- docs/changelog.txt | 2 ++ library/LuaApi.cpp | 5 ++--- library/include/modules/Items.h | 2 +- library/modules/Items.cpp | 32 +++++++++++++++++++++++++++++++- plugins/createitem.cpp | 29 ++++++----------------------- 5 files changed, 42 insertions(+), 28 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 16278a9a93..6b6bbe61d7 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -74,12 +74,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 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/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/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; From 270880ccaf9ff54c03d99628372dcd09eac99515 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Aug 2024 19:28:23 -0700 Subject: [PATCH 15/18] add instrumentation --- plugins/preserve-rooms.cpp | 63 +++++++++++++++++++++++++++----------- 1 file changed, 45 insertions(+), 18 deletions(-) diff --git a/plugins/preserve-rooms.cpp b/plugins/preserve-rooms.cpp index d69b099f7c..65eb4a73a4 100644 --- a/plugins/preserve-rooms.cpp +++ b/plugins/preserve-rooms.cpp @@ -6,7 +6,6 @@ #include "modules/Buildings.h" #include "modules/EventManager.h" #include "modules/Gui.h" -#include "modules/Translation.h" #include "modules/Units.h" #include "modules/World.h" @@ -33,9 +32,9 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled); REQUIRE_GLOBAL(world); namespace DFHack { - DBG_DECLARE(persistent_per_save_example, control, DebugCategory::LINFO); - DBG_DECLARE(persistent_per_save_example, cycle, DebugCategory::LINFO); - DBG_DECLARE(persistent_per_save_example, event, DebugCategory::LINFO); + 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"; @@ -207,7 +206,7 @@ static void assign_nobles(color_ostream &out) { continue; Buildings::setOwner(zone, unit); INFO(cycle,out).print("assigning %s to a %s-associated %s\n", - Translation::TranslateName(&unit->name, false).c_str(), code.c_str(), + Units::getReadableName(unit).c_str(), code.c_str(), ENUM_KEY_STR(civzone_type, zone->type).c_str()); break; } @@ -243,7 +242,8 @@ static int32_t get_spouse_hfid(color_ostream &out, df::historical_figure *hf) { } // handles when units disappear from their assignments compared to the last scan -static void handle_missing_assignments(ZoneAssignments::iterator *pit, +static void handle_missing_assignments(color_ostream &out, + ZoneAssignments::iterator *pit, const ZoneAssignments::iterator & it_end, bool share_with_spouse, int32_t next_zone_id) @@ -262,23 +262,35 @@ static void handle_missing_assignments(ZoneAssignments::iterator *pit, // 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-screen or is dead; if we can assign room to spouse then we don't need to reserve the room + // 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)) { + 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 - // when the unit with the registered hfid returns, they will be assigned back to the zone + 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); - if (share_with_spouse) + if (share_with_spouse) { + 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(hfid); zone->spec_sub_flag.bits.active = false; } @@ -294,7 +306,7 @@ static void process_rooms(color_ostream &out, auto it_end = last_known.end(); for (auto zone : vec) { if (!zone->assigned_unit) { - handle_missing_assignments(&it, it_end, share_with_spouse, zone->id); + handle_missing_assignments(out, &it, it_end, share_with_spouse, zone->id); continue; } auto hf = df::historical_figure::find(zone->assigned_unit->hist_figure_id); @@ -303,7 +315,7 @@ static void process_rooms(color_ostream &out, 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(&it, it_end, share_with_spouse, -1); + handle_missing_assignments(out, &it, it_end, share_with_spouse, -1); last_known = assignments; } @@ -319,6 +331,10 @@ static void do_cycle(color_ostream &out) { process_rooms(out, last_known_assignments_office, world->buildings.other.ZONE_OFFICE); process_rooms(out, last_known_assignments_dining, world->buildings.other.ZONE_DINING_HALL); process_rooms(out, 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()); } ///////////////////////////////////////////////////// @@ -338,8 +354,10 @@ static bool spouse_has_sharable_room(color_ostream& out, int32_t hfid, df::civzo if (!spouse) return false; for (auto owned_zone : spouse->owned_buildings) { - if (owned_zone->type == ztype) + if (owned_zone->type == ztype) { + DEBUG(event,out).print("spouse had sharable room; no need to set ownership\n"); return true; + } } return false; } @@ -353,10 +371,14 @@ static void on_new_active_unit(color_ostream& out, void* data) { 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; } @@ -368,12 +390,12 @@ static void on_new_active_unit(color_ostream& out, void* data) { // static void preserve_rooms_cycle(color_ostream &out) { - DEBUG(control,out).print("entering preserve_rooms_cycle\n"); + 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("entering preserve_rooms_setFeature (enabled=%d, feature=%s)\n", + 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); @@ -389,7 +411,7 @@ static bool preserve_rooms_setFeature(color_ostream &out, bool enabled, string f } static bool preserve_rooms_getFeature(color_ostream &out, string feature) { - DEBUG(control,out).print("entering preserve_rooms_getFeature (feature=%s)\n", feature.c_str()); + 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") @@ -398,7 +420,7 @@ static bool preserve_rooms_getFeature(color_ostream &out, string feature) { } static bool preserve_rooms_resetFeatureState(color_ostream &out, string feature) { - DEBUG(control,out).print("entering preserve_rooms_resetFeatureState (feature=%s)\n", feature.c_str()); + 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; }); @@ -418,6 +440,7 @@ 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); } @@ -426,6 +449,7 @@ 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 ""; @@ -436,6 +460,7 @@ 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; } @@ -444,10 +469,11 @@ 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 Translation::TranslateName(&hf->name, false); + return Units::getReadableName(hf); } } return ""; @@ -457,6 +483,7 @@ 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; } @@ -465,7 +492,7 @@ static int preserve_rooms_getState(lua_State *L) { color_ostream *out = Lua::GetOutput(L); if (!out) out = &Core::getInstance().getConsole(); - DEBUG(control,*out).print("entering preserve_rooms_getState\n"); + DEBUG(control,*out).print("preserve_rooms_getState\n"); unordered_map features; features.emplace("track-missions", config.get_bool(CONFIG_TRACK_MISSIONS)); From b3660352da0f1e3cbbba851b51f3ee639dca2371 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Aug 2024 20:22:42 -0700 Subject: [PATCH 16/18] new unit event should trigger whenever units enter the active vector --- library/modules/EventManager.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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; }); } From 1a058000f1f9dcfab18cd87de325a3ab7c3c8dd7 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Aug 2024 20:23:38 -0700 Subject: [PATCH 17/18] we need to track membership in the active vector --- plugins/lua/preserve-rooms.lua | 4 ++-- plugins/preserve-rooms.cpp | 35 ++++++++++++++++++++++------------ 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/plugins/lua/preserve-rooms.lua b/plugins/lua/preserve-rooms.lua index c83e7f9a3c..59415499f8 100644 --- a/plugins/lua/preserve-rooms.lua +++ b/plugins/lua/preserve-rooms.lua @@ -144,7 +144,7 @@ function ReservedWidget:init() }, }, widgets.Panel{ - frame={h=5}, + frame={t=0, h=5}, frame_style=gui.FRAME_MEDIUM, frame_background=gui.CLEAR_PEN, visible=preserve_rooms_isReserved, @@ -153,7 +153,7 @@ function ReservedWidget:init() frame={t=0, l=0}, text={ 'Reserved for traveling unit:', NEWLINE, - {text=preserve_rooms_getReservationName, pen=COLOR_YELLOW}, + {gap=1, text=preserve_rooms_getReservationName, pen=COLOR_YELLOW}, }, }, widgets.HotkeyLabel{ diff --git a/plugins/preserve-rooms.cpp b/plugins/preserve-rooms.cpp index 65eb4a73a4..e8a1383261 100644 --- a/plugins/preserve-rooms.cpp +++ b/plugins/preserve-rooms.cpp @@ -22,6 +22,7 @@ using std::pair; using std::string; using std::unordered_map; +using std::unordered_set; using std::vector; using namespace DFHack; @@ -243,6 +244,7 @@ static int32_t get_spouse_hfid(color_ostream &out, df::historical_figure *hf) { // 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, @@ -258,7 +260,7 @@ static void handle_missing_assignments(color_ostream &out, // if unit data is completely gone, then they're not likely to come back continue; } - if (Units::isActive(unit) && !Units::isDead(unit)) { + 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; } @@ -270,7 +272,7 @@ static void handle_missing_assignments(color_ostream &out, // 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)) + 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(), @@ -286,17 +288,19 @@ static void handle_missing_assignments(color_ostream &out, 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); - if (share_with_spouse) { + 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); } - reserved_zones[zone_id].push_back(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) @@ -306,7 +310,7 @@ static void process_rooms(color_ostream &out, auto it_end = last_known.end(); for (auto zone : vec) { if (!zone->assigned_unit) { - handle_missing_assignments(out, &it, it_end, share_with_spouse, zone->id); + 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); @@ -315,7 +319,7 @@ static void process_rooms(color_ostream &out, 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, &it, it_end, share_with_spouse, -1); + handle_missing_assignments(out, active_unit_ids, &it, it_end, share_with_spouse, -1); last_known = assignments; } @@ -323,14 +327,18 @@ static void process_rooms(color_ostream &out, static void do_cycle(color_ostream &out) { cycle_timestamp = world->frame_counter; - DEBUG(cycle,out).print("running %s cycle\n", plugin_name); + TRACE(cycle,out).print("running %s cycle\n", plugin_name); assign_nobles(out); - process_rooms(out, last_known_assignments_bedroom, world->buildings.other.ZONE_BEDROOM); - process_rooms(out, last_known_assignments_office, world->buildings.other.ZONE_OFFICE); - process_rooms(out, last_known_assignments_dining, world->buildings.other.ZONE_DINING_HALL); - process_rooms(out, last_known_assignments_tomb, world->buildings.other.ZONE_TOMB, false); + 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(), @@ -365,8 +373,11 @@ static bool spouse_has_sharable_room(color_ostream& out, int32_t hfid, df::civzo 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) + 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()) From 847ed9689b8631660fd0160024101fdae84388a8 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 27 Aug 2024 20:26:17 -0700 Subject: [PATCH 18/18] update changelog --- docs/changelog.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index 16278a9a93..0d170006be 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