diff --git a/.gitignore b/.gitignore index 39900a3bb..5ce1240d3 100644 --- a/.gitignore +++ b/.gitignore @@ -64,3 +64,6 @@ build/CPack*Config.cmake # ctags file tags + +# Mac OS X .DS_Store files +.DS_Store diff --git a/Lua API.html b/Lua API.html index bd17cc644..1a2888eb0 100644 --- a/Lua API.html +++ b/Lua API.html @@ -344,92 +344,97 @@ ul.auto-toc {

Contents

-

Named types

+

Named types

Named types are exposed in the df tree with names identical to the C++ version, except for the :: vs . difference.

All types and the global object have the following features:

@@ -678,7 +684,7 @@ xml have a type.find(key) function that wraps method provided in C++.

-

Global functions

+

Global functions

The df table itself contains the following functions and values:

-

Recursive table assignment

+

Recursive table assignment

Recursive assignment is invoked when a lua table is assigned to a C++ object or field, i.e. one of:

-

DFHack API

+

DFHack API

DFHack utility functions are placed in the dfhack global tree.

-

Native utilities

+

Native utilities

-

Input & Output

+

Input & Output

  • dfhack.print(args...)

    Output tab-separated args as standard lua print would do, @@ -846,7 +852,7 @@ string, global environment and command-line history file.

-

Exception handling

+

Exception handling

  • dfhack.error(msg[,level[,verbose]])

    Throws a dfhack exception object with location and stack trace. @@ -902,7 +908,7 @@ following properties:

-

Miscellaneous

+

Miscellaneous

  • dfhack.VERSION

    DFHack version string constant.

    @@ -915,7 +921,7 @@ both from the curry call and the closure call itself. I.e.
-

Locking and finalization

+

Locking and finalization

  • dfhack.with_suspend(f[,args...])

    Calls f with arguments after grabbing the DF core suspend lock. @@ -948,7 +954,7 @@ Implemented using call_with_final

-

Persistent configuration storage

+

Persistent configuration storage

This api is intended for storing configuration options in the world itself. It probably should be restricted to data that is world-dependent.

Entries are identified by a string key, but it is also possible to manage @@ -998,7 +1004,7 @@ as an all-zero mask.

the persistent entry will NOT delete the associated masks.

-

Material info lookup

+

Material info lookup

A material info record has fields:

  • type, index, material

    @@ -1042,7 +1048,7 @@ Accept dfhack_material_category auto-assign table.

-

Random number generation

+

Random number generation

  • dfhack.random.new([seed[,perturb_count]])

    Creates a new random number generator object. Without any @@ -1086,7 +1092,7 @@ Dimension may be 1, 2 or 3 (default).

-

C++ function wrappers

+

C++ function wrappers

Thin wrappers around C++ functions, similar to the ones for virtual methods. One notable difference is that these explicit wrappers allow argument count adjustment according to the usual lua rules, so trailing false/nil arguments @@ -1127,7 +1133,7 @@ can be omitted.

-

Gui module

+

Gui module

  • dfhack.gui.getCurViewscreen([skip_dismissed])

    Returns the topmost viewscreen. If skip_dismissed is true, @@ -1197,7 +1203,7 @@ operations accordingly. The units are used to call

-

Job module

+

Job module

  • dfhack.job.cloneJobStruct(job)

    Creates a deep copy of the given job.

    @@ -1252,10 +1258,13 @@ the flags in the job item.

  • dfhack.job.isSuitableMaterial(job_item, mat_type, mat_index)

    Likewise, if replacing material.

  • +
  • dfhack.job.getName(job)

    +

    Returns the job's description, as seen in the Units and Jobs screens.

    +
-

Units module

+

Units module

  • dfhack.units.getPosition(unit)

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

    @@ -1359,7 +1368,7 @@ or raws. The ignore_noble boolean disables the
-

Items module

+

Items module

  • dfhack.items.getPosition(item)

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

    @@ -1420,10 +1429,16 @@ Returns false in case of error.

  • dfhack.items.getSubtypeDef(item_type, subtype)

    Returns the raw definition for the given item type and subtype, or nil if invalid.

  • +
  • dfhack.items.getItemBaseValue(item_type, subtype, material, mat_index)

    +

    Calculates the base value for an item of the specified type and material.

    +
  • +
  • dfhack.items.getValue(item)

    +

    Calculates the Basic Value of an item, as seen in the View Item screen.

    +
-

Maps module

+

Maps module

  • dfhack.maps.getSize()

    Returns map size in blocks: x, y, z

    @@ -1492,7 +1507,7 @@ burrows, or the presence of invaders.

-

Burrows module

+

Burrows module

  • dfhack.burrows.findByName(name)

    Returns the burrow pointer or nil.

    @@ -1527,7 +1542,7 @@ burrows, or the presence of invaders.

-

Buildings module

+

Buildings module

  • dfhack.buildings.getGeneralRef(building, type)

    Searches for a general_ref with the given type.

    @@ -1677,7 +1692,7 @@ can be determined this way, constructBuilding
-

Constructions module

+

Constructions module

  • dfhack.constructions.designateNew(pos,type,item_type,mat_index)

    Designates a new construction at given position. If there already is @@ -1693,7 +1708,7 @@ Returns true, was_only_planned if removed; or false if none fo

-

Screen API

+

Screen API

The screen module implements support for drawing to the tiled screen of the game. Note that drawing only has any effect when done from callbacks, so it can only be feasibly used in the core context.

@@ -1881,7 +1896,7 @@ options; if multiple interpretations exist, the table will contain multiple keys
-

Internal API

+

Internal API

These functions are intended for the use by dfhack developers, and are only documented here for completeness:

    @@ -1950,7 +1965,7 @@ Returns: file_names or empty table if not found.

-

Core interpreter context

+

Core interpreter context

While plugins can create any number of interpreter instances, there is one special context managed by dfhack core. It is the only context that can receive events from DF and plugins.

@@ -1981,7 +1996,7 @@ Using timeout_active(id,nil) cancels the timer
-

Event type

+

Event type

An event is a native object transparently wrapping a lua table, and implementing a __call metamethod. When it is invoked, it loops through the table with next and calls all contained values. @@ -2013,7 +2028,7 @@ order using dfhack.safecall.

-

Lua Modules

+

Lua Modules

DFHack sets up the lua interpreter so that the built-in require function can be used to load shared lua code from hack/lua/. The dfhack namespace reference itself may be obtained via @@ -2042,7 +2057,7 @@ in this document.

-

Global environment

+

Global environment

A number of variables and functions are provided in the base global environment by the mandatory init file dfhack.lua:

    @@ -2105,7 +2120,7 @@ Returns nil if any of obj or indices is nil, or a numeric inde
-

utils

+

utils

  • utils.compare(a,b)

    Comparator function; returns -1 if a<b, 1 if a>b, 0 otherwise.

    @@ -2254,7 +2269,7 @@ throws an error.

-

dumper

+

dumper

A third-party lua table dumper module from http://lua-users.org/wiki/DataDumper. Defines one function:

@@ -2267,7 +2282,7 @@ the other arguments see the original documentation link above.

-

class

+

class

Implements a trivial single-inheritance class system.

  • Foo = defclass(Foo[, ParentClass])

    @@ -2358,7 +2373,7 @@ library itself uses them for constructors.

-

In-game UI Library

+

In-game UI Library

A number of lua modules with names starting with gui are dedicated to wrapping the natives of the dfhack.screen module in a way that is easy to use. This allows relatively easily and naturally creating @@ -2367,12 +2382,12 @@ dialogs that integrate in the main game UI window.

things ranging from the basic Painter, View and Screen classes, to fully functional predefined dialogs.

-

gui

+

gui

This module defines the most important classes and functions for implementing interfaces. This documents those of them that are considered stable.

-

Misc

+

Misc

  • USE_GRAPHICS

    Contains the value of dfhack.screen.inGraphicsMode(), which cannot be @@ -2411,7 +2426,7 @@ msec. This is intended for rendering blinking interface objects.

-

ViewRect class

+

ViewRect class

This class represents an on-screen rectangle with an associated independent clip area rectangle. It is the base of the Painter class, and is used by Views to track their client area.

@@ -2459,7 +2474,7 @@ it with the clip area of the original object.

-

Painter class

+

Painter class

The painting natives in dfhack.screen apply to the whole screen, are completely stateless and don't implement clipping.

The Painter class inherits from ViewRect to provide clipping and local @@ -2530,7 +2545,7 @@ painter:pen(foo):seek(x,y):char(1):advance(1):string('bar')...

-

View class

+

View class

This class is the common abstract base of both the stand-alone screens and common widgets to be used inside them. It defines the basic layout, rendering and event handling framework.

@@ -2654,7 +2669,7 @@ Returns true if any of the subviews handled the event.

-

Screen class

+

Screen class

This is a View subclass intended for use as a stand-alone dialog or screen. It adds the following methods:

    @@ -2708,7 +2723,7 @@ the screen is removed by any means here.

-

FramedScreen class

+

FramedScreen class

A Screen subclass that paints a visible frame around its body. Most dialogs should inherit from this class.

A framed screen has the following attributes:

@@ -2746,10 +2761,10 @@ Most dialogs should inherit from this class.

-

gui.widgets

+

gui.widgets

This module implements some basic widgets based on the View infrastructure.

-

Widget class

+

Widget class

Base of all the widgets. Inherits from View and has the following attributes:

  • frame = {...}

    @@ -2815,7 +2830,7 @@ inset, or a table with the following fields:

-

Panel class

+

Panel class

Inherits from Widget, and intended for grouping a number of subviews.

Has attributes:

    @@ -2828,7 +2843,7 @@ inset, or a table with the following fields:

-

Pages class

+

Pages class

Subclass of Panel; keeps exactly one child visible.

  • Pages{ ..., selected = ... }

    @@ -2844,7 +2859,7 @@ It is permitted to use the subview object, or its v
-

EditField class

+

EditField class

Subclass of Widget; implements a simple edit field.

Attributes:

@@ -2866,7 +2881,7 @@ If it returns false, the character is ignored.
-

Label class

+

Label class

This Widget subclass implements flowing semi-static text.

It has the following attributes:

@@ -2962,7 +2977,7 @@ this may be extended with mouse click support.

-

List class

+

List class

The List widget implements a simple list with paging.

It has the following attributes:

@@ -3048,7 +3063,7 @@ with the following fields:

-

FilteredList class

+

FilteredList class

This widget combines List, EditField and Label into a combo-box like construction that allows filtering the list by subwords of its items.

In addition to passing through all attributes supported by List, it @@ -3101,14 +3116,14 @@ index pos in the unfiltered list if p

-

Plugins

+

Plugins

DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.

The following plugins have lua support.

-

burrows

+

burrows

Implements extended burrow manipulations.

Events:

    @@ -3146,16 +3161,16 @@ set is the same as used by the command line.

    The lua module file also re-exports functions from dfhack.burrows.

-

sort

+

sort

Does not export any native functions as of now. Instead, it calls lua code to perform the actual ordering of list items.

-

Eventful

+

Eventful

This plugin exports some events to lua thus allowing to run lua functions on DF world events.

-

List of events

+

List of events

  1. onReactionComplete(reaction,unit,input_items,input_reagents,output_items,call_native)

    Auto activates if detects reactions starting with LUA_HOOK_. Is called when reaction finishes.

    @@ -3185,7 +3200,7 @@ tweaking (e.g. adding custom reactions)

-

Events from EventManager

+

Events from EventManager

These events are straight from EventManager module. Each of them first needs to be enabled. See functions for more info. If you register a listener before the game is loaded, be aware that no events will be triggered immediately after loading, so you might need to add another event listener for when the game first loads in some cases.

  1. onBuildingCreatedDestroyed(building_id)

    @@ -3218,7 +3233,7 @@ tweaking (e.g. adding custom reactions)

-

Functions

+

Functions

  1. registerReaction(reaction_name,callback)

    Simplified way of using onReactionComplete; the callback is function (same params as event).

    @@ -3232,10 +3247,13 @@ tweaking (e.g. adding custom reactions)

  2. enableEvent(evType,frequency)

    Enable event checking for EventManager events. For event types use eventType table. Note that different types of events require different frequencies to be effective. The frequency is how many ticks EventManager will wait before checking if that type of event has happened. If multiple scripts or plugins use the same event type, the smallest frequency is the one that is used, so you might get events triggered more often than the frequency you use here.

  3. +
  4. registerSidebar(shop_name,callback)

    +

    Enable callback when sidebar for shop_name is drawn. Usefull for custom workshop views e.g. using gui.dwarfmode lib.

    +
-

Examples

+

Examples

Spawn dragon breath on each item attempt to contaminate wound:

 b=require "plugins.eventful"
@@ -3247,13 +3265,13 @@ end
 
 b=require "plugins.eventful"
 
-b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native)
+b.registerReaction("LUA_HOOK_LAY_BOMB",function(reaction,unit,in_items,in_reag,out_items,call_native)
   local pos=copyall(unit.pos)
   -- spawn dragonbreath after 100 ticks
   dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end)
   --do not call real item creation code
   call_native.value=false
-end
+end)
 

Grenade example:

@@ -3270,9 +3288,57 @@ b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS")
 
+
+

Building-hacks

+

This plugin overwrites some methods in workshop df class so that mechanical workshops are possible. Although +plugin export a function it's recommended to use lua decorated function.

+
+

Functions

+
+
registerBuilding(table) where table must contain name, as a workshop raw name, the rest are optional:
+
    +
  1. name -- custom workshop id e.g. SOAPMAKER
  2. +
  3. fix_impassible -- if true make impassible tiles impassible to liquids too
  4. +
  5. consume -- how much machine power is needed to work. Disables reactions if not supplied enough
  6. +
  7. produce -- how much machine power is produced. Use discouraged as there is no way to change this at runtime
  8. +
  9. gears -- a table or {x=?,y=?} of connection points for machines
  10. +
  11. action -- a table of number (how much ticks to skip) and a function which gets called on shop update
  12. +
  13. animate -- a table of frames which can be a table of:
      +
    1. tables of 4 numbers {tile,fore,back,bright} OR
    2. +
    3. empty table (tile not modified) OR
    4. +
    5. {x=<number> y=<number> + 4 numbers like in first case}, this generates full frame useful for animations that change little (1-2 tiles)
    6. +
    +
  14. +
+
+
Animate table also might contain:
+
    +
  1. frameLenght -- how many ticks does one frame take OR
  2. +
  3. isMechanical -- a bool that says to try to match to mechanical system (i.e. how gears are turning)
  4. +
+
+
+
+
+

Examples

+

Simple mechanical workshop:

+
+require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER",
+  consume=15,
+  gears={x=0,y=0}, --connection point
+  animate={
+    isMechanical=true, --animate the same connection point as vanilla gear
+    frames={
+    {{x=0,y=0,42,7,0,0}}, --first frame, 1 changed tile
+    {{x=0,y=0,15,7,0,0}} -- second frame, same
+    }
+  }
+
+
+
-

Scripts

+

Scripts

Any files with the .lua extension placed into hack/scripts/* are automatically used by the DFHack core as commands. The matching command name consists of the name of the file sans @@ -3303,7 +3369,7 @@ The name argument should be the name stem, as

Note that this function lets errors propagate to the caller.

-

Save init script

+

Save init script

If a save directory contains a file called raw/init.lua, it is automatically loaded and executed every time the save is loaded. The same applies to any files called raw/init.d/*.lua. Every diff --git a/Lua API.rst b/Lua API.rst index 072d9265f..3f2cf64ad 100644 --- a/Lua API.rst +++ b/Lua API.rst @@ -25,9 +25,10 @@ implemented by Lua files located in hack/lua/... DF data structure wrapper ========================= -DF structures described by the xml files in library/xml are exported -to lua code as a tree of objects and functions under the ``df`` global, -which broadly maps to the ``df`` namespace in C++. +Data structures of the game are defined in XML files located in library/xml +(and online at http://github.com/DFHack/df-structures), and automatically exported +to lua code as a tree of objects and functions under the ``df`` global, which +also broadly maps to the ``df`` namespace in the headers generated for C++. **WARNING**: The wrapper provides almost raw access to the memory of the game, so mistakes in manipulating objects are as likely to @@ -979,6 +980,10 @@ Job module Likewise, if replacing material. +* ``dfhack.job.getName(job)`` + + Returns the job's description, as seen in the Units and Jobs screens. + Units module ------------ @@ -1187,6 +1192,14 @@ Items module Returns the raw definition for the given item type and subtype, or *nil* if invalid. +* ``dfhack.items.getItemBaseValue(item_type, subtype, material, mat_index)`` + + Calculates the base value for an item of the specified type and material. + +* ``dfhack.items.getValue(item)`` + + Calculates the Basic Value of an item, as seen in the View Item screen. + Maps module ----------- @@ -3167,6 +3180,10 @@ Functions Enable event checking for EventManager events. For event types use ``eventType`` table. Note that different types of events require different frequencies to be effective. The frequency is how many ticks EventManager will wait before checking if that type of event has happened. If multiple scripts or plugins use the same event type, the smallest frequency is the one that is used, so you might get events triggered more often than the frequency you use here. +5. ``registerSidebar(shop_name,callback)`` + + Enable callback when sidebar for ``shop_name`` is drawn. Usefull for custom workshop views e.g. using gui.dwarfmode lib. + Examples -------- Spawn dragon breath on each item attempt to contaminate wound:: @@ -3180,13 +3197,13 @@ Reaction complete example:: b=require "plugins.eventful" - b.onReactionComplete.one=function(reaction,unit,in_items,in_reag,out_items,call_native) + b.registerReaction("LUA_HOOK_LAY_BOMB",function(reaction,unit,in_items,in_reag,out_items,call_native) local pos=copyall(unit.pos) -- spawn dragonbreath after 100 ticks dfhack.timeout(100,"ticks",function() dfhack.maps.spawnFlow(pos,6,0,0,50000) end) --do not call real item creation code call_native.value=false - end + end) Grenade example:: @@ -3200,6 +3217,48 @@ Integrated tannery:: b=require "plugins.eventful" b.addReactionToShop("TAN_A_HIDE","LEATHERWORKS") + +Building-hacks +============== + +This plugin overwrites some methods in workshop df class so that mechanical workshops are possible. Although +plugin export a function it's recommended to use lua decorated function. + +Functions +--------- + +``registerBuilding(table)`` where table must contain name, as a workshop raw name, the rest are optional: + 1. name -- custom workshop id e.g. ``SOAPMAKER`` + 2. fix_impassible -- if true make impassible tiles impassible to liquids too + 3. consume -- how much machine power is needed to work. Disables reactions if not supplied enough + 4. produce -- how much machine power is produced. Use discouraged as there is no way to change this at runtime + 5. gears -- a table or ``{x=?,y=?}`` of connection points for machines + 6. action -- a table of number (how much ticks to skip) and a function which gets called on shop update + 7. animate -- a table of frames which can be a table of: + + a. tables of 4 numbers ``{tile,fore,back,bright}`` OR + b. empty table (tile not modified) OR + c. ``{x= y= + 4 numbers like in first case}``, this generates full frame useful for animations that change little (1-2 tiles) + +Animate table also might contain: + 1. frameLenght -- how many ticks does one frame take OR + 2. isMechanical -- a bool that says to try to match to mechanical system (i.e. how gears are turning) + +Examples +-------- + +Simple mechanical workshop:: + + require('plugins.building-hacks').registerBuilding{name="BONE_GRINDER", + consume=15, + gears={x=0,y=0}, --connection point + animate={ + isMechanical=true, --animate the same connection point as vanilla gear + frames={ + {{x=0,y=0,42,7,0,0}}, --first frame, 1 changed tile + {{x=0,y=0,15,7,0,0}} -- second frame, same + } + } ======= Scripts diff --git a/NEWS b/NEWS index 165938e2b..db889b4ae 100644 --- a/NEWS +++ b/NEWS @@ -5,11 +5,14 @@ DFHack future - Lua API for listing files in directory. Needed for mod-manager. - Lua API for creating unit combat reports and writing to gamelog. - support for multiple raw/init.d/*.lua init scripts in one save. + - eventful now has a more friendly way of making custom sidebars + - new plugin: building-hacks. Allows to add custom functionality and/or animations to buildings. New scripts: - gui/mod-manager: allows installing/uninstalling mods into df from df/mods directory. - gui/clone-uniform: duplicates the currently selected uniform in the military screen. - fix/build-location: partial work-around for bug 5991 (trying to build wall while standing on it) + - undump-buildings: removes dump designation from materials used in buildings. New commands: - move the 'grow', 'extirpate' and 'immolate' commands as 'plant' subcommands diff --git a/Readme.html b/Readme.html index b93dc3bfe..9ca67c5dd 100644 --- a/Readme.html +++ b/Readme.html @@ -543,48 +543,49 @@ access DF memory and allow for easier development of new tools.

  • embark
  • lever
  • stripcaged
  • -
  • create-items
  • -
  • locate-ore
  • -
  • soundsense-season
  • -
  • multicmd
  • +
  • undump-buildings
  • +
  • create-items
  • +
  • locate-ore
  • +
  • soundsense-season
  • +
  • multicmd
  • -
  • In-game interface tools
  • +
    +

    undump-buildings

    +

    Undesignates building base materials for dumping.

    +
    -

    create-items

    +

    create-items

    Spawn arbitrary items under the cursor.

    The first argument gives the item category, the second gives the material, and the optionnal third gives the number of items to create (defaults to 20).

    @@ -3194,7 +3199,7 @@ create-items bar adamantine
    -

    locate-ore

    +

    locate-ore

    Scan the map for metal ores.

    Finds and designate for digging one tile of a specific metal ore. Only works for native metal ores, does not handle reaction stuff (eg STEEL).

    @@ -3207,7 +3212,7 @@ locate-ore iron
    -

    soundsense-season

    +

    soundsense-season

    It is a well known issue that Soundsense cannot detect the correct current season when a savegame is loaded and has to play random season music until a season switch occurs.

    @@ -3216,7 +3221,7 @@ to gamelog.txt on every map load to fix this. For best results call the script from dfhack.init.

    -

    multicmd

    +

    multicmd

    Run multiple dfhack commands. The argument is split around the character ; and all parts are run sequencially as independent dfhack commands. Useful for hotkeys.

    @@ -3227,7 +3232,7 @@ dfhack commands. Useful for hotkeys.

    -

    In-game interface tools

    +

    In-game interface tools

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

    @@ -3242,7 +3247,7 @@ guideline because it arguably just fixes small usability bugs in the game UI.

    -

    Dwarf Manipulator

    +

    Dwarf Manipulator

    Implemented by the 'manipulator' plugin.

    To activate, open the unit screen and press 'l'.

    images/manipulator.png @@ -3281,7 +3286,7 @@ cursor onto that cell instead of toggling it. directly to the main dwarf mode screen.

    -

    AutoMaterial

    +

    AutoMaterial

    Implemented by the 'automaterial' plugin.

    This makes building constructions (walls, floors, fortifications, etc) a little bit easier by saving you from having to trawl through long lists of materials each time @@ -3340,7 +3345,7 @@ materials, it returns you back to this screen. If you use this along with severa enabled materials, you should be able to place complex constructions more conveniently.

    -

    gui/liquids

    +

    gui/liquids

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

    images/liquids.png

    This script is a gui front-end to the liquids plugin and works similar to it, @@ -3360,7 +3365,7 @@ rivers power water wheels even when full and technically not flowing.

    After setting up the desired operations using the described keys, use Enter to apply them.

    -

    gui/mechanisms

    +

    gui/mechanisms

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

    images/mechanisms.png

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

    -

    gui/rename

    +

    gui/rename

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

      @@ -3393,7 +3398,7 @@ their species string.

      unit profession change to Ctrl-Shift-T.

    -

    gui/room-list

    +

    gui/room-list

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

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

    list, and allows unassigning them.

    -

    gui/choose-weapons

    +

    gui/choose-weapons

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

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

    and may lead to inappropriate weapons being selected.

    -

    gui/clone-uniform

    +

    gui/clone-uniform

    Bind to a key (the example config uses Ctrl-C), and activate in the Uniforms page of the military screen with the cursor in the leftmost list.

    When invoked, the script duplicates the currently selected uniform template, and selects the newly created copy.

    -

    gui/guide-path

    +

    gui/guide-path

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

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

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

    -

    gui/workshop-job

    +

    gui/workshop-job

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

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

    -

    gui/workflow

    +

    gui/workflow

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

    images/workflow.png @@ -3510,7 +3515,7 @@ the current stock value. The bright green dashed line is the target limit (maximum) and the dark green line is that minus the gap (minimum).

    -

    gui/assign-rack

    +

    gui/assign-rack

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

    images/assign-rack.png @@ -3534,7 +3539,7 @@ the intended user. In order to aid in the choice, it shows the number of currently assigned racks for every valid squad.

    -

    gui/advfort

    +

    gui/advfort

    This script allows to perform jobs in adventure mode. For more complete help press '?' while script is running. It's most confortable to use this as a keybinding. (e.g. keybinding set Ctrl-T gui/advfort). Possible arguments:

    @@ -3553,7 +3558,7 @@ implies -a
    -

    gui/companion-order

    +

    gui/companion-order

    A script to issue orders for companions. Select companions with lower case chars, issue orders with upper case. Must be in look or talk mode to issue command on tile.

    images/companion-order.png @@ -3569,7 +3574,7 @@ case. Must be in look or talk mode to issue command on tile.

    -

    gui/gm-editor

    +

    gui/gm-editor

    There are three ways to open this editor:

    • using gui/gm-editor command/keybinding - opens editor on what is selected @@ -3584,14 +3589,14 @@ the same as version above.
    • in-game help.

    -

    gui/mod-manager

    +

    gui/mod-manager

    A way to simply install and remove small mods. It looks for specially formated mods in df subfolder 'mods'. Mods are not included, for example mods see: github mini mod repository

    images/mod-manager.png
    -

    Behavior Mods

    +

    Behavior Mods

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

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

    -

    Siege Engine

    +

    Siege Engine

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

    -

    Rationale

    +

    Rationale

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

    -

    Configuration UI

    +

    Configuration UI

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

    @@ -3638,7 +3643,7 @@ menu.

    -

    Power Meter

    +

    Power Meter

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

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

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

    -

    Steam Engine

    +

    Steam Engine

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

    -

    Rationale

    +

    Rationale

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

    -

    Construction

    +

    Construction

    The workshop needs water as its input, which it takes via a passable floor tile below it, like usual magma workshops do. The magma version also needs magma.

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

    -

    Operation

    +

    Operation

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

    -

    Explosions

    +

    Explosions

    The engine must be constructed using barrel, pipe and piston from fire-safe, or in the magma version magma-safe metals.

    During operation weak parts get gradually worn out, and @@ -3728,7 +3733,7 @@ toppled during operation by a building destroyer, or a tantruming dwarf.

    -

    Save files

    +

    Save files

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

    -

    Add Spatter

    +

    Add Spatter

    This plugin makes reactions with names starting with SPATTER_ADD_ produce contaminants on the items instead of improvements. The produced contaminants are immune to being washed away by water or destroyed by diff --git a/Readme.rst b/Readme.rst index 9b8fd07cb..499920cab 100644 --- a/Readme.rst +++ b/Readme.rst @@ -2335,6 +2335,10 @@ alternatively pass cage IDs as arguments:: stripcaged weapons 25321 34228 +undump-buildings +================ +Undesignates building base materials for dumping. + create-items ============ Spawn arbitrary items under the cursor. diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 8c76a36f7..3a374445f 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -1337,6 +1337,7 @@ static const LuaWrapper::FunctionReg dfhack_job_module[] = { WRAPM(Job,checkDesignationsNow), WRAPM(Job,isSuitableItem), WRAPM(Job,isSuitableMaterial), + WRAPM(Job,getName), WRAPN(is_equal, jobEqual), WRAPN(is_item_equal, jobItemEqual), { NULL, NULL } @@ -1475,6 +1476,8 @@ static const LuaWrapper::FunctionReg dfhack_items_module[] = { WRAPM(Items, isCasteMaterial), WRAPM(Items, getSubtypeCount), WRAPM(Items, getSubtypeDef), + WRAPM(Items, getItemBaseValue), + WRAPM(Items, getValue), WRAPN(moveToGround, items_moveToGround), WRAPN(moveToContainer, items_moveToContainer), WRAPN(moveToBuilding, items_moveToBuilding), diff --git a/library/include/df/custom/viewscreen.methods.inc b/library/include/df/custom/viewscreen.methods.inc new file mode 100644 index 000000000..c5d277716 --- /dev/null +++ b/library/include/df/custom/viewscreen.methods.inc @@ -0,0 +1 @@ +friend struct df::interfacest; diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index 34ca98162..e5b6eb4df 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -173,5 +173,11 @@ DFHACK_EXPORT bool remove(MapExtras::MapCache &mc, df::item *item, bool no_uncat /// Detaches the items from its current location and turns it into a projectile DFHACK_EXPORT df::proj_itemst *makeProjectile(MapExtras::MapCache &mc, df::item *item); + +/// Gets value of base-quality item with specified type and material +DFHACK_EXPORT int getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_subtype); + +/// Gets the value of a specific item, ignoring civ values and trade agreements +DFHACK_EXPORT int getValue(df::item *item); } } diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index b13d9c5a9..4b3950ebd 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -81,6 +81,7 @@ namespace DFHack DFHACK_EXPORT bool isSuitableItem(df::job_item *item, df::item_type itype, int isubtype); DFHACK_EXPORT bool isSuitableMaterial(df::job_item *item, int mat_type, int mat_index); + DFHACK_EXPORT std::string getName(df::job *job); } DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b); diff --git a/library/modules/EventManager.cpp b/library/modules/EventManager.cpp index bfc7c4907..88f4d56d6 100644 --- a/library/modules/EventManager.cpp +++ b/library/modules/EventManager.cpp @@ -30,6 +30,7 @@ using namespace std; using namespace DFHack; using namespace EventManager; +using namespace df::enums; /* * TODO: @@ -322,12 +323,8 @@ static void manageJobInitiatedEvent(color_ostream& out) { //helper function for manageJobCompletedEvent static int32_t getWorkerID(df::job* job) { - for ( size_t a = 0; a < job->general_refs.size(); a++ ) { - if ( job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) - continue; - return ((df::general_ref_unit_workerst*)job->general_refs[a])->unit_id; - } - return -1; + auto ref = findRef(job->general_refs, general_ref_type::UNIT_WORKER); + return ref ? ref->getID() : -1; } /* diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 6673e6f1b..38d63d867 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -534,7 +534,7 @@ bool Items::setOwner(df::item *item, df::unit *unit) { df::general_ref *ref = item->general_refs[i]; - if (!strict_virtual_cast(ref)) + if (ref->getType() != general_ref_type::UNIT_ITEMOWNER) continue; if (auto cur = ref->getUnit()) @@ -997,16 +997,22 @@ df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item) if (!ref) return NULL; + auto proj = df::allocate(); + if (!proj) { + delete ref; + return NULL; + } + if (!detachItem(mc, item)) { delete ref; + delete proj; return NULL; } item->pos = pos; item->flags.bits.in_job = true; - auto proj = new df::proj_itemst(); proj->link = new df::proj_list_link(); proj->link->item = proj; proj->id = (*proj_next_id)++; @@ -1022,3 +1028,303 @@ df::proj_itemst *Items::makeProjectile(MapExtras::MapCache &mc, df::item *item) return proj; } + +int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, int16_t mat_type, int32_t mat_subtype) +{ + int value = 0; + switch (item_type) + { + case item_type::BAR: + case item_type::SMALLGEM: + case item_type::BLOCKS: + case item_type::SKIN_TANNED: + value = 5; + break; + + case item_type::ROUGH: + case item_type::BOULDER: + case item_type::WOOD: + value = 3; + break; + + case item_type::DOOR: + case item_type::FLOODGATE: + case item_type::BED: + case item_type::CHAIR: + case item_type::CHAIN: + case item_type::FLASK: + case item_type::GOBLET: + case item_type::INSTRUMENT: + case item_type::TOY: + case item_type::CAGE: + case item_type::BARREL: + case item_type::BUCKET: + case item_type::ANIMALTRAP: + case item_type::TABLE: + case item_type::COFFIN: + case item_type::BOX: + case item_type::BIN: + case item_type::ARMORSTAND: + case item_type::WEAPONRACK: + case item_type::CABINET: + case item_type::FIGURINE: + case item_type::AMULET: + case item_type::SCEPTER: + case item_type::CROWN: + case item_type::RING: + case item_type::EARRING: + case item_type::BRACELET: + case item_type::GEM: + case item_type::ANVIL: + case item_type::TOTEM: + case item_type::BACKPACK: + case item_type::QUIVER: + case item_type::BALLISTAARROWHEAD: + case item_type::PIPE_SECTION: + case item_type::HATCH_COVER: + case item_type::GRATE: + case item_type::QUERN: + case item_type::MILLSTONE: + case item_type::SPLINT: + case item_type::CRUTCH: + case item_type::SLAB: + case item_type::BOOK: + value = 10; + break; + + case item_type::WINDOW: + case item_type::STATUE: + value = 25; + break; + + case item_type::CORPSE: + case item_type::CORPSEPIECE: + case item_type::REMAINS: + return 0; + + case item_type::WEAPON: + if (size_t(item_subtype) < world->raws.itemdefs.weapons.size()) + value = world->raws.itemdefs.weapons[item_subtype]->value; + else + value = 10; + break; + + case item_type::ARMOR: + if (size_t(item_subtype) < world->raws.itemdefs.armor.size()) + value = world->raws.itemdefs.armor[item_subtype]->value; + else + value = 10; + break; + + case item_type::SHOES: + if (size_t(item_subtype) < world->raws.itemdefs.shoes.size()) + value = world->raws.itemdefs.shoes[item_subtype]->value; + else + value = 5; + break; + + case item_type::SHIELD: + if (size_t(item_subtype) < world->raws.itemdefs.shields.size()) + value = world->raws.itemdefs.shields[item_subtype]->value; + else + value = 10; + break; + + case item_type::HELM: + if (size_t(item_subtype) < world->raws.itemdefs.helms.size()) + value = world->raws.itemdefs.helms[item_subtype]->value; + else + value = 10; + break; + + case item_type::GLOVES: + if (size_t(item_subtype) < world->raws.itemdefs.gloves.size()) + value = world->raws.itemdefs.gloves[item_subtype]->value; + else + value = 5; + break; + + case item_type::AMMO: + if (size_t(item_subtype) < world->raws.itemdefs.ammo.size()) + value = world->raws.itemdefs.ammo[item_subtype]->value; + else + value = 1; + break; + + case item_type::MEAT: + case item_type::PLANT: + case item_type::LEAVES: + case item_type::CHEESE: + value = 2; + break; + + case item_type::FISH: + case item_type::FISH_RAW: + case item_type::EGG: + value = 2; + if (size_t(mat_type) < world->raws.creatures.all.size()) + { + auto creature = world->raws.creatures.all[mat_type]; + if (size_t(mat_subtype) < creature->caste.size()) + { + auto caste = creature->caste[mat_subtype]; + mat_type = caste->misc.bone_mat; + mat_subtype = caste->misc.bone_matidx; + } + } + break; + + case item_type::VERMIN: + value = 0; + if (size_t(mat_type) < world->raws.creatures.all.size()) + { + auto creature = world->raws.creatures.all[mat_type]; + if (size_t(mat_subtype) < creature->caste.size()) + value = creature->caste[mat_subtype]->misc.petvalue; + } + value /= 2; + if (!value) + return 1; + return value; + + case item_type::PET: + if (size_t(mat_type) < world->raws.creatures.all.size()) + { + auto creature = world->raws.creatures.all[mat_type]; + if (size_t(mat_subtype) < creature->caste.size()) + return creature->caste[mat_subtype]->misc.petvalue; + } + return 0; + + case item_type::SEEDS: + case item_type::DRINK: + case item_type::POWDER_MISC: + case item_type::LIQUID_MISC: + case item_type::COIN: + case item_type::GLOB: + case item_type::ORTHOPEDIC_CAST: + value = 1; + break; + + case item_type::THREAD: + value = 6; + break; + + case item_type::CLOTH: + value = 7; + break; + + case item_type::PANTS: + if (size_t(item_subtype) < world->raws.itemdefs.pants.size()) + value = world->raws.itemdefs.pants[item_subtype]->value; + else + value = 10; + break; + + case item_type::CATAPULTPARTS: + case item_type::BALLISTAPARTS: + case item_type::TRAPPARTS: + value = 30; + break; + + case item_type::SIEGEAMMO: + case item_type::TRACTION_BENCH: + value = 20; + break; + + case item_type::TRAPCOMP: + if (size_t(item_subtype) < world->raws.itemdefs.trapcomps.size()) + value = world->raws.itemdefs.trapcomps[item_subtype]->value; + else + value = 10; + break; + + case item_type::FOOD: + return 10; + +// case item_type::ROCK: + default: + return 0; + + case item_type::TOOL: + if (size_t(item_subtype) < world->raws.itemdefs.tools.size()) + value = world->raws.itemdefs.tools[item_subtype]->value; + else + value = 10; + break; + } + + MaterialInfo mat; + if (mat.decode(mat_type, mat_subtype)) + value *= mat.material->material_value; + return value; +} + +int Items::getValue(df::item *item) +{ + CHECK_NULL_POINTER(item); + + int16_t item_type = item->getType(); + int16_t item_subtype = item->getSubtype(); + int16_t mat_type = item->getMaterial(); + int32_t mat_subtype = item->getMaterialIndex(); + + // Get base value for item type, subtype, and material + int value = getItemBaseValue(item_type, item_subtype, mat_type, mat_subtype); + + // Ignore entity value modifications + + // Improve value based on quality + int quality = item->getQuality(); + value *= (quality + 1); + if (quality == 5) + value *= 2; + + // Add improvement values + int impValue = item->getThreadDyeValue(NULL) + item->getImprovementsValue(NULL); + if (item_type == item_type::AMMO) // Ammo improvements are worth less + impValue /= 30; + value += impValue; + + // Degrade value due to wear + switch (item->getWear()) + { + case 1: + value = value * 3 / 4; + break; + case 2: + value = value / 2; + break; + case 3: + value = value / 4; + break; + } + + // Ignore value bonuses from magic, since that never actually happens + + // Artifacts have 10x value + if (item->flags.bits.artifact_mood) + value *= 10; + + // Boost value from stack size + value *= item->getStackSize(); + // ...but not for coins + if (item_type == item_type::COIN) + { + value /= 500; + if (!value) + value = 1; + } + + // Handle vermin swarms + if (item_type == item_type::VERMIN || item_type == item_type::PET) + { + int divisor = 1; + auto creature = vector_get(world->raws.creatures.all, mat_type); + if (creature && size_t(mat_subtype) < creature->caste.size()) + divisor = creature->caste[mat_subtype]->misc.petvalue_divisor; + if (divisor > 1) + value /= divisor; + } + return value; +} \ No newline at end of file diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index a86a82d8a..1cfc0fa78 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -53,6 +53,7 @@ using namespace std; #include "df/general_ref.h" #include "df/general_ref_unit_workerst.h" #include "df/general_ref_building_holderst.h" +#include "df/interface_button_building_new_jobst.h" using namespace DFHack; using namespace df::enums; @@ -92,7 +93,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job, bool keepEverything) pnew->job_items[a] = new df::job_item(*pnew->job_items[a]); for ( size_t a = 0; a < job->general_refs.size(); a++ ) - if ( keepEverything || job->general_refs[a]->getType() != df::enums::general_ref_type::UNIT_WORKER ) + if ( keepEverything || job->general_refs[a]->getType() != general_ref_type::UNIT_WORKER ) pnew->general_refs.push_back(job->general_refs[a]->clone()); return pnew; @@ -264,28 +265,18 @@ df::building *DFHack::Job::getHolder(df::job *job) { CHECK_NULL_POINTER(job); - for (size_t i = 0; i < job->general_refs.size(); i++) - { - VIRTUAL_CAST_VAR(ref, df::general_ref_building_holderst, job->general_refs[i]); - if (ref) - return ref->getBuilding(); - } + auto ref = getGeneralRef(job, general_ref_type::BUILDING_HOLDER); - return NULL; + return ref ? ref->getBuilding() : NULL; } df::unit *DFHack::Job::getWorker(df::job *job) { CHECK_NULL_POINTER(job); - for (size_t i = 0; i < job->general_refs.size(); i++) - { - VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->general_refs[i]); - if (ref) - return ref->getUnit(); - } + auto ref = getGeneralRef(job, general_ref_type::UNIT_WORKER); - return NULL; + return ref ? ref->getUnit() : NULL; } void DFHack::Job::setJobCooldown(df::building *workshop, df::unit *worker, int cooldown) @@ -325,8 +316,8 @@ bool DFHack::Job::removeWorker(df::job *job, int cooldown) for (size_t i = 0; i < job->general_refs.size(); i++) { - VIRTUAL_CAST_VAR(ref, df::general_ref_unit_workerst, job->general_refs[i]); - if (!ref) + df::general_ref *ref = job->general_refs[i]; + if (ref->getType() != general_ref_type::UNIT_WORKER) continue; auto worker = ref->getUnit(); @@ -476,3 +467,25 @@ bool Job::isSuitableMaterial(df::job_item *item, int mat_type, int mat_index) return minfo.isValid() && iinfo.matches(*item, &minfo); } + +std::string Job::getName(df::job *job) +{ + CHECK_NULL_POINTER(job); + + std::string desc; + auto button = df::allocate(); + button->reaction_name = job->reaction_name; + button->hist_figure_id = job->hist_figure_id; + button->job_type = job->job_type; + button->item_type = job->item_type; + button->item_subtype = job->item_subtype; + button->mat_type = job->mat_type; + button->mat_index = job->mat_index; + button->item_category = job->item_category; + button->material_category = job->material_category; + + button->getLabel(&desc); + delete button; + + return desc; +} diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 655371091..fd1ccc523 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -584,8 +584,8 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) if ( dx == 0 && dy == 0 ) { //check for forbidden hatches and floors and such - df::enums::tile_building_occ::tile_building_occ upOcc = index_tile(block2->occupancy,pos2).bits.building; - if ( upOcc == df::enums::tile_building_occ::Impassable || upOcc == df::enums::tile_building_occ::Obstacle || upOcc == df::enums::tile_building_occ::Floored ) + df::tile_building_occ upOcc = index_tile(block2->occupancy,pos2).bits.building; + if ( upOcc == tile_building_occ::Impassable || upOcc == tile_building_occ::Obstacle || upOcc == tile_building_occ::Floored ) return false; if ( shape1 == tiletype_shape::STAIR_UPDOWN && shape2 == shape1 ) @@ -617,7 +617,7 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) return false; //unusable ramp //there has to be an unforbidden hatch above the ramp - if ( index_tile(block2->occupancy,pos2).bits.building != df::enums::tile_building_occ::Dynamic ) + if ( index_tile(block2->occupancy,pos2).bits.building != tile_building_occ::Dynamic ) return false; //note that forbidden hatches have Floored occupancy. unforbidden ones have dynamic occupancy df::building* building = Buildings::findAtTile(pos2); @@ -625,7 +625,7 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) out << __FILE__ << ", line " << __LINE__ << ": couldn't find hatch.\n"; return false; } - if ( building->getType() != df::enums::building_type::Hatch ) { + if ( building->getType() != building_type::Hatch ) { return false; } return true; @@ -661,8 +661,8 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) if ( !blockUp ) return false; - df::enums::tile_building_occ::tile_building_occ occupancy = index_tile(blockUp->occupancy,up).bits.building; - if ( occupancy == df::enums::tile_building_occ::Obstacle || occupancy == df::enums::tile_building_occ::Floored || occupancy == df::enums::tile_building_occ::Impassable ) + df::tile_building_occ occupancy = index_tile(blockUp->occupancy,up).bits.building; + if ( occupancy == tile_building_occ::Obstacle || occupancy == tile_building_occ::Floored || occupancy == tile_building_occ::Impassable ) return false; return true; } diff --git a/library/xml b/library/xml index 42736fe49..499507eac 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 42736fe494e7edeb884caa45a8669996a0eafb11 +Subproject commit 499507eac5bdd3abc70e204497248627327cf8de diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 6e1425b9c..e13284274 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -164,6 +164,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(3dveins 3dveins.cpp) DFHACK_PLUGIN(strangemood strangemood.cpp) DFHACK_PLUGIN(command-prompt command-prompt.cpp) + DFHACK_PLUGIN(building-hacks building-hacks.cpp LINK_LIBRARIES lua) endif() # this is the skeleton plugin. If you want to make your own, make a copy and then change it diff --git a/plugins/building-hacks.cpp b/plugins/building-hacks.cpp new file mode 100644 index 000000000..b9c9ea3c9 --- /dev/null +++ b/plugins/building-hacks.cpp @@ -0,0 +1,389 @@ +//most of the code is shamelessly stolen from steam-engine.cpp +#include "Core.h" +#include "Error.h" +#include +#include +#include + +#include "LuaTools.h" +#include +#include "MiscUtils.h" + +#include "df/building_doorst.h" +#include "df/building_workshopst.h" +#include "df/machine.h" +#include "df/machine_tile_set.h" +#include "df/power_info.h" +#include "df/world.h" +#include "df/buildings_other_id.h" +#include "df/coord.h" +#include "df/tile_building_occ.h" +#include "df/building_drawbuffer.h" + +#include + +using namespace DFHack; +using namespace df::enums; +using df::global::world; + +DFHACK_PLUGIN("building-hacks"); +struct graphic_tile //could do just 31x31 and be done, but it's nicer to have flexible imho. +{ + int16_t tile; //originally uint8_t but we need to indicate non-animated tiles + int8_t fore; + int8_t back; + int8_t bright; +}; +struct workshop_hack_data +{ + int32_t myType; + bool impassible_fix; + //machine stuff + df::machine_tile_set connections; + df::power_info powerInfo; + //animation + std::vector > frames; + bool machine_timing; //6 frames used in vanilla + int frame_skip; // e.g. 2 means have to ticks between frames + //updateCallback: + int skip_updates; +}; +typedef std::map workshops_data_t; +workshops_data_t hacked_workshops; + +static void handle_update_action(color_ostream &out,df::building_workshopst*){}; + +DEFINE_LUA_EVENT_1(onUpdateAction,handle_update_action,df::building_workshopst*); +DFHACK_PLUGIN_LUA_EVENTS { + DFHACK_LUA_EVENT(onUpdateAction), + DFHACK_LUA_END +}; +struct work_hook : df::building_workshopst{ + typedef df::building_workshopst interpose_base; + + workshop_hack_data* find_def() + { + if (type == workshop_type::Custom) + { + auto it=hacked_workshops.find(this->getCustomType()); + if(it!=hacked_workshops.end()) + return &(it->second); + } + return NULL; + } + inline bool is_fully_built() + { + return getBuildStage() >= getMaxBuildStage(); + } + DEFINE_VMETHOD_INTERPOSE(uint32_t,getImpassableOccupancy,()) + { + if(auto def = find_def()) + { + if(def->impassible_fix) + return tile_building_occ::Impassable; + } + return INTERPOSE_NEXT(getImpassableOccupancy)(); + } + + DEFINE_VMETHOD_INTERPOSE(void, getPowerInfo, (df::power_info *info)) + { + if (auto def = find_def()) + { + info->produced = def->powerInfo.produced; + info->consumed = def->powerInfo.consumed; + return; + } + + INTERPOSE_NEXT(getPowerInfo)(info); + } + DEFINE_VMETHOD_INTERPOSE(df::machine_info*, getMachineInfo, ()) + { + if (find_def()) + return &machine; + + return INTERPOSE_NEXT(getMachineInfo)(); + } + DEFINE_VMETHOD_INTERPOSE(bool, isPowerSource, ()) + { + workshop_hack_data* def=find_def(); + if (def && def->powerInfo.produced>0) + return true; + + return INTERPOSE_NEXT(isPowerSource)(); + } + DEFINE_VMETHOD_INTERPOSE(void, categorize, (bool free)) + { + if (find_def()) + { + auto &vec = world->buildings.other[buildings_other_id::ANY_MACHINE]; + insert_into_vector(vec, &df::building::id, (df::building*)this); + } + + INTERPOSE_NEXT(categorize)(free); + } + + DEFINE_VMETHOD_INTERPOSE(void, uncategorize, ()) + { + if (find_def()) + { + auto &vec = world->buildings.other[buildings_other_id::ANY_MACHINE]; + erase_from_vector(vec, &df::building::id, id); + } + + INTERPOSE_NEXT(uncategorize)(); + } + DEFINE_VMETHOD_INTERPOSE(bool, canConnectToMachine, (df::machine_tile_set *info)) + { + if (auto def = find_def()) + { + int real_cx = centerx, real_cy = centery; + bool ok = false; + + for (size_t i = 0; i < def->connections.tiles.size(); i++) + { + // the original function connects to the center tile + centerx = x1 + def->connections.tiles[i].x; + centery = y1 + def->connections.tiles[i].y; + + if (!INTERPOSE_NEXT(canConnectToMachine)(info)) + continue; + + ok = true; + break; + } + + centerx = real_cx; centery = real_cy; + return ok; + } + else + return INTERPOSE_NEXT(canConnectToMachine)(info); + } + DEFINE_VMETHOD_INTERPOSE(bool, isUnpowered, ()) + { + if (auto def = find_def()) + { + if(def->powerInfo.consumed==0) + return false; + if(machine.machine_id==-1) + return true; + df::machine* target_machine=df::machine::find(machine.machine_id); + if(target_machine && target_machine->flags.bits.active) + return false; + return true; + } + + return INTERPOSE_NEXT(isUnpowered)(); + } + DEFINE_VMETHOD_INTERPOSE(void, updateAction, ()) + { + if(auto def = find_def()) + { + if(def->skip_updates!=0 && is_fully_built()) + { + df::world* world = df::global::world; + if(world->frame_counter % def->skip_updates == 0) + { + CoreSuspendClaimer suspend; + color_ostream_proxy out(Core::getInstance().getConsole()); + onUpdateAction(out,this); + } + } + } + INTERPOSE_NEXT(updateAction)(); + } + DEFINE_VMETHOD_INTERPOSE(void, drawBuilding, (df::building_drawbuffer *db, int16_t unk)) + { + INTERPOSE_NEXT(drawBuilding)(db, unk); + + if (auto def = find_def()) + { + if (!is_fully_built() || def->frames.size()==0) + return; + int frame=0; + if(!def->machine_timing) + { + int frame_mod=def->frames.size()* def->frame_skip; + df::world* world = df::global::world; + frame=(world->frame_counter % frame_mod)/def->frame_skip; + } + else + { + if(machine.machine_id!=-1) + { + df::machine* target_machine=df::machine::find(machine.machine_id); + if(target_machine) + { + frame=target_machine->visual_phase % def->frames.size(); + } + } + } + int w=db->x2-db->x1+1; + std::vector &cur_frame=def->frames[frame]; + for(int i=0;i=0) + { + int tx=i % w; + int ty=i / w; + db->tile[tx][ty]=cur_frame[i].tile; + db->back[tx][ty]=cur_frame[i].back; + db->bright[tx][ty]=cur_frame[i].bright; + db->fore[tx][ty]=cur_frame[i].fore; + } + } + } + } +}; +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, getImpassableOccupancy); + +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, getPowerInfo); +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, getMachineInfo); +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, isPowerSource); +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, categorize); +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, uncategorize); +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, canConnectToMachine); +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, isUnpowered); +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, updateAction); +IMPLEMENT_VMETHOD_INTERPOSE(work_hook, drawBuilding); +void clear_mapping() +{ + hacked_workshops.clear(); +} +static void loadFrames(lua_State* L,workshop_hack_data& def,int stack_pos) +{ + luaL_checktype(L,stack_pos,LUA_TTABLE); + lua_pushvalue(L,stack_pos); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + luaL_checktype(L,-1,LUA_TTABLE); + lua_pushnil(L); + std::vector frame; + while (lua_next(L, -2) != 0) { + graphic_tile t; + lua_pushnumber(L,1); + lua_gettable(L,-2); + if(lua_isnil(L,-1)) + { + t.tile=-1; + lua_pop(L,1); + } + else + { + t.tile=lua_tonumber(L,-1); + lua_pop(L,1); + + lua_pushnumber(L,2); + lua_gettable(L,-2); + t.fore=lua_tonumber(L,-1); + lua_pop(L,1); + + lua_pushnumber(L,3); + lua_gettable(L,-2); + t.back=lua_tonumber(L,-1); + lua_pop(L,1); + + lua_pushnumber(L,4); + lua_gettable(L,-2); + t.bright=lua_tonumber(L,-1); + lua_pop(L,1); + + } + frame.push_back(t); + lua_pop(L,1); + } + lua_pop(L,1); + def.frames.push_back(frame); + } + lua_pop(L,1); + return ; +} +//arguments: custom type,impassible fix (bool), consumed power, produced power, list of connection points, update skip(0/nil to disable) +// table of frames,frame to tick ratio (-1 for machine control) +static int addBuilding(lua_State* L) +{ + workshop_hack_data newDefinition; + newDefinition.myType=luaL_checkint(L,1); + newDefinition.impassible_fix=luaL_checkint(L,2); + newDefinition.powerInfo.consumed=luaL_checkint(L,3); + newDefinition.powerInfo.produced=luaL_checkint(L,4); + //table of machine connection points + luaL_checktype(L,5,LUA_TTABLE); + lua_pushvalue(L,5); + lua_pushnil(L); + while (lua_next(L, -2) != 0) { + lua_getfield(L,-1,"x"); + int x=lua_tonumber(L,-1); + lua_pop(L,1); + lua_getfield(L,-1,"y"); + int y=lua_tonumber(L,-1); + lua_pop(L,1); + + newDefinition.connections.can_connect.push_back(-1);//TODO add this too... + newDefinition.connections.tiles.push_back(df::coord(x,y,0)); + + lua_pop(L,1); + } + lua_pop(L,1); + //updates + newDefinition.skip_updates=luaL_optinteger(L,6,0); + //animation + if(!lua_isnil(L,7)) + { + loadFrames(L,newDefinition,7); + newDefinition.frame_skip=luaL_optinteger(L,8,-1); + if(newDefinition.frame_skip==0) + newDefinition.frame_skip=1; + if(newDefinition.frame_skip<0) + newDefinition.machine_timing=true; + else + newDefinition.machine_timing=false; + } + hacked_workshops[newDefinition.myType]=newDefinition; + return 0; +} +DFHACK_PLUGIN_LUA_COMMANDS{ + DFHACK_LUA_COMMAND(addBuilding), + DFHACK_LUA_END +}; +static void enable_hooks(bool enable) +{ + INTERPOSE_HOOK(work_hook,getImpassableOccupancy).apply(enable); + //machine part + INTERPOSE_HOOK(work_hook,getPowerInfo).apply(enable); + INTERPOSE_HOOK(work_hook,getMachineInfo).apply(enable); + INTERPOSE_HOOK(work_hook,isPowerSource).apply(enable); + INTERPOSE_HOOK(work_hook,categorize).apply(enable); + INTERPOSE_HOOK(work_hook,uncategorize).apply(enable); + INTERPOSE_HOOK(work_hook,canConnectToMachine).apply(enable); + INTERPOSE_HOOK(work_hook,isUnpowered).apply(enable); + //update n render + INTERPOSE_HOOK(work_hook,updateAction).apply(enable); + INTERPOSE_HOOK(work_hook,drawBuilding).apply(enable); +} +DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) +{ + switch (event) { + case SC_WORLD_LOADED: + enable_hooks(true); + break; + case SC_WORLD_UNLOADED: + enable_hooks(false); + clear_mapping(); + break; + default: + break; + } + + return CR_OK; +} +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + enable_hooks(true); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + plugin_onstatechange(out,SC_WORLD_UNLOADED); + return CR_OK; +} diff --git a/plugins/diggingInvaders/assignJob.cpp b/plugins/diggingInvaders/assignJob.cpp index 9472fb0e7..eff2de4ad 100644 --- a/plugins/diggingInvaders/assignJob.cpp +++ b/plugins/diggingInvaders/assignJob.cpp @@ -88,10 +88,10 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapjob_type = df::enums::job_type::DestroyBuilding; //job->flags.bits.special = 1; - df::general_ref_building_holderst* buildingRef = new df::general_ref_building_holderst; + df::general_ref_building_holderst* buildingRef = df::allocate(); buildingRef->building_id = building->id; job->general_refs.push_back(buildingRef); - df::general_ref_unit_workerst* workerRef = new df::general_ref_unit_workerst; + df::general_ref_unit_workerst* workerRef = df::allocate(); workerRef->unit_id = firstInvader->id; job->general_refs.push_back(workerRef); getRidOfOldJob(firstInvader); @@ -118,7 +118,7 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mapjob_type = df::enums::job_type::RemoveConstruction; - df::general_ref_unit_workerst* workerRef = new df::general_ref_unit_workerst; + df::general_ref_unit_workerst* workerRef = df::allocate(); workerRef->unit_id = firstInvader->id; job->general_refs.push_back(workerRef); job->pos = pt2; @@ -189,7 +189,7 @@ int32_t assignJob(color_ostream& out, Edge firstImportantEdge, unordered_mappos = workHere; firstInvader->path.dest = goHere; location = goHere; - df::general_ref_unit_workerst* ref = new df::general_ref_unit_workerst; + df::general_ref_unit_workerst* ref = df::allocate(); ref->unit_id = firstInvader->id; job->general_refs.push_back(ref); firstInvader->job.hunt_target = NULL; diff --git a/plugins/lua/building-hacks.lua b/plugins/lua/building-hacks.lua new file mode 100644 index 000000000..e0a5b1010 --- /dev/null +++ b/plugins/lua/building-hacks.lua @@ -0,0 +1,109 @@ +local _ENV = mkmodule('plugins.building-hacks') +--[[ + from native: + addBuilding(custom type,impassible fix (bool), consumed power, produced power, list of connection points, + update skip(0/nil to disable),table of frames,frame to tick ratio (-1 for machine control)) + from here: + registerBuilding{ + name -- custom workshop id e.g. SOAPMAKER << required! + fix_impassible -- make impassible tiles impassible to liquids too + consume -- how much machine power is needed to work + produce -- how much machine power is produced + gears -- a table or {x=?,y=?} of connection points for machines + action -- a table of number (how much ticks to skip) and a function which gets called on shop update + animate -- a table of + frames -- a table of + tables of 4 numbers (tile,fore,back,bright) OR + empty table (tile not modified) OR + {x= y= + 4 numbers like in first case} -- this generates full frame even, usefull for animations that change little (1-2 tiles) + frameLenght -- how many ticks does one frame take OR + isMechanical -- a bool that says to try to match to mechanical system (i.e. how gears are turning) + } +]] +_registeredStuff={} +local function unregall(state) + if state==SC_WORLD_UNLOADED then + onUpdateAction._library=nil + dfhack.onStateChange.building_hacks= nil + _registeredStuff={} + end +end +local function onUpdateLocal(workshop) + local f=_registeredStuff[workshop:getCustomType()] + if f then + f(workshop) + end +end +local function findCustomWorkshop(name) + local raws=df.global.world.raws.buildings.all + for k,v in ipairs(raws) do + if v.code==name then + return v + end + end +end +local function registerUpdateAction(shopId,callback) + _registeredStuff[shopId]=callback + onUpdateAction._library=onUpdateLocal + dfhack.onStateChange.building_hacks=unregall +end +local function generateFrame(tiles,w,h) + local mTiles={} + for k,v in ipairs(tiles) do + mTiles[v.x]=mTiles[v.x] or {} + mTiles[v.x][v.y]=v + end + local ret={} + for ty=0,h-1 do + for tx=0,w-1 do + if mTiles[tx] and mTiles[tx][ty] then + table.insert(ret,mTiles[tx][ty]) -- leaves x and y in but who cares + else + table.insert(ret,{}) + end + end + end + return ret +end +local function processFrames(shop_def,frames) + local w,h=shop_def.dim_x,shop_def.dim_y + for frame_id,frame in ipairs(frames) do + if frame[1].x~=nil then + frames[frame_id]=generateFrame(frame,w,h) + end + end + return frames +end +function registerBuilding(args) + local shop_def=findCustomWorkshop(args.name) + local shop_id=shop_def.id + local fix_impassible + if args.fix_impassible then + fix_impassible=1 + else + fix_impassible=0 + end + local consume=args.consume or 0 + local produce=args.produce or 0 + local gears=args.gears or {} + local action=args.action --could be nil + local updateSkip=0 + if action~=nil then + updateSkip=action[1] + registerUpdateAction(shop_id,action[2]) + end + local animate=args.animate + local frameLength=1 + local frames + if animate~=nil then + frameLength=animate.frameLength + if animate.isMechanical then + frameLength=-1 + end + frames=processFrames(shop_def,animate.frames) + end + + addBuilding(shop_id,fix_impassible,consume,produce,gears,updateSkip,frames,frameLength) +end + +return _ENV \ No newline at end of file diff --git a/plugins/lua/eventful.lua b/plugins/lua/eventful.lua index a8840424b..2aa713583 100644 --- a/plugins/lua/eventful.lua +++ b/plugins/lua/eventful.lua @@ -25,13 +25,13 @@ local function getShopName(btype,bsubtype,bcustom) if typenames_shop[bsubtype]~=nil then return typenames_shop[bsubtype] else - return nil --todo add custom (not very useful) + return df.building_def_workshopst.find(bcustom).code end elseif btype==df.building_type.Furnace then if typenames_furnace[bsubtype]~=nil then return typenames_furnace[bsubtype] else - return nil --todo add custom (not very useful) + return df.building_def_furnacest.find(bcustom).code end end end @@ -77,9 +77,19 @@ local function onPostSidebar(workshop) wjob.choices_visible:insert("#",new_button) end end + if _registeredStuff.customSidebar and _registeredStuff.customSidebar[shop_id] then + _registeredStuff.customSidebar[shop_id](workshop) + end + end +end +local function customSidebarsCallback(workshop) + local shop_id=getShopName(workshop:getType(),workshop:getSubtype(),workshop:getCustomType()) + if shop_id then + if _registeredStuff.customSidebar and _registeredStuff.customSidebar[shop_id] then + _registeredStuff.customSidebar[shop_id](workshop) + end end end - function registerReaction(reaction_name,callback) _registeredStuff.reactionCallbacks=_registeredStuff.reactionCallbacks or {} _registeredStuff.reactionCallbacks[reaction_name]=callback @@ -87,6 +97,13 @@ function registerReaction(reaction_name,callback) dfhack.onStateChange.eventful=unregall end +function registerSidebar(shop_name,callback) + _registeredStuff.customSidebar=_registeredStuff.customSidebar or {} + _registeredStuff.customSidebar[shop_name]=callback + onWorkshopFillSidebarMenu._library=customSidebarsCallback + dfhack.onStateChange.eventful=unregall +end + function removeNative(shop_name,name) _registeredStuff.shopNonNative=_registeredStuff.shopNonNative or {} local shops=_registeredStuff.shopNonNative diff --git a/scripts/gui/advfort.lua b/scripts/gui/advfort.lua index 150aed52b..5eb554c1c 100644 --- a/scripts/gui/advfort.lua +++ b/scripts/gui/advfort.lua @@ -928,7 +928,7 @@ function usetool:init(args) wid.Label{ view_id="mainLabel", frame = {xalign=0,yalign=0}, - text={{key=keybinds.prevJob.key},{gap=1,text=dfhack.curry(usetool.getModeName,self)},{gap=1,key=keybinds.nextJob.key}, + text={{key=keybinds.prevJob.key},{gap=1,text=self:callback("getModeName")},{gap=1,key=keybinds.nextJob.key}, } }, diff --git a/scripts/gui/gm-editor.lua b/scripts/gui/gm-editor.lua index 269d09c31..4d0b09c6a 100644 --- a/scripts/gui/gm-editor.lua +++ b/scripts/gui/gm-editor.lua @@ -2,6 +2,7 @@ local gui = require 'gui' local dialog = require 'gui.dialogs' local widgets =require 'gui.widgets' +local guiScript = require 'gui.script' local args={...} local keybindings={ @@ -10,7 +11,9 @@ local keybindings={ lua_set={key="CUSTOM_ALT_S",desc="Set by using a lua function"}, insert={key="CUSTOM_ALT_I",desc="Insert a new value to the vector"}, delete={key="CUSTOM_ALT_D",desc="Delete selected entry"}, + reinterpret={key="CUSTOM_ALT_R",desc="Open selected entry as something else"}, help={key="HELP",desc="Show this help"}, + NOT_USED={key="SEC_SELECT",desc="Choose an enum value from a list"}, --not a binding... } function getTargetFromScreens() local my_trg @@ -75,6 +78,7 @@ function GmEditorUi:init(args) local helpPage=widgets.Panel{ subviews={widgets.Label{text=helptext,frame = {l=1,t=1,yalign=0}}}} local mainList=widgets.List{view_id="list_main",choices={},frame = {l=1,t=3,yalign=0},on_submit=self:callback("editSelected"), + on_submit2=self:callback("editSelectedEnum"), text_pen=dfhack.pen.parse{fg=COLOR_DARKGRAY,bg=0},cursor_pen=dfhack.pen.parse{fg=COLOR_YELLOW,bg=0}} local mainPage=widgets.Panel{ subviews={ @@ -155,6 +159,37 @@ end function GmEditorUi:currentTarget() return self.stack[#self.stack] end +function GmEditorUi:editSelectedEnum(index,choice) + local trg=self:currentTarget() + local trg_key=trg.keys[index] + if trg.target._field==nil then qerror("not an enum") end + local enum=trg.target:_field(trg_key)._type + + if enum._kind=="enum-type" then + local list={} + for i=enum._first_item, enum._last_item do + table.insert(list,{text=tostring(enum[i]),value=i}) + end + guiScript.start(function() + local ret,idx,choice=guiScript.showListPrompt("Choose item:",nil,3,list) + if ret then + trg.target[trg_key]=choice.value + self:updateTarget(true) + end + end) + + else + qerror("not an enum") + end +end +function GmEditorUi:openReinterpret(key) + local trg=self:currentTarget() + dialog.showInputPrompt(tostring(trg_key),"Enter new type:",COLOR_WHITE, + "",function(choice) + local ntype=df[tp] + self:pushTarget(df.reinterpret_cast(ntype,trg.target[key])) + end) +end function GmEditorUi:editSelected(index,choice) local trg=self:currentTarget() local trg_key=trg.keys[index] @@ -206,7 +241,6 @@ function GmEditorUi:set(key,input) self:updateTarget(true) end function GmEditorUi:onInput(keys) - if keys.LEAVESCREEN then if self.subviews.pages:getSelected()==2 then self.subviews.pages:setSelected(1) @@ -218,21 +252,35 @@ function GmEditorUi:onInput(keys) local _,stoff=df.sizeof(trg.target) local size,off=df.sizeof(trg.target:_field(self:getSelectedKey())) dialog.showMessage("Offset",string.format("Size hex=%x,%x dec=%d,%d\nRelative hex=%x dec=%d",size,off,size,off,off-stoff,off-stoff),COLOR_WHITE) - --elseif keys.CUSTOM_ALT_F then --filter? + elseif keys[keybindings.find.key] then self:find() elseif keys[keybindings.lua_set.key] then self:set(self:getSelectedKey()) - --elseif keys.CUSTOM_I then - -- self:insertSimple() elseif keys[keybindings.insert.key] then --insert self:insertNew() elseif keys[keybindings.delete.key] then --delete self:deleteSelected(self:getSelectedKey()) + elseif keys[keybindings.reinterpret.key] then + self:openReinterpret(self:getSelectedKey()) end self.super.onInput(self,keys) end +function getStringValue(trg,field) + local obj=trg.target + + local text=tostring(obj[field]) + pcall(function() + if obj._field ~= nil then + local enum=obj:_field(field)._type + if enum._kind=="enum-type" then + text=text.."("..tostring(enum[obj[field]])..")" + end + end + end) + return text +end function GmEditorUi:updateTarget(preserve_pos,reindex) local trg=self:currentTarget() if reindex then @@ -244,7 +292,7 @@ function GmEditorUi:updateTarget(preserve_pos,reindex) self.subviews.lbl_current_item:itemById('name').text=tostring(trg.target) local t={} for k,v in pairs(trg.keys) do - table.insert(t,{text={{text=string.format("%-25s",tostring(v))},{gap=1,text=tostring(trg.target[v]),}}}) + table.insert(t,{text={{text=string.format("%-25s",tostring(v))},{gap=1,text=getStringValue(trg,v)}}}) end local last_pos if preserve_pos then @@ -292,6 +340,8 @@ if #args~=0 then show_editor(t) end dialog.showInputPrompt("Gm Editor", "Object to edit:", COLOR_GRAY, "",thunk) + elseif args[1]=="free" then + show_editor(df.reinterpret_cast(df[args[2]],args[3])) else local t=load("return "..args[1])() show_editor(t) diff --git a/scripts/undump-buildings.lua b/scripts/undump-buildings.lua new file mode 100644 index 000000000..fc1511343 --- /dev/null +++ b/scripts/undump-buildings.lua @@ -0,0 +1,28 @@ +-- Undesignates building base materials for dumping. +function undump_buildings() + local buildings = df.global.world.buildings.all + local undumped = 0 + for i = 0, #buildings - 1 do + local building = buildings[i] + -- Zones and stockpiles don't have the contained_items field. + if df.building_actual:is_instance(building) then + local items = building.contained_items + for j = 0, #items - 1 do + local contained = items[j] + if contained.use_mode == 2 and contained.item.flags.dump then + -- print(building, contained.item) + undumped = undumped + 1 + contained.item.flags.dump = false + end + end + end + end + + if undumped > 0 then + local s = "s" + if undumped == 1 then s = "" end + print("Undumped "..undumped.." item"..s..".") + end +end + +undump_buildings()