diff --git a/CMakeLists.txt b/CMakeLists.txt index e83f20f54..d16390c45 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,7 +108,7 @@ OPTION(BUILD_PLUGINS "Build the plugins." ON) # enable C++11 features IF(UNIX) add_definitions(-DLINUX_BUILD) - SET(CMAKE_CXX_FLAGS_DEBUG "-g -Wall") + SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -Wall -Wno-unused-variable") SET(CMAKE_CXX_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic -std=c++0x") SET(CMAKE_C_FLAGS "-fvisibility=hidden -m32 -march=i686 -mtune=generic") ENDIF() diff --git a/LUA_API.rst b/LUA_API.rst index 050714c72..e20b946d8 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -594,41 +594,52 @@ 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 +can be omitted. -* ``dfhack.TranslateName(name,in_english,only_last_name)`` +* ``dfhack.isWorldLoaded()`` + + Checks if the world is loaded. + +* ``dfhack.isMapLoaded()`` + + Checks if the world and map are loaded. + +* ``dfhack.TranslateName(name[,in_english,only_last_name])`` Convert a language_name or only the last name part to string. Gui module ---------- -* ``dfhack.gui.getSelectedWorkshopJob(silent)`` +* ``dfhack.gui.getSelectedWorkshopJob([silent])`` When a job is selected in *'q'* mode, returns the job, else prints error unless silent and returns *nil*. -* ``dfhack.gui.getSelectedJob(silent)`` +* ``dfhack.gui.getSelectedJob([silent])`` Returns the job selected in a workshop or unit/jobs screen. -* ``dfhack.gui.getSelectedUnit(silent)`` +* ``dfhack.gui.getSelectedUnit([silent])`` Returns the unit selected via *'v'*, *'k'*, unit/jobs, or a full-screen item view of a cage or suchlike. -* ``dfhack.gui.getSelectedItem(silent)`` +* ``dfhack.gui.getSelectedItem([silent])`` Returns the item selected via *'v'* ->inventory, *'k'*, *'t'*, or a full-screen item view of a container. Note that in the last case, the highlighted *contained item* is returned, not the container itself. -* ``dfhack.gui.showAnnouncement(text,color,is_bright)`` +* ``dfhack.gui.showAnnouncement(text,color[,is_bright])`` Adds a regular announcement with given text, color, and brightness. The is_bright boolean actually seems to invert the brightness. -* ``dfhack.gui.showPopupAnnouncement(text,color,is_bright)`` +* ``dfhack.gui.showPopupAnnouncement(text,color[,is_bright])`` Pops up a titan-style modal announcement window. @@ -688,6 +699,10 @@ Units module Returns the language_name object visible in game, accounting for false identities. +* ``dfhack.units.getIdentity(unit)`` + + Returns the false identity of the unit if it has one, or *nil*. + * ``dfhack.units.getNemesis(unit)`` Returns the nemesis record of the unit if it has one, or *nil*. @@ -704,17 +719,24 @@ Units module The unit is capable of rational action, i.e. not dead, insane or zombie. -* ``dfhack.units.clearBurrowMembers(burrow)`` +* ``dfhack.units.getAge(unit[,true_age])`` - Removes all units from the burrow. + Returns the age of the unit in years as a floating-point value. + If ``true_age`` is true, ignores false identities. -* ``dfhack.units.isInBurrow(unit,burrow)`` +* ``dfhack.units.getNoblePositions(unit)`` - Checks if the unit is in the burrow. + Returns a list of tables describing noble position assignments, or *nil*. + Every table has fields ``entity``, ``assignment`` and ``position``. -* ``dfhack.units.setInBurrow(unit,burrow,enable)`` +* ``dfhack.units.getProfessionName(unit[,ignore_noble,plural])`` - Adds or removes the unit from the burrow. + Retrieves the profession name using custom profession, noble assignments + or raws. The ``ignore_noble`` boolean disables the use of noble positions. + +* ``dfhack.units.getCasteProfessionName(race,caste,prof_id[,plural])`` + + Retrieves the profession name for the given race/caste using raws. Items module @@ -724,6 +746,14 @@ Items module Returns true *x,y,z* of the item; may be not equal to item.pos if in inventory. +* ``dfhack.items.getGeneralRef(item, type)`` + + Searches for a general_ref with the given type. + +* ``dfhack.items.getSpecificRef(item, type)`` + + Searches for a specific_ref with the given type. + * ``dfhack.items.getOwner(item)`` Returns the owner unit or *nil*. @@ -765,9 +795,9 @@ Maps module Returns a map block object for given x,y,z in local block coordinates. -* ``dfhack.maps.getTileBlock(coords)`` +* ``dfhack.maps.getTileBlock(coords)``, or ``getTileBlock(x,y,z)`` - Returns a map block object for given df::coord in local tile coordinates. + Returns a map block object for given df::coord or x,y,z in local tile coordinates. * ``dfhack.maps.getRegionBiome(region_coord2d)`` @@ -781,35 +811,167 @@ Maps module Returns the local feature object with the given region coords and index. -* ``dfhack.maps.findBurrowByName(name)`` +* ``dfhack.maps.canWalkBetween(pos1, pos2)`` + + Checks if a dwarf may be able to walk between the two tiles, + using a pathfinding cache maintained by the game. Note that + this cache is only updated when the game is unpaused, and thus + can get out of date if doors are forbidden or unforbidden, or + tools like liquids or tiletypes are used. It also cannot possibly + take into account anything that depends on the actual units, like + burrows, or the presence of invaders. + + +Burrows module +-------------- + +* ``dfhack.burrows.findByName(name)`` Returns the burrow pointer or *nil*. -* ``dfhack.maps.listBurrowBlocks(burrow)`` +* ``dfhack.burrows.clearUnits(burrow)`` - Returns a table of map block pointers. + Removes all units from the burrow. + +* ``dfhack.burrows.isAssignedUnit(burrow,unit)`` + + Checks if the unit is in the burrow. -* ``dfhack.maps.clearBurrowTiles(burrow)`` +* ``dfhack.burrows.setAssignedUnit(burrow,unit,enable)`` + + Adds or removes the unit from the burrow. + +* ``dfhack.burrows.clearTiles(burrow)`` Removes all tiles from the burrow. -* ``dfhack.maps.isBurrowTile(burrow,tile_coord)`` +* ``dfhack.burrows.listBlocks(burrow)`` + + Returns a table of map block pointers. + +* ``dfhack.burrows.isAssignedTile(burrow,tile_coord)`` Checks if the tile is in burrow. -* ``dfhack.maps.setBurrowTile(burrow,tile_coord,enable)`` +* ``dfhack.burrows.setAssignedTile(burrow,tile_coord,enable)`` Adds or removes the tile from the burrow. Returns *false* if invalid coords. -* ``dfhack.maps.isBlockBurrowTile(burrow,block,x,y)`` +* ``dfhack.burrows.isAssignedBlockTile(burrow,block,x,y)`` Checks if the tile within the block is in burrow. -* ``dfhack.maps.setBlockBurrowTile(burrow,block,x,y,enable)`` +* ``dfhack.burrows.setAssignedBlockTile(burrow,block,x,y,enable)`` Adds or removes the tile from the burrow. Returns *false* if invalid coords. +Buildings module +---------------- + +* ``dfhack.buildings.getSize(building)`` + + Returns *width, height, centerx, centery*. + +* ``dfhack.buildings.findAtTile(pos)``, or ``findAtTile(x,y,z)`` + + Scans the buildings for the one located at the given tile. + Does not work on civzones. Warning: linear scan if the map + tile indicates there are buildings at it. + +* ``dfhack.buildings.findCivzonesAt(pos)``, or ``findCivzonesAt(x,y,z)`` + + Scans civzones, and returns a lua sequence of those that touch + the given tile, or *nil* if none. + +* ``dfhack.buildings.getCorrectSize(width, height, type, subtype, custom, direction)`` + + Computes correct dimensions for the specified building type and orientation, + using width and height for flexible dimensions. + Returns *is_flexible, width, height, center_x, center_y*. + +* ``dfhack.buildings.checkFreeTiles(pos,size[,extents,change_extents,allow_occupied])`` + + Checks if the rectangle defined by ``pos`` and ``size``, and possibly extents, + can be used for placing a building. If ``change_extents`` is true, bad tiles + are removed from extents. If ``allow_occupied``, the occupancy test is skipped. + +* ``dfhack.buildings.countExtentTiles(extents,defval)`` + + Returns the number of tiles included by extents, or defval. + +* ``dfhack.buildings.containsTile(building, x, y[, room])`` + + Checks if the building contains the specified tile, either directly, or as room. + +* ``dfhack.buildings.hasSupport(pos,size)`` + + Checks if a bridge constructed at specified position would have + support from terrain, and thus won't collapse if retracted. + +Low-level building creation functions; + +* ``dfhack.buildings.allocInstance(pos, type, subtype, custom)`` + + Creates a new building instance of given type, subtype and custom type, + at specified position. Returns the object, or *nil* in case of an error. + +* ``dfhack.buildings.setSize(building, width, height, direction)`` + + Configures an object returned by ``allocInstance``, using specified + parameters wherever appropriate. If the building has fixed size along + any dimension, the corresponding input parameter will be ignored. + Returns *false* if the building cannot be placed, or *true, width, + height, rect_area, true_area*. Returned width and height are the + final values used by the building; true_area is less than rect_area + if any tiles were removed from designation. + +* ``dfhack.buildings.constructAbstract(building)`` + + Links a fully configured object created by ``allocInstance`` into the + world. The object must be an abstract building, i.e. a stockpile or civzone. + Returns *true*, or *false* if impossible. + +* ``dfhack.buildings.constructWithItems(building, items)`` + + Links a fully configured object created by ``allocInstance`` into the + world for construction, using a list of specific items as material. + Returns *true*, or *false* if impossible. + +* ``dfhack.buildings.constructWithFilters(building, job_items)`` + + Links a fully configured object created by ``allocInstance`` into the + world for construction, using a list of job_item filters as inputs. + Returns *true*, or *false* if impossible. Filter objects are claimed + and possibly destroyed in any case. + Use a negative ``quantity`` field value to auto-compute the amount + from the size of the building. + +* ``dfhack.buildings.deconstruct(building)`` + + Destroys the building, or queues a deconstruction job. + Returns *true* if the building was destroyed and deallocated immediately. + +More high-level functions are implemented in lua and can be loaded by +``require('dfhack.buildings')``. See ``hack/lua/dfhack/buildings.lua``. + +Constructions module +-------------------- + +* ``dfhack.constructions.designateNew(pos,type,item_type,mat_index)`` + + Designates a new construction at given position. If there already is + a planned but not completed construction there, changes its type. + Returns *true*, or *false* if obstructed. + Note that designated constructions are technically buildings. + +* ``dfhack.constructions.designateRemove(pos)``, or ``designateRemove(x,y,z)`` + + If there is a construction or a planned construction at the specified + coordinates, designates it for removal, or instantly cancels the planned one. + Returns *true, was_only_planned* if removed; or *false* if none found. + + Core interpreter context ======================== @@ -823,6 +985,18 @@ Core context specific functions: Boolean value; *true* in the core context. +* ``dfhack.timeout(time,mode,callback)`` + + Arranges for the callback to be called once the specified + period of time passes. The ``mode`` argument specifies the + unit of time used, and may be one of ``'frames'`` (raw FPS), + ``'ticks'`` (unpaused FPS), ``'days'``, ``'months'``, + ``'years'`` (in-game time). All timers other than + ``'frames'`` are cancelled when the world is unloaded, + and cannot be queued until it is loaded again. + Returns the timer id, or *nil* if unsuccessful due to + world being unloaded. + * ``dfhack.onStateChange.foo = function(code)`` Event. Receives the same codes as plugin_onstatechange in C++. @@ -856,3 +1030,65 @@ Features: Invokes all listeners contained in the event in an arbitrary order using ``dfhack.safecall``. + +======= +Plugins +======= + +DFHack plugins may export native functions and events +to lua contexts. They are automatically imported by +``mkmodule('plugins.')``; this means that a lua +module file is still necessary for ``require`` to read. + +The following plugins have lua support. + +burrows +======= + +Implements extended burrow manipulations. + +Events: + +* ``onBurrowRename.foo = function(burrow)`` + + Emitted when a burrow might have been renamed either through + the game UI, or ``renameBurrow()``. + +* ``onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype)`` + + Emitted when a tile might have been dug out. Only tracked if the + auto-growing burrows feature is enabled. + +Native functions: + +* ``renameBurrow(burrow,name)`` + + Renames the burrow, emitting ``onBurrowRename`` and updating auto-grow state properly. + +* ``findByName(burrow,name)`` + + Finds a burrow by name, using the same rules as the plugin command line interface. + Namely, trailing ``'+'`` characters marking auto-grow burrows are ignored. + +* ``copyUnits(target,source,enable)`` + + Applies units from ``source`` burrow to ``target``. The ``enable`` + parameter specifies if they are to be added or removed. + +* ``copyTiles(target,source,enable)`` + + Applies tiles from ``source`` burrow to ``target``. The ``enable`` + parameter specifies if they are to be added or removed. + +* ``setTilesByKeyword(target,keyword,enable)`` + + Adds or removes tiles matching a predefined keyword. The keyword + set is the same as used by the command line. + +The lua module file also re-exports functions from ``dfhack.burrows``. + +sort +==== + +Does not export any native functions as of now. Instead, it +calls lua code to perform the actual ordering of list items. diff --git a/Lua API.html b/Lua API.html index cbf35f0eb..7aa8e651a 100644 --- a/Lua API.html +++ b/Lua API.html @@ -342,14 +342,22 @@ ul.auto-toc {
  • Units module
  • Items module
  • Maps module
  • +
  • Burrows module
  • +
  • Buildings module
  • +
  • Constructions module
  • -
  • Core interpreter context
  • +
  • Plugins +
  • @@ -842,37 +850,46 @@ Accept dfhack_material_category auto-assign table.

    C++ function wrappers

    -

    Thin wrappers around C++ functions, similar to the ones for virtual methods.

    +

    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 +can be omitted.

    Gui module

      -
    • dfhack.gui.getSelectedWorkshopJob(silent)

      +
    • dfhack.gui.getSelectedWorkshopJob([silent])

      When a job is selected in 'q' mode, returns the job, else prints error unless silent and returns nil.

    • -
    • dfhack.gui.getSelectedJob(silent)

      +
    • dfhack.gui.getSelectedJob([silent])

      Returns the job selected in a workshop or unit/jobs screen.

    • -
    • dfhack.gui.getSelectedUnit(silent)

      +
    • dfhack.gui.getSelectedUnit([silent])

      Returns the unit selected via 'v', 'k', unit/jobs, or a full-screen item view of a cage or suchlike.

    • -
    • dfhack.gui.getSelectedItem(silent)

      +
    • dfhack.gui.getSelectedItem([silent])

      Returns the item selected via 'v' ->inventory, 'k', 't', or a full-screen item view of a container. Note that in the last case, the highlighted contained item is returned, not the container itself.

    • -
    • dfhack.gui.showAnnouncement(text,color,is_bright)

      +
    • dfhack.gui.showAnnouncement(text,color[,is_bright])

      Adds a regular announcement with given text, color, and brightness. The is_bright boolean actually seems to invert the brightness.

    • -
    • dfhack.gui.showPopupAnnouncement(text,color,is_bright)

      +
    • dfhack.gui.showPopupAnnouncement(text,color[,is_bright])

      Pops up a titan-style modal announcement window.

    @@ -923,6 +940,9 @@ a lua list containing them.

  • dfhack.units.getVisibleName(unit)

    Returns the language_name object visible in game, accounting for false identities.

  • +
  • dfhack.units.getIdentity(unit)

    +

    Returns the false identity of the unit if it has one, or nil.

    +
  • dfhack.units.getNemesis(unit)

    Returns the nemesis record of the unit if it has one, or nil.

  • @@ -935,14 +955,20 @@ a lua list containing them.

  • dfhack.units.isSane(unit)

    The unit is capable of rational action, i.e. not dead, insane or zombie.

  • -
  • dfhack.units.clearBurrowMembers(burrow)

    -

    Removes all units from the burrow.

    +
  • dfhack.units.getAge(unit[,true_age])

    +

    Returns the age of the unit in years as a floating-point value. +If true_age is true, ignores false identities.

  • -
  • dfhack.units.isInBurrow(unit,burrow)

    -

    Checks if the unit is in the burrow.

    +
  • dfhack.units.getNoblePositions(unit)

    +

    Returns a list of tables describing noble position assignments, or nil. +Every table has fields entity, assignment and position.

  • -
  • dfhack.units.setInBurrow(unit,burrow,enable)

    -

    Adds or removes the unit from the burrow.

    +
  • dfhack.units.getProfessionName(unit[,ignore_noble,plural])

    +

    Retrieves the profession name using custom profession, noble assignments +or raws. The ignore_noble boolean disables the use of noble positions.

    +
  • +
  • dfhack.units.getCasteProfessionName(race,caste,prof_id[,plural])

    +

    Retrieves the profession name for the given race/caste using raws.

  • @@ -952,6 +978,12 @@ a lua list containing them.

  • dfhack.items.getPosition(item)

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

  • +
  • dfhack.items.getGeneralRef(item, type)

    +

    Searches for a general_ref with the given type.

    +
  • +
  • dfhack.items.getSpecificRef(item, type)

    +

    Searches for a specific_ref with the given type.

    +
  • dfhack.items.getOwner(item)

    Returns the owner unit or nil.

  • @@ -985,8 +1017,8 @@ Returns false in case of error.

  • dfhack.maps.getBlock(x,y,z)

    Returns a map block object for given x,y,z in local block coordinates.

  • -
  • dfhack.maps.getTileBlock(coords)

    -

    Returns a map block object for given df::coord in local tile coordinates.

    +
  • dfhack.maps.getTileBlock(coords), or getTileBlock(x,y,z)

    +

    Returns a map block object for given df::coord or x,y,z in local tile coordinates.

  • dfhack.maps.getRegionBiome(region_coord2d)

    Returns the biome info struct for the given global map region.

    @@ -997,32 +1029,148 @@ Returns false in case of error.

  • dfhack.maps.getLocalInitFeature(region_coord2d,index)

    Returns the local feature object with the given region coords and index.

  • -
  • dfhack.maps.findBurrowByName(name)

    +
  • dfhack.maps.canWalkBetween(pos1, pos2)

    +

    Checks if a dwarf may be able to walk between the two tiles, +using a pathfinding cache maintained by the game. Note that +this cache is only updated when the game is unpaused, and thus +can get out of date if doors are forbidden or unforbidden, or +tools like liquids or tiletypes are used. It also cannot possibly +take into account anything that depends on the actual units, like +burrows, or the presence of invaders.

    +
  • + +
    +
    +

    Burrows module

    +
    +
    +

    Buildings module

    + +

    Low-level building creation functions;

    + +

    More high-level functions are implemented in lua and can be loaded by +require('dfhack.buildings'). See hack/lua/dfhack/buildings.lua.

    +
    +
    +

    Constructions module

    + +
    -

    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.

    @@ -1031,12 +1179,23 @@ only context that can receive events from DF and plugins.

  • dfhack.is_core_context

    Boolean value; true in the core context.

  • +
  • dfhack.timeout(time,mode,callback)

    +

    Arranges for the callback to be called once the specified +period of time passes. The mode argument specifies the +unit of time used, and may be one of 'frames' (raw FPS), +'ticks' (unpaused FPS), 'days', 'months', +'years' (in-game time). All timers other than +'frames' are cancelled when the world is unloaded, +and cannot be queued until it is loaded again. +Returns the timer id, or nil if unsuccessful due to +world being unloaded.

    +
  • dfhack.onStateChange.foo = function(code)

    Event. Receives the same codes as plugin_onstatechange in C++.

  • -

    Event type

    +

    Event type

    An event is just a lua table with a predefined metatable that contains a __call metamethod. When it is invoked, it loops through the table with next and calls all contained values. @@ -1061,6 +1220,57 @@ order using dfhack.safecall.

    +
    +

    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

    +

    Implements extended burrow manipulations.

    +

    Events:

    +
      +
    • onBurrowRename.foo = function(burrow)

      +

      Emitted when a burrow might have been renamed either through +the game UI, or renameBurrow().

      +
    • +
    • onDigComplete.foo = function(job_type,pos,old_tiletype,new_tiletype)

      +

      Emitted when a tile might have been dug out. Only tracked if the +auto-growing burrows feature is enabled.

      +
    • +
    +

    Native functions:

    +
      +
    • renameBurrow(burrow,name)

      +

      Renames the burrow, emitting onBurrowRename and updating auto-grow state properly.

      +
    • +
    • findByName(burrow,name)

      +

      Finds a burrow by name, using the same rules as the plugin command line interface. +Namely, trailing '+' characters marking auto-grow burrows are ignored.

      +
    • +
    • copyUnits(target,source,enable)

      +

      Applies units from source burrow to target. The enable +parameter specifies if they are to be added or removed.

      +
    • +
    • copyTiles(target,source,enable)

      +

      Applies tiles from source burrow to target. The enable +parameter specifies if they are to be added or removed.

      +
    • +
    • setTilesByKeyword(target,keyword,enable)

      +

      Adds or removes tiles matching a predefined keyword. The keyword +set is the same as used by the command line.

      +
    • +
    +

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

    +
    +
    +

    sort

    +

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

    +
    +
    diff --git a/depends/protobuf/CMakeLists.txt b/depends/protobuf/CMakeLists.txt index f4a3b6d19..dddefb06b 100644 --- a/depends/protobuf/CMakeLists.txt +++ b/depends/protobuf/CMakeLists.txt @@ -57,7 +57,7 @@ IF(CMAKE_COMPILER_IS_GNUCC) ENDIF() IF (HAVE_HASH_MAP EQUAL 0) - MESSAGE(SEND_ERROR "Could not find a working hash map implementation. Please update GCC.") + MESSAGE(SEND_ERROR "Could not find a working hash map implementation. Please install GCC >= 4.4, and all necessary 32-bit C++ development libraries.") ENDIF() FIND_PACKAGE(Threads) @@ -200,6 +200,8 @@ google/protobuf/compiler/zip_writer.cc LIST(APPEND LIBPROTOBUF_FULL_SRCS ${LIBPROTOBUF_LITE_SRCS}) +SET(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -Wno-sign-compare") + INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) SET(PROTOBUF_INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}) INCLUDE_DIRECTORIES(${ZLIB_INCLUDE_DIRS}) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index b4ea751e7..32b7d9dad 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -64,6 +64,7 @@ DataStatics.cpp DataStaticsCtor.cpp DataStaticsFields.cpp MiscUtils.cpp +Types.cpp PluginManager.cpp TileTypes.cpp VersionInfoFactory.cpp @@ -100,6 +101,7 @@ Process-linux.cpp SET(MODULE_HEADERS include/modules/Buildings.h +include/modules/Burrows.h include/modules/Constructions.h include/modules/Units.h include/modules/Engravings.h @@ -120,6 +122,7 @@ include/modules/Graphic.h SET( MODULE_SOURCES modules/Buildings.cpp +modules/Burrows.cpp modules/Constructions.cpp modules/Units.cpp modules/Engravings.cpp @@ -298,6 +301,9 @@ install(DIRECTORY lua/ DESTINATION ${DFHACK_LUA_DESTINATION} FILES_MATCHING PATTERN "*.lua") +install(DIRECTORY ${dfhack_SOURCE_DIR}/scripts + DESTINATION ${DFHACK_DATA_DESTINATION}) + # Unused for so long that it's not even relevant now... if(BUILD_DEVEL) if(WIN32) diff --git a/library/Console-linux.cpp b/library/Console-linux.cpp index 9a0905647..3c0ad8938 100644 --- a/library/Console-linux.cpp +++ b/library/Console-linux.cpp @@ -163,6 +163,7 @@ namespace DFHack return false; return true; } + return false; } public: @@ -489,7 +490,7 @@ namespace DFHack { right_arrow: /* right arrow */ - if (raw_cursor != raw_buffer.size()) + if (size_t(raw_cursor) != raw_buffer.size()) { raw_cursor++; prompt_refresh(); @@ -510,7 +511,7 @@ namespace DFHack history_index = 0; break; } - else if (history_index >= history.size()) + else if (size_t(history_index) >= history.size()) { history_index = history.size()-1; break; @@ -545,7 +546,7 @@ namespace DFHack if (seq[1] == '3' && seq2 == '~' ) { // delete - if (raw_buffer.size() > 0 && raw_cursor < raw_buffer.size()) + if (raw_buffer.size() > 0 && size_t(raw_cursor) < raw_buffer.size()) { raw_buffer.erase(raw_cursor,1); prompt_refresh(); @@ -555,11 +556,11 @@ namespace DFHack } break; default: - if (raw_buffer.size() == raw_cursor) + if (raw_buffer.size() == size_t(raw_cursor)) { raw_buffer.append(1,c); raw_cursor++; - if (plen+raw_buffer.size() < get_columns()) + if (plen+raw_buffer.size() < size_t(get_columns())) { /* Avoid a full update of the line in the * trivial case. */ diff --git a/library/Core.cpp b/library/Core.cpp index 6e1e0986a..d7e4435ce 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -51,6 +51,8 @@ using namespace std; #include "RemoteServer.h" #include "LuaTools.h" +#include "MiscUtils.h" + using namespace DFHack; #include "df/ui.h" @@ -72,8 +74,6 @@ using df::global::init; // FIXME: A lot of code in one file, all doing different things... there's something fishy about it. -static void loadScriptFile(Core *core, PluginManager *plug_mgr, string fname, bool silent); -static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command); static bool parseKeySpec(std::string keyspec, int *psym, int *pmod); struct Core::Cond @@ -178,21 +178,10 @@ void fHKthread(void * iodata) { color_ostream_proxy out(core->getConsole()); - vector args; - Core::cheap_tokenise(stuff, args); - if (args.empty()) { - out.printerr("Empty hotkey command.\n"); - continue; - } + auto rv = core->runCommand(out, stuff); - string first = args[0]; - args.erase(args.begin()); - command_result cr = plug_mgr->InvokeCommand(out, first, args); - - if(cr == CR_NEEDS_CONSOLE) - { - out.printerr("It isn't possible to run an interactive command outside the console.\n"); - } + if (rv == CR_NOT_IMPLEMENTED) + out.printerr("Invalid hotkey command: '%s'\n", stuff.c_str()); } } } @@ -212,38 +201,122 @@ struct sortable }; }; -static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clueless_counter, const string &command) +static std::string getLuaHelp(std::string path) +{ + ifstream script(path); + + if (script.good()) + { + std::string help; + if (getline(script, help) && + help.substr(0,3) == "-- ") + return help.substr(3); + } + + return "Lua script."; +} + +static std::map listLuaScripts(std::string path) +{ + std::vector files; + getdir(path, files); + + std::map pset; + for (size_t i = 0; i < files.size(); i++) + { + if (hasEnding(files[i], ".lua")) + { + std::string help = getLuaHelp(path + files[i]); + + pset[files[i].substr(0, files[i].size()-4)] = help; + } + } + return pset; +} + +static bool fileExists(std::string path) +{ + ifstream script(path); + return script.good(); +} + +namespace { + struct ScriptArgs { + const string *pcmd; + vector *pargs; + }; +} + +static bool init_run_script(color_ostream &out, lua_State *state, void *info) +{ + auto args = (ScriptArgs*)info; + if (!lua_checkstack(state, args->pargs->size()+10)) + return false; + Lua::PushDFHack(state); + lua_getfield(state, -1, "run_script"); + lua_remove(state, -2); + lua_pushstring(state, args->pcmd->c_str()); + for (size_t i = 0; i < args->pargs->size(); i++) + lua_pushstring(state, (*args->pargs)[i].c_str()); + return true; +} + +static command_result runLuaScript(color_ostream &out, std::string filename, vector &args) +{ + ScriptArgs data; + data.pcmd = &filename; + data.pargs = &args; + +#ifndef LINUX_BUILD + filename = toLower(filename); +#endif + + bool ok = Lua::RunCoreQueryLoop(out, Lua::Core::State, init_run_script, &data); + + return ok ? CR_OK : CR_FAILURE; +} + +command_result Core::runCommand(color_ostream &out, const std::string &command) { - Console & con = core->getConsole(); - if (!command.empty()) { - // cut the input into parts vector parts; Core::cheap_tokenise(command,parts); if(parts.size() == 0) - { - clueless_counter ++; - return; - } + return CR_NOT_IMPLEMENTED; + string first = parts[0]; parts.erase(parts.begin()); - if (first[0] == '#') return; + if (first[0] == '#') + return CR_OK; cerr << "Invoking: " << command << endl; - + + return runCommand(out, first, parts); + } + else + return CR_NOT_IMPLEMENTED; +} + +command_result Core::runCommand(color_ostream &con, const std::string &first, vector &parts) +{ + if (!first.empty()) + { // let's see what we actually got if(first=="help" || first == "?" || first == "man") { if(!parts.size()) { - con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" - "Some basic editing capabilities are included (single-line text editing).\n" - "The console also has a command history - you can navigate it with Up and Down keys.\n" - "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" - "by clicking on the program icon in the top bar of the window.\n\n" - "Basic commands:\n" + if (con.is_console()) + { + con.print("This is the DFHack console. You can type commands in and manage DFHack plugins from it.\n" + "Some basic editing capabilities are included (single-line text editing).\n" + "The console also has a command history - you can navigate it with Up and Down keys.\n" + "On Windows, you may have to resize your console window. The appropriate menu is accessible\n" + "by clicking on the program icon in the top bar of the window.\n\n"); + } + con.print("Basic commands:\n" " help|?|man - This text.\n" " help COMMAND - Usage help for the given command.\n" " ls|dir [PLUGIN] - List available commands. Optionally for single plugin.\n" @@ -274,9 +347,16 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue con.reset_color(); if (!pcmd.usage.empty()) con << "Usage:\n" << pcmd.usage << flush; - return; + return CR_OK; } } + auto filename = getHackPath() + "scripts/" + parts[0] + ".lua"; + if (fileExists(filename)) + { + string help = getLuaHelp(filename); + con.print("%s: %s\n", parts[0].c_str(), help.c_str()); + return CR_OK; + } con.printerr("Unknown command: %s\n", parts[0].c_str()); } else @@ -421,6 +501,13 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue con.print(" %-22s- %s\n",(*iter).name.c_str(), (*iter).description.c_str()); con.reset_color(); } + auto scripts = listLuaScripts(getHackPath() + "scripts/"); + if (!scripts.empty()) + { + con.print("\nscripts:\n"); + for (auto iter = scripts.begin(); iter != scripts.end(); ++iter) + con.print(" %-22s- %s\n", iter->first.c_str(), iter->second.c_str()); + } } } else if(first == "plug") @@ -439,10 +526,10 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue { std::string keystr = parts[1]; if (parts[0] == "set") - core->ClearKeyBindings(keystr); + ClearKeyBindings(keystr); for (int i = parts.size()-1; i >= 2; i--) { - if (!core->AddKeyBinding(keystr, parts[i])) { + if (!AddKeyBinding(keystr, parts[i])) { con.printerr("Invalid key spec: %s\n", keystr.c_str()); break; } @@ -452,7 +539,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue { for (size_t i = 1; i < parts.size(); i++) { - if (!core->ClearKeyBindings(parts[i])) { + if (!ClearKeyBindings(parts[i])) { con.printerr("Invalid key spec: %s\n", parts[i].c_str()); break; } @@ -460,7 +547,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue } else if (parts.size() == 2 && parts[0] == "list") { - std::vector list = core->ListKeyBindings(parts[1]); + std::vector list = ListKeyBindings(parts[1]); if (list.empty()) con << "No bindings." << endl; for (size_t i = 0; i < list.size(); i++) @@ -478,13 +565,19 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue } else if(first == "fpause") { - World * w = core->getWorld(); + World * w = getWorld(); w->SetPauseState(true); - con.print("The game was forced to pause!"); + con.print("The game was forced to pause!\n"); } else if(first == "cls") { - con.clear(); + if (con.is_console()) + ((Console&)con).clear(); + else + { + con.printerr("No console to clear.\n"); + return CR_NEEDS_CONSOLE; + } } else if(first == "die") { @@ -494,12 +587,13 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue { if(parts.size() == 1) { - loadScriptFile(core, plug_mgr, parts[0], false); + loadScriptFile(con, parts[0], false); } else { con << "Usage:" << endl << " script " << endl; + return CR_WRONG_USAGE; } } else @@ -507,35 +601,44 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue command_result res = plug_mgr->InvokeCommand(con, first, parts); if(res == CR_NOT_IMPLEMENTED) { - con.printerr("%s is not a recognized command.\n", first.c_str()); - clueless_counter ++; + auto filename = getHackPath() + "scripts/" + first + ".lua"; + if (fileExists(filename)) + res = runLuaScript(con, filename, parts); + else + con.printerr("%s is not a recognized command.\n", first.c_str()); } + else if (res == CR_NEEDS_CONSOLE) + con.printerr("%s needs interactive console to work.\n", first.c_str()); + return res; } + + return CR_OK; } + + return CR_NOT_IMPLEMENTED; } -static void loadScriptFile(Core *core, PluginManager *plug_mgr, string fname, bool silent) +bool Core::loadScriptFile(color_ostream &out, string fname, bool silent) { if(!silent) - core->getConsole() << "Loading script at " << fname << std::endl; + out << "Loading script at " << fname << std::endl; ifstream script(fname); if (script.good()) { - int tmp = 0; string command; while (getline(script, command)) { if (!command.empty()) - runInteractiveCommand(core, plug_mgr, tmp, command); + runCommand(out, command); } + return true; } else { if(!silent) - core->getConsole().printerr("Error loading script\n"); + out.printerr("Error loading script\n"); + return false; } - - script.close(); } // A thread function... for the interactive console. @@ -555,7 +658,7 @@ void fIOthread(void * iodata) return; } - loadScriptFile(core, plug_mgr, "dfhack.init", true); + core->loadScriptFile(con, "dfhack.init", true); con.print("DFHack is ready. Have a nice day!\n" "Type in '?' or 'help' for general help, 'ls' to see all commands.\n"); @@ -582,7 +685,10 @@ void fIOthread(void * iodata) main_history.save("dfhack.history"); } - runInteractiveCommand(core, plug_mgr, clueless_counter, command); + auto rv = core->runCommand(con, command); + + if (rv == CR_NOT_IMPLEMENTED) + clueless_counter++; if(clueless_counter == 3) { @@ -636,6 +742,15 @@ void Core::fatal (std::string output, bool deactivate) #endif } +std::string Core::getHackPath() +{ +#ifdef LINUX_BUILD + return p->getPath() + "/hack/"; +#else + return p->getPath() + "\\hack\\"; +#endif +} + bool Core::Init() { if(started) @@ -963,6 +1078,9 @@ int Core::Update() // notify all the plugins that a game tick is finished plug_mgr->OnUpdate(out); + // process timers in lua + Lua::Core::onUpdate(out); + // Release the fake suspend lock { lock_guard lock(d->AccessMutex); diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index 05988e419..7f0bacc9e 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -42,13 +42,14 @@ using namespace DFHack; void *type_identity::do_allocate_pod() { - void *p = malloc(size); - memset(p, 0, size); + size_t sz = byte_size(); + void *p = malloc(sz); + memset(p, 0, sz); return p; } void type_identity::do_copy_pod(void *tgt, const void *src) { - memmove(tgt, src, size); + memmove(tgt, src, byte_size()); }; bool type_identity::do_destroy_pod(void *obj) { @@ -81,8 +82,9 @@ bool type_identity::destroy(void *obj) { } void *enum_identity::do_allocate() { - void *p = malloc(byte_size()); - memcpy(p, &first_item_value, std::min(byte_size(), sizeof(int64_t))); + size_t sz = byte_size(); + void *p = malloc(sz); + memcpy(p, &first_item_value, std::min(sz, sizeof(int64_t))); return p; } @@ -96,7 +98,7 @@ std::vector compound_identity::top_scope; compound_identity::compound_identity(size_t size, TAllocateFn alloc, compound_identity *scope_parent, const char *dfhack_name) - : constructed_identity(size, alloc), scope_parent(scope_parent), dfhack_name(dfhack_name) + : constructed_identity(size, alloc), dfhack_name(dfhack_name), scope_parent(scope_parent) { next = list; list = this; } @@ -144,8 +146,8 @@ enum_identity::enum_identity(size_t size, const char *const *keys, const void *attrs, struct_identity *attr_type) : compound_identity(size, NULL, scope_parent, dfhack_name), - first_item_value(first_item_value), last_item_value(last_item_value), - keys(keys), base_type(base_type), attrs(attrs), attr_type(attr_type) + keys(keys), first_item_value(first_item_value), last_item_value(last_item_value), + base_type(base_type), attrs(attrs), attr_type(attr_type) { } @@ -230,15 +232,26 @@ void virtual_identity::doInit(Core *core) known[vtable_ptr] = this; } +virtual_identity *virtual_identity::find(const std::string &name) +{ + auto name_it = name_lookup.find(name); + + return (name_it != name_lookup.end()) ? name_it->second : NULL; +} + virtual_identity *virtual_identity::get(virtual_ptr instance_ptr) { if (!instance_ptr) return NULL; + return find(get_vtable(instance_ptr)); +} + +virtual_identity *virtual_identity::find(void *vtable) +{ // Actually, a reader/writer lock would be sufficient, // since the table is only written once per class. tthread::lock_guard lock(*known_mutex); - void *vtable = get_vtable(instance_ptr); std::map::iterator it = known.find(vtable); if (it != known.end()) @@ -380,7 +393,7 @@ int DFHack::findEnumItem(const std::string &name, int size, const char *const *i void DFHack::flagarrayToString(std::vector *pvec, const void *p, int bytes, int base, int size, const char *const *items) { - for (unsigned i = 0; i < bytes*8; i++) { + for (int i = 0; i < bytes*8; i++) { int value = getBitfieldField(p, i, 1); if (value) diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index a34938f95..79aa3bcf1 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -28,6 +28,7 @@ namespace df { NUMBER_IDENTITY_TRAITS(int64_t); NUMBER_IDENTITY_TRAITS(uint64_t); NUMBER_IDENTITY_TRAITS(float); + NUMBER_IDENTITY_TRAITS(double); bool_identity identity_traits::identity; stl_string_identity identity_traits::identity; diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 5f2195760..d554754e4 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -46,6 +46,9 @@ distribution. #include "modules/Materials.h" #include "modules/Maps.h" #include "modules/MapCache.h" +#include "modules/Burrows.h" +#include "modules/Buildings.h" +#include "modules/Constructions.h" #include "LuaWrapper.h" #include "LuaTools.h" @@ -58,14 +61,20 @@ distribution. #include "df/unit.h" #include "df/item.h" #include "df/material.h" +#include "df/assumed_identity.h" #include "df/nemesis_record.h" #include "df/historical_figure.h" +#include "df/historical_entity.h" +#include "df/entity_position.h" +#include "df/entity_position_assignment.h" +#include "df/histfig_entity_link_positionst.h" #include "df/plant_raw.h" #include "df/creature_raw.h" #include "df/inorganic_raw.h" #include "df/dfhack_material_category.h" #include "df/job_material_category.h" #include "df/burrow.h" +#include "df/building_civzonest.h" #include #include @@ -74,6 +83,17 @@ distribution. using namespace DFHack; using namespace DFHack::LuaWrapper; +void Lua::Push(lua_State *state, const Units::NoblePosition &pos) +{ + lua_createtable(state, 0, 3); + Lua::PushDFObject(state, pos.entity); + lua_setfield(state, -2, "entity"); + Lua::PushDFObject(state, pos.assignment); + lua_setfield(state, -2, "assignment"); + Lua::PushDFObject(state, pos.position); + lua_setfield(state, -2, "position"); +} + int Lua::PushPosXYZ(lua_State *state, df::coord pos) { if (!pos.isValid()) @@ -90,6 +110,37 @@ int Lua::PushPosXYZ(lua_State *state, df::coord pos) } } +static df::coord2d CheckCoordXY(lua_State *state, int base, bool vararg = false) +{ + df::coord2d p; + if (vararg && lua_gettop(state) <= base) + Lua::CheckDFAssign(state, &p, base); + else + { + p = df::coord2d( + luaL_checkint(state, base), + luaL_checkint(state, base+1) + ); + } + return p; +} + +static df::coord CheckCoordXYZ(lua_State *state, int base, bool vararg = false) +{ + df::coord p; + if (vararg && lua_gettop(state) <= base) + Lua::CheckDFAssign(state, &p, base); + else + { + p = df::coord( + luaL_checkint(state, base), + luaL_checkint(state, base+1), + luaL_checkint(state, base+2) + ); + } + return p; +} + /************************************************** * Per-world persistent configuration storage API * **************************************************/ @@ -353,6 +404,7 @@ static void push_matinfo(lua_State *state, MaterialInfo &info) case MaterialInfo::Plant: id = "plant"; break; case MaterialInfo::Creature: id = "creature"; break; case MaterialInfo::Inorganic: id = "inorganic"; break; + default: break; } lua_pushstring(state, id); @@ -500,8 +552,7 @@ static int dfhack_matinfo_matches(lua_State *state) else if (lua_istable(state, 2)) { df::dfhack_material_category tmp; - if (!Lua::AssignDFObject(*Lua::GetOutput(state), state, &tmp, 2, false)) - lua_error(state); + Lua::CheckDFAssign(state, &tmp, 2, false); lua_pushboolean(state, info.matches(tmp)); } else @@ -551,11 +602,20 @@ static void OpenModule(lua_State *state, const char *mname, #define WRAP(function) { #function, df::wrap_function(function,true) } #define WRAPN(name, function) { #name, df::wrap_function(function,true) } +/***** DFHack module *****/ + +static bool isWorldLoaded() { return Core::getInstance().isWorldLoaded(); } +static bool isMapLoaded() { return Core::getInstance().isMapLoaded(); } + static const LuaWrapper::FunctionReg dfhack_module[] = { + WRAP(isWorldLoaded), + WRAP(isMapLoaded), WRAPM(Translation, TranslateName), { NULL, NULL } }; +/***** Gui module *****/ + static const LuaWrapper::FunctionReg dfhack_gui_module[] = { WRAPM(Gui, getSelectedWorkshopJob), WRAPM(Gui, getSelectedJob), @@ -566,6 +626,8 @@ static const LuaWrapper::FunctionReg dfhack_gui_module[] = { { NULL, NULL } }; +/***** Job module *****/ + static bool jobEqual(df::job *job1, df::job *job2) { return *job1 == *job2; } static bool jobItemEqual(df::job_item *job1, df::job_item *job2) { return *job1 == *job2; } @@ -602,18 +664,20 @@ static const luaL_Reg dfhack_job_funcs[] = { { NULL, NULL } }; +/***** Units module *****/ static const LuaWrapper::FunctionReg dfhack_units_module[] = { WRAPM(Units, getContainer), WRAPM(Units, setNickname), WRAPM(Units, getVisibleName), + WRAPM(Units, getIdentity), WRAPM(Units, getNemesis), WRAPM(Units, isDead), WRAPM(Units, isAlive), WRAPM(Units, isSane), - WRAPM(Units, clearBurrowMembers), - WRAPM(Units, isInBurrow), - WRAPM(Units, setInBurrow), + WRAPM(Units, getAge), + WRAPM(Units, getProfessionName), + WRAPM(Units, getCasteProfessionName), { NULL, NULL } }; @@ -622,11 +686,26 @@ static int units_getPosition(lua_State *state) return Lua::PushPosXYZ(state, Units::getPosition(Lua::CheckDFObject(state,1))); } +static int units_getNoblePositions(lua_State *state) +{ + std::vector np; + + if (Units::getNoblePositions(&np, Lua::CheckDFObject(state,1))) + Lua::PushVector(state, np); + else + lua_pushnil(state); + + return 1; +} + static const luaL_Reg dfhack_units_funcs[] = { { "getPosition", units_getPosition }, + { "getNoblePositions", units_getNoblePositions }, { NULL, NULL } }; +/***** Items module *****/ + static bool items_moveToGround(df::item *item, df::coord pos) { MapExtras::MapCache mc; @@ -640,6 +719,8 @@ static bool items_moveToContainer(df::item *item, df::item *container) } static const LuaWrapper::FunctionReg dfhack_items_module[] = { + WRAPM(Items, getGeneralRef), + WRAPM(Items, getSpecificRef), WRAPM(Items, getOwner), WRAPM(Items, setOwner), WRAPM(Items, getContainer), @@ -667,42 +748,171 @@ static const luaL_Reg dfhack_items_funcs[] = { { NULL, NULL } }; +/***** Maps module *****/ -static bool maps_isBlockBurrowTile(df::burrow *burrow, df::map_block *block, int x, int y) +static const LuaWrapper::FunctionReg dfhack_maps_module[] = { + WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock), + WRAPM(Maps, getRegionBiome), + WRAPM(Maps, getGlobalInitFeature), + WRAPM(Maps, getLocalInitFeature), + WRAPM(Maps, canWalkBetween), + { NULL, NULL } +}; + +static int maps_getTileBlock(lua_State *L) { - return Maps::isBlockBurrowTile(burrow, block, df::coord2d(x,y)); + auto pos = CheckCoordXYZ(L, 1, true); + Lua::PushDFObject(L, Maps::getTileBlock(pos)); + return 1; } -static bool maps_setBlockBurrowTile(df::burrow *burrow, df::map_block *block, int x, int y, bool enable) +static const luaL_Reg dfhack_maps_funcs[] = { + { "getTileBlock", maps_getTileBlock }, + { NULL, NULL } +}; + +/***** Burrows module *****/ + +static bool burrows_isAssignedBlockTile(df::burrow *burrow, df::map_block *block, int x, int y) { - return Maps::setBlockBurrowTile(burrow, block, df::coord2d(x,y), enable); + return Burrows::isAssignedBlockTile(burrow, block, df::coord2d(x,y)); } -static const LuaWrapper::FunctionReg dfhack_maps_module[] = { - WRAPN(getBlock, (df::map_block* (*)(int32_t,int32_t,int32_t))Maps::getBlock), - WRAPN(getTileBlock, (df::map_block* (*)(df::coord))Maps::getTileBlock), - WRAPM(Maps, getRegionBiome), - WRAPM(Maps, getGlobalInitFeature), - WRAPM(Maps, getLocalInitFeature), - WRAPM(Maps, findBurrowByName), - WRAPM(Maps, clearBurrowTiles), - WRAPN(isBlockBurrowTile, maps_isBlockBurrowTile), - WRAPN(setBlockBurrowTile, maps_setBlockBurrowTile), - WRAPM(Maps, isBurrowTile), - WRAPM(Maps, setBurrowTile), +static bool burrows_setAssignedBlockTile(df::burrow *burrow, df::map_block *block, int x, int y, bool enable) +{ + return Burrows::setAssignedBlockTile(burrow, block, df::coord2d(x,y), enable); +} + +static const LuaWrapper::FunctionReg dfhack_burrows_module[] = { + WRAPM(Burrows, findByName), + WRAPM(Burrows, clearUnits), + WRAPM(Burrows, isAssignedUnit), + WRAPM(Burrows, setAssignedUnit), + WRAPM(Burrows, clearTiles), + WRAPN(isAssignedBlockTile, burrows_isAssignedBlockTile), + WRAPN(setAssignedBlockTile, burrows_setAssignedBlockTile), + WRAPM(Burrows, isAssignedTile), + WRAPM(Burrows, setAssignedTile), { NULL, NULL } }; -static int maps_listBurrowBlocks(lua_State *state) +static int burrows_listBlocks(lua_State *state) { std::vector pvec; - Maps::listBurrowBlocks(&pvec, Lua::CheckDFObject(state,1)); + Burrows::listBlocks(&pvec, Lua::CheckDFObject(state,1)); Lua::PushVector(state, pvec); return 1; } -static const luaL_Reg dfhack_maps_funcs[] = { - { "listBurrowBlocks", maps_listBurrowBlocks }, +static const luaL_Reg dfhack_burrows_funcs[] = { + { "listBlocks", burrows_listBlocks }, + { NULL, NULL } +}; + +/***** Buildings module *****/ + +static bool buildings_containsTile(df::building *bld, int x, int y, bool room) { + return Buildings::containsTile(bld, df::coord2d(x,y), room); +} + +static const LuaWrapper::FunctionReg dfhack_buildings_module[] = { + WRAPM(Buildings, allocInstance), + WRAPM(Buildings, checkFreeTiles), + WRAPM(Buildings, countExtentTiles), + WRAPN(containsTile, buildings_containsTile), + WRAPM(Buildings, hasSupport), + WRAPM(Buildings, constructAbstract), + WRAPM(Buildings, constructWithItems), + WRAPM(Buildings, constructWithFilters), + WRAPM(Buildings, deconstruct), + { NULL, NULL } +}; + +static int buildings_findAtTile(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + Lua::PushDFObject(L, Buildings::findAtTile(pos)); + return 1; +} + +static int buildings_findCivzonesAt(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + std::vector pvec; + if (Buildings::findCivzonesAt(&pvec, pos)) + Lua::PushVector(L, pvec); + else + lua_pushnil(L); + return 1; +} + +static int buildings_getCorrectSize(lua_State *state) +{ + df::coord2d size(luaL_optint(state, 1, 1), luaL_optint(state, 2, 1)); + + auto t = (df::building_type)luaL_optint(state, 3, -1); + int st = luaL_optint(state, 4, -1); + int cu = luaL_optint(state, 5, -1); + int d = luaL_optint(state, 6, 0); + + df::coord2d center; + bool flexible = Buildings::getCorrectSize(size, center, t, st, cu, d); + + lua_pushboolean(state, flexible); + lua_pushinteger(state, size.x); + lua_pushinteger(state, size.y); + lua_pushinteger(state, center.x); + lua_pushinteger(state, center.y); + return 5; +} + +static int buildings_setSize(lua_State *state) +{ + auto bld = Lua::CheckDFObject(state, 1); + df::coord2d size(luaL_optint(state, 2, 1), luaL_optint(state, 3, 1)); + int dir = luaL_optint(state, 4, 0); + bool ok = Buildings::setSize(bld, size, dir); + lua_pushboolean(state, ok); + if (ok) + { + auto size = Buildings::getSize(bld).second; + int area = size.x*size.y; + lua_pushinteger(state, size.x); + lua_pushinteger(state, size.y); + lua_pushinteger(state, area); + lua_pushinteger(state, Buildings::countExtentTiles(&bld->room, area)); + return 5; + } + else + return 1; +} + +static const luaL_Reg dfhack_buildings_funcs[] = { + { "findAtTile", buildings_findAtTile }, + { "findCivzonesAt", buildings_findCivzonesAt }, + { "getCorrectSize", buildings_getCorrectSize }, + { "setSize", buildings_setSize }, + { NULL, NULL } +}; + +/***** Constructions module *****/ + +static const LuaWrapper::FunctionReg dfhack_constructions_module[] = { + WRAPM(Constructions, designateNew), + { NULL, NULL } +}; + +static int constructions_designateRemove(lua_State *L) +{ + auto pos = CheckCoordXYZ(L, 1, true); + bool imm = false; + lua_pushboolean(L, Constructions::designateRemove(pos, &imm)); + lua_pushboolean(L, imm); + return 2; +} + +static const luaL_Reg dfhack_constructions_funcs[] = { + { "designateRemove", constructions_designateRemove }, { NULL, NULL } }; @@ -722,4 +932,7 @@ void OpenDFHackApi(lua_State *state) OpenModule(state, "units", dfhack_units_module, dfhack_units_funcs); OpenModule(state, "items", dfhack_items_module, dfhack_items_funcs); OpenModule(state, "maps", dfhack_maps_module, dfhack_maps_funcs); + OpenModule(state, "burrows", dfhack_burrows_module, dfhack_burrows_funcs); + OpenModule(state, "buildings", dfhack_buildings_module, dfhack_buildings_funcs); + OpenModule(state, "constructions", dfhack_constructions_module); } diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 469caa65f..794cb52a6 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -53,6 +53,7 @@ distribution. #include "df/building.h" #include "df/unit.h" #include "df/item.h" +#include "df/world.h" #include #include @@ -84,7 +85,7 @@ void *DFHack::Lua::GetDFObject(lua_State *state, type_identity *type, int val_in return get_object_internal(state, type, val_index, exact_type, false); } -void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type) +static void check_valid_ptr_index(lua_State *state, int val_index) { if (lua_type(state, val_index) == LUA_TNONE) { @@ -93,23 +94,47 @@ void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_ else luaL_error(state, "at index %d: pointer expected", val_index); } +} - if (lua_isnil(state, val_index)) - return NULL; +static void dfhack_printerr(lua_State *S, const std::string &str); - void *rv = get_object_internal(state, type, val_index, exact_type, false); +static void signal_typeid_error(color_ostream *out, lua_State *state, + type_identity *type, const char *msg, + int val_index, bool perr, bool signal) +{ + std::string error = stl_sprintf(msg, type->getFullName().c_str()); - if (!rv) + if (signal) { - std::string error = "invalid pointer type"; - if (type) - error += "; expected: " + type->getFullName(); - if (val_index > 0) luaL_argerror(state, val_index, error.c_str()); else luaL_error(state, "at index %d: %s", val_index, error.c_str()); } + else if (perr) + { + if (out) + out->printerr("%s", error.c_str()); + else + dfhack_printerr(state, error); + } + else + lua_pushstring(state, error.c_str()); +} + + +void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type) +{ + check_valid_ptr_index(state, val_index); + + if (lua_isnil(state, val_index)) + return NULL; + + void *rv = get_object_internal(state, type, val_index, exact_type, false); + + if (!rv) + signal_typeid_error(NULL, state, type, "invalid pointer type; expected: %s", + val_index, false, true); return rv; } @@ -162,11 +187,13 @@ static Console *get_console(lua_State *state) return static_cast(pstream); } +static int DFHACK_TOSTRING_TOKEN = 0; + static std::string lua_print_fmt(lua_State *L) { /* Copied from lua source to fully replicate builtin print */ int n = lua_gettop(L); /* number of arguments */ - lua_getglobal(L, "tostring"); + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TOSTRING_TOKEN); std::stringstream ss; @@ -319,7 +346,7 @@ static int DFHACK_EXCEPTION_META_TOKEN = 0; static void error_tostring(lua_State *L, bool keep_old = false) { - lua_getglobal(L, "tostring"); + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TOSTRING_TOKEN); if (keep_old) lua_pushvalue(L, -2); else @@ -341,7 +368,7 @@ static void error_tostring(lua_State *L, bool keep_old = false) } } -static void report_error(lua_State *L, color_ostream *out = NULL) +static void report_error(lua_State *L, color_ostream *out = NULL, bool pop = false) { error_tostring(L, true); @@ -353,7 +380,7 @@ static void report_error(lua_State *L, color_ostream *out = NULL) else dfhack_printerr(L, msg); - lua_pop(L, 1); + lua_pop(L, pop?2:1); } static bool convert_to_exception(lua_State *L, int slevel, lua_State *thread = NULL) @@ -542,10 +569,7 @@ bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres bool ok = lua_pcall(L, nargs, nres, base) == LUA_OK; if (!ok && perr) - { - report_error(L, &out); - lua_pop(L, 1); - } + report_error(L, &out, true); lua_remove(L, base); set_dfhack_output(L, cur_out); @@ -656,10 +680,7 @@ int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, lua_State *thre int rv = resume_helper(from, thread, nargs, nres); if (!Lua::IsSuccess(rv) && perr) - { - report_error(from, &out); - lua_pop(from, 1); - } + report_error(from, &out, true); set_dfhack_output(from, cur_out); @@ -682,6 +703,19 @@ int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, int nargs, int */ static int DFHACK_LOADED_TOKEN = 0; +static int DFHACK_DFHACK_TOKEN = 0; +static int DFHACK_BASE_G_TOKEN = 0; +static int DFHACK_REQUIRE_TOKEN = 0; + +void Lua::PushDFHack(lua_State *state) +{ + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); +} + +void Lua::PushBaseGlobals(lua_State *state) +{ + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN); +} bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module) { @@ -699,7 +733,7 @@ bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *m } lua_pop(state, 2); - lua_getglobal(state, "require"); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_REQUIRE_TOKEN); lua_pushstring(state, module); return Lua::SafeCall(out, state, 1, 1); @@ -730,21 +764,75 @@ bool DFHack::Lua::Require(color_ostream &out, lua_State *state, return false; if (setglobal) - lua_setglobal(state, module.c_str()); + { + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN); + lua_swap(state); + lua_setfield(state, -2, module.c_str()); + } else lua_pop(state, 1); return true; } +static bool doAssignDFObject(color_ostream *out, lua_State *state, + type_identity *type, void *target, int val_index, + bool exact, bool perr, bool signal) +{ + if (signal) + check_valid_ptr_index(state, val_index); + + if (lua_istable(state, val_index)) + { + val_index = lua_absindex(state, val_index); + lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME); + Lua::PushDFObject(state, type, target); + lua_pushvalue(state, val_index); + + if (signal) + { + lua_call(state, 2, 0); + return true; + } + else + return Lua::SafeCall(*out, state, 2, 0, perr); + } + else if (!lua_isuserdata(state, val_index)) + { + signal_typeid_error(out, state, type, "pointer to %s expected", + val_index, perr, signal); + return false; + } + else + { + void *in_ptr = Lua::GetDFObject(state, type, val_index, exact); + if (!in_ptr) + { + signal_typeid_error(out, state, type, "incompatible pointer type: %s expected", + val_index, perr, signal); + return false; + } + if (!type->copy(target, in_ptr)) + { + signal_typeid_error(out, state, type, "no copy support for %s", + val_index, perr, signal); + return false; + } + return true; + } +} + bool DFHack::Lua::AssignDFObject(color_ostream &out, lua_State *state, - type_identity *type, void *target, int val_index, bool perr) + type_identity *type, void *target, int val_index, + bool exact_type, bool perr) { - val_index = lua_absindex(state, val_index); - lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME); - PushDFObject(state, type, target); - lua_pushvalue(state, val_index); - return Lua::SafeCall(out, state, 2, 0, perr); + return doAssignDFObject(&out, state, type, target, val_index, exact_type, perr, false); +} + +void DFHack::Lua::CheckDFAssign(lua_State *state, type_identity *type, + void *target, int val_index, bool exact_type) +{ + doAssignDFObject(NULL, state, type, target, val_index, exact_type, false, true); } bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std::string &code, @@ -764,10 +852,7 @@ bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std if (luaL_loadbuffer(state, code.data(), code.size(), debug_tag) != LUA_OK) { if (perr) - { - report_error(state, &out); - lua_pop(state, 1); - } + report_error(state, &out, true); return false; } @@ -810,13 +895,9 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, bool (*init)(color_ostream&, lua_State*, void*), void *arg) { - if (!out.is_console()) - return false; if (!lua_checkstack(state, 20)) return false; - Console &con = static_cast(out); - lua_State *thread; int rv; std::string prompt; @@ -836,6 +917,10 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, return false; } + // If not interactive, run without coroutine and bail out + if (!out.is_console()) + return SafeCall(out, state, lua_gettop(state)-base-1, 0); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN); lua_pushvalue(state, base+1); lua_remove(state, base+1); @@ -846,6 +931,8 @@ bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state, rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile); } + Console &con = static_cast(out); + while (rv == LUA_YIELD) { if (histfile != histname) @@ -900,7 +987,7 @@ namespace { static bool init_interpreter(color_ostream &out, lua_State *state, void *info) { auto args = (InterpreterArgs*)info; - lua_getglobal(state, "dfhack"); + lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); lua_getfield(state, -1, "interpreter"); lua_remove(state, -2); lua_pushstring(state, args->prompt); @@ -1104,10 +1191,7 @@ static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfu lua_pushvalue(L, argbase+i); if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK) - { - report_error(L); - lua_pop(L, 1); - } + report_error(L, NULL, true); } static void dfhack_event_invoke(lua_State *L, int base, bool from_c) @@ -1252,9 +1336,22 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_pushcfunction(state, lua_dfhack_println); lua_setglobal(state, "print"); + lua_getglobal(state, "require"); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_REQUIRE_TOKEN); + lua_getglobal(state, "tostring"); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_TOSTRING_TOKEN); + // Create the dfhack global lua_newtable(state); + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); + + lua_rawgeti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); + lua_dup(state); + lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN); + lua_setfield(state, -2, "BASE_G"); + lua_pushboolean(state, IsCoreContext(state)); lua_setfield(state, -2, "is_core_context"); @@ -1295,6 +1392,17 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) luaL_setfuncs(state, dfhack_coro_funcs, 0); lua_pop(state, 1); + // split the global environment + lua_newtable(state); + lua_newtable(state); + lua_rawgeti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); + lua_setfield(state, -2, "__index"); + lua_setmetatable(state, -2); + lua_dup(state); + lua_setglobal(state, "_G"); + lua_dup(state); + lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS); + // load dfhack.lua Require(out, state, "dfhack"); @@ -1305,13 +1413,135 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) return state; } +static int next_timeout_id = 0; +static int frame_idx = 0; +static std::multimap frame_timers; +static std::multimap tick_timers; + +int DFHACK_TIMEOUTS_TOKEN = 0; + +static const char *const timeout_modes[] = { + "frames", "ticks", "days", "months", "years", NULL +}; + +int dfhack_timeout(lua_State *L) +{ + using df::global::world; + using df::global::enabler; + + lua_Number time = luaL_checknumber(L, 1); + int mode = luaL_checkoption(L, 2, NULL, timeout_modes); + luaL_checktype(L, 3, LUA_TFUNCTION); + lua_settop(L, 3); + + if (mode > 0 && !Core::getInstance().isWorldLoaded()) + { + lua_pushnil(L); + return 1; + } + + switch (mode) + { + case 2: + time *= 1200; + break; + case 3: + time *= 33600; + break; + case 4: + time *= 403200; + break; + default:; + } + + int id = next_timeout_id++; + int delta = time; + + if (delta <= 0) + luaL_error(L, "Invalid timeout: %d", delta); + + if (mode) + tick_timers.insert(std::pair(world->frame_counter+delta, id)); + else + frame_timers.insert(std::pair(frame_idx+delta, id)); + + lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); + lua_swap(L); + lua_rawseti(L, -2, id); + + lua_pushinteger(L, id); + return 1; +} + +static void cancel_tick_timers() +{ + using Lua::Core::State; + + Lua::StackUnwinder frame(State); + lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); + + for (auto it = tick_timers.begin(); it != tick_timers.end(); ++it) + { + lua_pushnil(State); + lua_rawseti(State, frame[1], it->second); + } + + tick_timers.clear(); +} + void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) { if (!State) return; + switch (code) + { + case SC_MAP_UNLOADED: + case SC_WORLD_UNLOADED: + cancel_tick_timers(); + break; + + default:; + } + Lua::Push(State, code); Lua::InvokeEvent(out, State, (void*)onStateChange, 1); } +void DFHack::Lua::Core::onUpdate(color_ostream &out) +{ + using df::global::world; + + Lua::StackUnwinder frame(State); + lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); + + frame_idx++; + + while (!frame_timers.empty() && + frame_timers.begin()->first <= frame_idx) + { + int id = frame_timers.begin()->second; + frame_timers.erase(frame_timers.begin()); + + lua_rawgeti(State, frame[1], id); + lua_pushnil(State); + lua_rawseti(State, frame[1], id); + + Lua::SafeCall(out, State, 0, 0); + } + + while (!tick_timers.empty() && + tick_timers.begin()->first <= world->frame_counter) + { + int id = tick_timers.begin()->second; + tick_timers.erase(tick_timers.begin()); + + lua_rawgeti(State, frame[1], id); + lua_pushnil(State); + lua_rawseti(State, frame[1], id); + + Lua::SafeCall(out, State, 0, 0); + } +} + void DFHack::Lua::Core::Init(color_ostream &out) { if (State) @@ -1321,12 +1551,18 @@ void DFHack::Lua::Core::Init(color_ostream &out) Lua::Open(out, State); + lua_newtable(State); + lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN); + // Register events - lua_getglobal(State, "dfhack"); + lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN); MakeEvent(State, (void*)onStateChange); lua_setfield(State, -2, "onStateChange"); + lua_pushcfunction(State, dfhack_timeout); + lua_setfield(State, -2, "timeout"); + lua_pop(State, 1); } diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 2cfe8a84e..c58c25d11 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -576,35 +576,6 @@ static void write_field(lua_State *state, const struct_field_info *field, void * } } -/** - * Metamethod: represent a type node as string. - */ -static int meta_type_tostring(lua_State *state) -{ - if (!lua_getmetatable(state, 1)) - return 0; - - lua_getfield(state, -1, "__metatable"); - const char *cname = lua_tostring(state, -1); - - lua_pushstring(state, stl_sprintf("", cname).c_str()); - return 1; -} - -/** - * Metamethod: represent a DF object reference as string. - */ -static int meta_ptr_tostring(lua_State *state) -{ - uint8_t *ptr = get_object_addr(state, 1, 0, "access"); - - lua_getfield(state, UPVAL_METATABLE, "__metatable"); - const char *cname = lua_tostring(state, -1); - - lua_pushstring(state, stl_sprintf("<%s: 0x%08x>", cname, (unsigned)ptr).c_str()); - return 1; -} - /** * Metamethod: __index for structures. */ @@ -1058,6 +1029,11 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?"); field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); } + catch (Error::InvalidArgument &e) { + const char *vn = e.expr(); + std::string tmp = stl_sprintf("Invalid argument; expected: %s", vn ? vn : "?"); + field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); + } catch (std::exception &e) { std::string tmp = stl_sprintf("C++ exception: %s", e.what()); field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke"); @@ -1127,26 +1103,39 @@ static void IndexFields(lua_State *state, int base, struct_identity *pstruct, bo name = pstruct->getName() + ("." + name); lua_pop(state, 1); + bool add_to_enum = true; + // Handle the field switch (fields[i].mode) { case struct_field_info::OBJ_METHOD: AddMethodWrapper(state, base+1, base+2, name.c_str(), (function_identity_base*)fields[i].type); - break; + continue; case struct_field_info::CLASS_METHOD: + continue; + + case struct_field_info::POINTER: + // Skip class-typed pointers within unions + if ((fields[i].count & 2) != 0 && fields[i].type && + fields[i].type->type() == IDTYPE_CLASS) + add_to_enum = false; break; default: - // Do not add invalid globals to the enumeration order - if (!globals || *(void**)fields[i].offset) - AssociateId(state, base+3, ++cnt, name.c_str()); - - lua_pushlightuserdata(state, (void*)&fields[i]); - lua_setfield(state, base+2, name.c_str()); break; } + + // Do not add invalid globals to the enumeration order + if (globals && !*(void**)fields[i].offset) + add_to_enum = false; + + if (add_to_enum) + AssociateId(state, base+3, ++cnt, name.c_str()); + + lua_pushlightuserdata(state, (void*)&fields[i]); + lua_setfield(state, base+2, name.c_str()); } } diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 1c3b61c15..0b958f37c 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -542,10 +542,23 @@ static int meta_sizeof(lua_State *state) return 2; } - type_identity *id = get_object_identity(state, 1, "df.sizeof()", true); + type_identity *id = get_object_identity(state, 1, "df.sizeof()", true, true); - lua_pushinteger(state, id->byte_size()); + // Static arrays need special handling + if (id->type() == IDTYPE_BUFFER) + { + auto buf = (df::buffer_container_identity*)id; + type_identity *item = buf->getItemType(); + int count = buf->getSize(); + + fetch_container_details(state, lua_gettop(state), &item, &count); + + lua_pushinteger(state, item->byte_size() * count); + } + else + lua_pushinteger(state, id->byte_size()); + // Add the address if (lua_isuserdata(state, 1)) { lua_pushnumber(state, (size_t)get_object_ref(state, 1)); diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index b9ff35cfc..8658788bb 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -41,7 +41,11 @@ distribution. #include const char *DFHack::Error::NullPointer::what() const throw() { - return "NULL pointer access"; + return "DFHack::Error::NullPointer"; +} + +const char *DFHack::Error::InvalidArgument::what() const throw() { + return "DFHack::Error::InvalidArgument"; } std::string stl_sprintf(const char *fmt, ...) { diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index 837c3e2d5..320e8dec8 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -28,6 +28,7 @@ distribution. #include "PluginManager.h" #include "RemoteServer.h" #include "Console.h" +#include "Types.h" #include "DataDefs.h" #include "MiscUtils.h" @@ -45,41 +46,8 @@ using namespace std; #include "tinythread.h" using namespace tthread; -#ifdef LINUX_BUILD - #include - #include -#else - #include "wdirent.h" -#endif - #include -static int getdir (string dir, vector &files) -{ - DIR *dp; - struct dirent *dirp; - if((dp = opendir(dir.c_str())) == NULL) - { - return errno; - } - while ((dirp = readdir(dp)) != NULL) { - files.push_back(string(dirp->d_name)); - } - closedir(dp); - return 0; -} - -bool hasEnding (std::string const &fullString, std::string const &ending) -{ - if (fullString.length() > ending.length()) - { - return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); - } - else - { - return false; - } -} struct Plugin::RefLock { RefLock() @@ -563,10 +531,10 @@ void Plugin::push_function(lua_State *state, LuaFunction *fn) PluginManager::PluginManager(Core * core) { #ifdef LINUX_BUILD - string path = core->p->getPath() + "/hack/plugins/"; + string path = core->getHackPath() + "plugins/"; const string searchstr = ".plug.so"; #else - string path = core->p->getPath() + "\\hack\\plugins\\"; + string path = core->getHackPath() + "plugins\\"; const string searchstr = ".plug.dll"; #endif cmdlist_mutex = new mutex(); @@ -624,7 +592,7 @@ command_result PluginManager::InvokeCommand(color_ostream &out, const std::strin bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top) { Plugin *plugin = getPluginByCommand(command); - return plugin ? plugin->can_invoke_hotkey(command, top) : false; + return plugin ? plugin->can_invoke_hotkey(command, top) : true; } void PluginManager::OnUpdate(color_ostream &out) diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index c8af46cbf..689c783a8 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -216,6 +216,9 @@ void DFHack::describeMaterial(BasicMaterialInfo *info, const MaterialInfo &mat, case MaterialInfo::Plant: info->set_plant_id(mat.index); break; + + default: + break; } } @@ -298,7 +301,7 @@ void DFHack::describeUnit(BasicUnitInfo *info, df::unit *unit, if (mask && mask->labors()) { - for (int i = 0; i < sizeof(unit->status.labors)/sizeof(bool); i++) + for (size_t i = 0; i < sizeof(unit->status.labors)/sizeof(bool); i++) if (unit->status.labors[i]) info->add_labors(i); } @@ -399,7 +402,7 @@ static command_result GetWorldInfo(color_ostream &stream, case GAMETYPE_ADVENTURE_MAIN: out->set_mode(GetWorldInfoOut::MODE_ADVENTURE); - if (auto unit = vector_get(world->units.other[0], 0)) + if (auto unit = vector_get(world->units.active, 0)) out->set_player_unit_id(unit->id); if (!ui_advmode) @@ -630,7 +633,7 @@ static command_result ListSquads(color_ostream &stream, static command_result SetUnitLabors(color_ostream &stream, const SetUnitLaborsIn *in) { - for (size_t i = 0; i < in->change_size(); i++) + for (int i = 0; i < in->change_size(); i++) { auto change = in->change(i); auto unit = df::unit::find(change.unit_id()); @@ -708,7 +711,7 @@ command_result CoreService::RunCommand(color_ostream &stream, for (int i = 0; i < in->arguments_size(); i++) args.push_back(in->arguments(i)); - return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args); + return Core::getInstance().runCommand(stream, cmd, args); } command_result CoreService::CoreSuspend(color_ostream &stream, const EmptyMessage*, IntMessage *cnt) diff --git a/library/TileTypes.cpp b/library/TileTypes.cpp index fa5d99555..5564e5e3f 100644 --- a/library/TileTypes.cpp +++ b/library/TileTypes.cpp @@ -68,6 +68,8 @@ namespace DFHack return tiletype::LavaPillar; case tiletype_material::STONE: return tiletype::StonePillar; + default: + break; } } diff --git a/library/Types.cpp b/library/Types.cpp new file mode 100644 index 000000000..72f8bdb8c --- /dev/null +++ b/library/Types.cpp @@ -0,0 +1,129 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#include "Internal.h" +#include "Export.h" +#include "MiscUtils.h" +#include "Error.h" +#include "Types.h" + +#ifndef LINUX_BUILD + #include + #include "wdirent.h" +#else + #include + #include + #include + #include +#endif + +#include +#include + +#include +#include + + +int DFHack::getdir(std::string dir, std::vector &files) +{ + DIR *dp; + struct dirent *dirp; + if((dp = opendir(dir.c_str())) == NULL) + { + return errno; + } + while ((dirp = readdir(dp)) != NULL) { + files.push_back(std::string(dirp->d_name)); + } + closedir(dp); + return 0; +} + +bool DFHack::hasEnding (std::string const &fullString, std::string const &ending) +{ + if (fullString.length() > ending.length()) + { + return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending)); + } + else + { + return false; + } +} + +df::general_ref *DFHack::findRef(std::vector &vec, df::general_ref_type type) +{ + for (int i = vec.size()-1; i >= 0; i--) + { + df::general_ref *ref = vec[i]; + if (ref->getType() == type) + return ref; + } + + return NULL; +} + +bool DFHack::removeRef(std::vector &vec, df::general_ref_type type, int id) +{ + for (int i = vec.size()-1; i >= 0; i--) + { + df::general_ref *ref = vec[i]; + if (ref->getType() != type || ref->getID() != id) + continue; + + vector_erase_at(vec, i); + delete ref; + return true; + } + + return false; +} + +df::specific_ref *DFHack::findRef(std::vector &vec, df::specific_ref_type type) +{ + for (int i = vec.size()-1; i >= 0; i--) + { + df::specific_ref *ref = vec[i]; + if (ref->type == type) + return ref; + } + + return NULL; +} + +bool DFHack::removeRef(std::vector &vec, df::specific_ref_type type, void *ptr) +{ + for (int i = vec.size()-1; i >= 0; i--) + { + df::specific_ref *ref = vec[i]; + if (ref->type != type || ref->object != ptr) + continue; + + vector_erase_at(vec, i); + delete ref; + return true; + } + + return false; +} diff --git a/library/include/Core.h b/library/include/Core.h index 4f9b10c84..fe83715cf 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -35,6 +35,8 @@ distribution. #include "modules/Graphic.h" #include "SDL_events.h" +#include "RemoteClient.h" + struct WINDOW; namespace tthread @@ -117,10 +119,16 @@ namespace DFHack /// returns a named pointer. void *GetData(std::string key); + command_result runCommand(color_ostream &out, const std::string &command, std::vector ¶meters); + command_result runCommand(color_ostream &out, const std::string &command); + bool loadScriptFile(color_ostream &out, std::string fname, bool silent = false); + bool ClearKeyBindings(std::string keyspec); bool AddKeyBinding(std::string keyspec, std::string cmdline); std::vector ListKeyBindings(std::string keyspec); + std::string getHackPath(); + bool isWorldLoaded() { return (last_world_data_ptr != NULL); } bool isMapLoaded() { return (last_local_map_ptr != NULL && last_world_data_ptr != NULL); } @@ -177,7 +185,7 @@ namespace DFHack } s_mods; std::vector allModules; DFHack::PluginManager * plug_mgr; - + // hotkey-related stuff struct KeyBinding { int modifiers; diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index d4d757d94..57773e898 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -83,7 +83,7 @@ namespace DFHack public: virtual ~type_identity() {} - size_t byte_size() { return size; } + virtual size_t byte_size() { return size; } virtual identity_type type() = 0; @@ -318,6 +318,9 @@ namespace DFHack public: static virtual_identity *get(virtual_ptr instance_ptr); + static virtual_identity *find(void *vtable); + static virtual_identity *find(const std::string &name); + bool is_instance(virtual_ptr instance_ptr) { if (!instance_ptr) return false; if (vtable_ptr) { @@ -372,14 +375,14 @@ namespace DFHack template int linear_index(const DFHack::enum_list_attr &lst, T val) { - for (int i = 0; i < lst.size; i++) + for (size_t i = 0; i < lst.size; i++) if (lst.items[i] == val) return i; return -1; } inline int linear_index(const DFHack::enum_list_attr &lst, const std::string &val) { - for (int i = 0; i < lst.size; i++) + for (size_t i = 0; i < lst.size; i++) if (lst.items[i] == val) return i; return -1; diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index a775c85b2..dcd0ae979 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -289,9 +289,11 @@ namespace df {} buffer_container_identity(int size, type_identity *item, enum_identity *ienum = NULL) - : container_identity(item->byte_size()*size, NULL, item, ienum), size(size) + : container_identity(0, NULL, item, ienum), size(size) {} + size_t byte_size() { return getItemType()->byte_size()*size; } + std::string getFullName(type_identity *item); int getSize() { return size; } @@ -445,6 +447,7 @@ namespace df NUMBER_IDENTITY_TRAITS(int64_t); NUMBER_IDENTITY_TRAITS(uint64_t); NUMBER_IDENTITY_TRAITS(float); + NUMBER_IDENTITY_TRAITS(double); template<> struct DFHACK_EXPORT identity_traits { static bool_identity identity; diff --git a/library/include/Error.h b/library/include/Error.h index 0fbc6ba78..448c1a4f2 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -51,6 +51,18 @@ namespace DFHack #define CHECK_NULL_POINTER(var) \ { if (var == NULL) throw DFHack::Error::NullPointer(#var); } + class DFHACK_EXPORT InvalidArgument : public All { + const char *expr_; + public: + InvalidArgument(const char *expr_ = NULL) : expr_(expr_) {} + const char *expr() const { return expr_; } + virtual const char *what() const throw(); + }; + +#define CHECK_INVALID_ARGUMENT(expr) \ + { if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); } + + class DFHACK_EXPORT AllSymbols : public All{}; // Syntax errors and whatnot, the xml can't be read class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols diff --git a/library/include/LuaTools.h b/library/include/LuaTools.h index b2d440a6b..c3a7921c3 100644 --- a/library/include/LuaTools.h +++ b/library/include/LuaTools.h @@ -36,6 +36,10 @@ distribution. namespace DFHack { class function_identity_base; + + namespace Units { + struct NoblePosition; + } } namespace DFHack {namespace Lua { @@ -44,6 +48,9 @@ namespace DFHack {namespace Lua { */ DFHACK_EXPORT lua_State *Open(color_ostream &out, lua_State *state = NULL); + DFHACK_EXPORT void PushDFHack(lua_State *state); + DFHACK_EXPORT void PushBaseGlobals(lua_State *state); + /** * Load a module using require(). Leaves the stack as is. */ @@ -105,7 +112,15 @@ namespace DFHack {namespace Lua { * Return behavior is of SafeCall below. */ DFHACK_EXPORT bool AssignDFObject(color_ostream &out, lua_State *state, - type_identity *type, void *target, int val_index, bool perr = true); + type_identity *type, void *target, int val_index, + bool exact_type = false, bool perr = true); + + /** + * Assign the value at val_index to the target of given identity using df.assign(). + * Otherwise throws an error. + */ + DFHACK_EXPORT void CheckDFAssign(lua_State *state, type_identity *type, + void *target, int val_index, bool exact_type = false); /** * Push the pointer onto the stack as a wrapped DF object of a specific type. @@ -135,8 +150,19 @@ namespace DFHack {namespace Lua { * Assign the value at val_index to the target using df.assign(). */ template - bool AssignDFObject(color_ostream &out, lua_State *state, T *target, int val_index, bool perr = true) { - return AssignDFObject(out, state, df::identity_traits::get(), target, val_index, perr); + bool AssignDFObject(color_ostream &out, lua_State *state, T *target, + int val_index, bool exact_type = false, bool perr = true) { + return AssignDFObject(out, state, df::identity_traits::get(), + target, val_index, exact_type, perr); + } + + /** + * Assign the value at val_index to the target using df.assign(). + * Throws in case of an error. + */ + template + void CheckDFAssign(lua_State *state, T *target, int val_index, bool exact_type = false) { + CheckDFAssign(state, df::identity_traits::get(), target, val_index, exact_type); } /** @@ -243,14 +269,21 @@ namespace DFHack {namespace Lua { } inline void Push(lua_State *state, df::coord &obj) { PushDFObject(state, &obj); } inline void Push(lua_State *state, df::coord2d &obj) { PushDFObject(state, &obj); } + void Push(lua_State *state, const Units::NoblePosition &pos); template inline void Push(lua_State *state, T *ptr) { PushDFObject(state, ptr); } template - void PushVector(lua_State *state, const T &pvec) + void PushVector(lua_State *state, const T &pvec, bool addn = false) { - lua_createtable(state,pvec.size(),0); + lua_createtable(state,pvec.size(), addn?1:0); + + if (addn) + { + lua_pushinteger(state, pvec.size()); + lua_setfield(state, -2, "n"); + } for (size_t i = 0; i < pvec.size(); i++) { @@ -267,6 +300,26 @@ namespace DFHack {namespace Lua { DFHACK_EXPORT void MakeEvent(lua_State *state, void *key); DFHACK_EXPORT void InvokeEvent(color_ostream &out, lua_State *state, void *key, int num_args); + class StackUnwinder { + lua_State *state; + int top; + public: + StackUnwinder(lua_State *state, int bias = 0) : state(state), top(0) { + if (state) top = lua_gettop(state) - bias; + } + ~StackUnwinder() { + if (state) lua_settop(state, top); + } + operator int () { return top; } + int operator+ (int v) { return top + v; } + int operator- (int v) { return top + v; } + int operator[] (int v) { return top + v; } + StackUnwinder &operator += (int v) { top += v; return *this; } + StackUnwinder &operator -= (int v) { top += v; return *this; } + StackUnwinder &operator ++ () { top++; return *this; } + StackUnwinder &operator -- () { top--; return *this; } + }; + /** * Namespace for the common lua interpreter state. * All accesses must be done under CoreSuspender. @@ -280,6 +333,8 @@ namespace DFHack {namespace Lua { // Events signalled by the core void onStateChange(color_ostream &out, int code); + // Signals timers + void onUpdate(color_ostream &out); template inline void Push(T &arg) { Lua::Push(State, arg); } template inline void Push(const T &arg) { Lua::Push(State, arg); } diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index bccbb5e62..65884badc 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -73,7 +73,7 @@ namespace DFHack */ template void flagarray_to_ints(RepeatedField *pf, const BitArray &val) { - for (int i = 0; i < val.size*8; i++) + for (size_t i = 0; i < val.size*8; i++) if (val.is_set(T(i))) pf->Add(i); } diff --git a/library/include/Types.h b/library/include/Types.h index a8b4516cf..1f211649d 100644 --- a/library/include/Types.h +++ b/library/include/Types.h @@ -28,6 +28,10 @@ distribution. #include "Pragma.h" #include "Export.h" +#include "DataDefs.h" +#include "df/general_ref.h" +#include "df/specific_ref.h" + namespace DFHack { struct t_matglossPair @@ -69,4 +73,13 @@ namespace DFHack std::string name; uint32_t xpNxtLvl; }; + + DFHACK_EXPORT int getdir(std::string dir, std::vector &files); + DFHACK_EXPORT bool hasEnding (std::string const &fullString, std::string const &ending); + + DFHACK_EXPORT df::general_ref *findRef(std::vector &vec, df::general_ref_type type); + DFHACK_EXPORT bool removeRef(std::vector &vec, df::general_ref_type type, int id); + + DFHACK_EXPORT df::specific_ref *findRef(std::vector &vec, df::specific_ref_type type); + DFHACK_EXPORT bool removeRef(std::vector &vec, df::specific_ref_type type, void *ptr); }// namespace DFHack \ No newline at end of file diff --git a/library/include/df/custom/block_burrow.methods.inc b/library/include/df/custom/block_burrow.methods.inc index 216a58662..3959fcf35 100644 --- a/library/include/df/custom/block_burrow.methods.inc +++ b/library/include/df/custom/block_burrow.methods.inc @@ -1,26 +1,21 @@ inline bool getassignment( const df::coord2d &xy ) { - return getassignment(xy.x,xy.y); + return tile_bitmask.getassignment(xy); } inline bool getassignment( int x, int y ) { - return (tile_bitmask[y] & (1 << x)); + return tile_bitmask.getassignment(x,y); } inline void setassignment( const df::coord2d &xy, bool bit ) { - return setassignment(xy.x,xy.y, bit); + return tile_bitmask.setassignment(xy, bit); } inline void setassignment( int x, int y, bool bit ) { - if(bit) - tile_bitmask[y] |= (1 << x); - else - tile_bitmask[y] &= ~(1 << x); + return tile_bitmask.setassignment(x, y, bit); } bool has_assignments() { - for (int i = 0; i < 16; i++) - if (tile_bitmask[i]) - return true; - return false; + return tile_bitmask.has_assignments(); } + diff --git a/library/include/df/custom/block_square_event_mineralst.methods.inc b/library/include/df/custom/block_square_event_mineralst.methods.inc index 216a58662..4da4c65be 100644 --- a/library/include/df/custom/block_square_event_mineralst.methods.inc +++ b/library/include/df/custom/block_square_event_mineralst.methods.inc @@ -1,26 +1,20 @@ inline bool getassignment( const df::coord2d &xy ) { - return getassignment(xy.x,xy.y); + return tile_bitmask.getassignment(xy); } inline bool getassignment( int x, int y ) { - return (tile_bitmask[y] & (1 << x)); + return tile_bitmask.getassignment(x,y); } inline void setassignment( const df::coord2d &xy, bool bit ) { - return setassignment(xy.x,xy.y, bit); + return tile_bitmask.setassignment(xy, bit); } inline void setassignment( int x, int y, bool bit ) { - if(bit) - tile_bitmask[y] |= (1 << x); - else - tile_bitmask[y] &= ~(1 << x); + return tile_bitmask.setassignment(x, y, bit); } bool has_assignments() { - for (int i = 0; i < 16; i++) - if (tile_bitmask[i]) - return true; - return false; + return tile_bitmask.has_assignments(); } diff --git a/library/include/df/custom/tile_bitmask.methods.inc b/library/include/df/custom/tile_bitmask.methods.inc new file mode 100644 index 000000000..18b312a08 --- /dev/null +++ b/library/include/df/custom/tile_bitmask.methods.inc @@ -0,0 +1,38 @@ +inline uint16_t &operator[] (int y) +{ + return bits[y]; +} +void clear() +{ + memset(bits,0,sizeof(bits)); +} +void set_all() +{ + memset(bits,0xFF,sizeof(bits)); +} +inline bool getassignment( const df::coord2d &xy ) +{ + return getassignment(xy.x,xy.y); +} +inline bool getassignment( int x, int y ) +{ + return (bits[y] & (1 << x)); +} +inline void setassignment( const df::coord2d &xy, bool bit ) +{ + return setassignment(xy.x,xy.y, bit); +} +inline void setassignment( int x, int y, bool bit ) +{ + if(bit) + bits[y] |= (1 << x); + else + bits[y] &= ~(1 << x); +} +bool has_assignments() +{ + for (int i = 0; i < 16; i++) + if (bits[i]) + return true; + return false; +} diff --git a/library/include/modules/Buildings.h b/library/include/modules/Buildings.h index ec44f7713..6e0a22052 100644 --- a/library/include/modules/Buildings.h +++ b/library/include/modules/Buildings.h @@ -34,6 +34,14 @@ distribution. #include "df/siegeengine_type.h" #include "df/trap_type.h" +namespace df +{ + struct job_item; + struct item; + struct building_extents; + struct building_civzonest; +} + namespace DFHack { namespace Buildings @@ -84,5 +92,86 @@ DFHACK_EXPORT bool Read (const uint32_t index, t_building & building); */ DFHACK_EXPORT bool ReadCustomWorkshopTypes(std::map & btypes); +/** + * Find the building located at the specified tile. + * Does not work on civzones. + */ +DFHACK_EXPORT df::building *findAtTile(df::coord pos); + +/** + * Find civzones located at the specified tile. + */ +DFHACK_EXPORT bool findCivzonesAt(std::vector *pvec, df::coord pos); + +/** + * Allocates a building object using this type and position. + */ +DFHACK_EXPORT df::building *allocInstance(df::coord pos, df::building_type type, int subtype = -1, int custom = -1); + +/** + * Sets size and center to the correct dimensions of the building. + * If part of the original size value was used, returns true. + */ +DFHACK_EXPORT bool getCorrectSize(df::coord2d &size, df::coord2d ¢er, + df::building_type type, int subtype = -1, int custom = -1, + int direction = 0); + +/** + * Checks if the tiles are free to be built upon. + */ +DFHACK_EXPORT bool checkFreeTiles(df::coord pos, df::coord2d size, + df::building_extents *ext = NULL, + bool create_ext = false, bool allow_occupied = false); + +/** + * Returns the number of tiles included by the extent, or defval. + */ +DFHACK_EXPORT int countExtentTiles(df::building_extents *ext, int defval = -1); + +/** + * Checks if the building contains the specified tile. + */ +DFHACK_EXPORT bool containsTile(df::building *bld, df::coord2d tile, bool room = false); + +/** + * Checks if the area has support from the terrain. + */ +DFHACK_EXPORT bool hasSupport(df::coord pos, df::coord2d size); + +/** + * Sets the size of the building, using size and direction as guidance. + * Returns true if the building can be created at its position, using that size. + */ +DFHACK_EXPORT bool setSize(df::building *bld, df::coord2d size, int direction = 0); + +/** + * Decodes the size of the building into pos and size. + */ +DFHACK_EXPORT std::pair getSize(df::building *bld); + +/** + * Constructs an abstract building, i.e. stockpile or civzone. + */ +DFHACK_EXPORT bool constructAbstract(df::building *bld); + +/** + * Initiates construction of the building, using specified items as inputs. + * Returns true if success. + */ +DFHACK_EXPORT bool constructWithItems(df::building *bld, std::vector items); + +/** + * Initiates construction of the building, using specified item filters. + * Assumes immediate ownership of the item objects, and deletes them in case of error. + * Returns true if success. + */ +DFHACK_EXPORT bool constructWithFilters(df::building *bld, std::vector items); + +/** + * Deconstructs or queues deconstruction of a building. + * Returns true if the building has been destroyed instantly. + */ +DFHACK_EXPORT bool deconstruct(df::building *bld); + } } diff --git a/library/include/modules/Burrows.h b/library/include/modules/Burrows.h new file mode 100644 index 000000000..e26a2dc20 --- /dev/null +++ b/library/include/modules/Burrows.h @@ -0,0 +1,78 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once +#include "Export.h" +#include "DataDefs.h" +#include "modules/Maps.h" + +#include + +/** + * \defgroup grp_burrows Burrows module and its types + * @ingroup grp_modules + */ + +namespace df +{ + struct unit; + struct burrow; + struct block_burrow; +} + +namespace DFHack +{ +namespace Burrows +{ + DFHACK_EXPORT df::burrow *findByName(std::string name); + + // Units + DFHACK_EXPORT void clearUnits(df::burrow *burrow); + + DFHACK_EXPORT bool isAssignedUnit(df::burrow *burrow, df::unit *unit); + DFHACK_EXPORT void setAssignedUnit(df::burrow *burrow, df::unit *unit, bool enable); + + // Tiles + DFHACK_EXPORT void clearTiles(df::burrow *burrow); + + DFHACK_EXPORT void listBlocks(std::vector *pvec, df::burrow *burrow); + + DFHACK_EXPORT bool isAssignedBlockTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); + DFHACK_EXPORT bool setAssignedBlockTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); + + inline bool isAssignedTile(df::burrow *burrow, df::coord tile) { + return isAssignedBlockTile(burrow, Maps::getTileBlock(tile), tile); + } + inline bool setAssignedTile(df::burrow *burrow, df::coord tile, bool enable) { + return setAssignedBlockTile(burrow, Maps::getTileBlock(tile), tile, enable); + } + + DFHACK_EXPORT df::block_burrow *getBlockMask(df::burrow *burrow, df::map_block *block, bool create = false); + DFHACK_EXPORT bool deleteBlockMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask); + + inline bool deleteBlockMask(df::burrow *burrow, df::map_block *block) { + return deleteBlockMask(burrow, block, getBlockMask(burrow, block)); + } +} +} diff --git a/library/include/modules/Constructions.h b/library/include/modules/Constructions.h index e76cc7ba1..f8a3e5c61 100644 --- a/library/include/modules/Constructions.h +++ b/library/include/modules/Constructions.h @@ -31,6 +31,8 @@ distribution. #include "Export.h" #include "DataDefs.h" #include "df/construction.h" +#include "df/construction_type.h" +#include "df/item_type.h" /** * \defgroup grp_constructions Construction module parts @@ -57,6 +59,12 @@ DFHACK_EXPORT bool isValid(); DFHACK_EXPORT uint32_t getCount(); DFHACK_EXPORT bool copyConstruction (const int32_t index, t_construction &out); DFHACK_EXPORT df::construction * getConstruction (const int32_t index); + +DFHACK_EXPORT bool designateNew(df::coord pos, df::construction_type type, + df::item_type item = df::item_type::NONE, int mat_index = -1); + +DFHACK_EXPORT bool designateRemove(df::coord pos, bool *immediate = NULL); + } } #endif diff --git a/library/include/modules/Items.h b/library/include/modules/Items.h index e7720fa91..8353d24c3 100644 --- a/library/include/modules/Items.h +++ b/library/include/modules/Items.h @@ -36,6 +36,7 @@ distribution. #include "df/item.h" #include "df/item_type.h" #include "df/general_ref.h" +#include "df/specific_ref.h" namespace df { @@ -126,6 +127,10 @@ DFHACK_EXPORT bool copyItem(df::item * source, dfh_item & target); /// write copied item back to its origin DFHACK_EXPORT bool writeItem(const dfh_item & item); +/// Retrieve refs +DFHACK_EXPORT df::general_ref *getGeneralRef(df::item *item, df::general_ref_type type); +DFHACK_EXPORT df::specific_ref *getSpecificRef(df::item *item, df::specific_ref_type type); + /// Retrieve the owner of the item. DFHACK_EXPORT df::unit *getOwner(df::item *item); /// Set the owner of the item. Pass NULL as unit to remove the owner. diff --git a/library/include/modules/Job.h b/library/include/modules/Job.h index d733c542d..a4e173a8d 100644 --- a/library/include/modules/Job.h +++ b/library/include/modules/Job.h @@ -30,6 +30,9 @@ distribution. #include "Module.h" #include +#include "DataDefs.h" +#include "df/job_item_ref.h" + namespace df { struct job; @@ -58,6 +61,10 @@ namespace DFHack // lists jobs with ids >= *id_var, and sets *id_var = *job_next_id; DFHACK_EXPORT bool listNewlyCreated(std::vector *pvec, int *id_var); + + DFHACK_EXPORT bool attachJobItem(df::job *job, df::item *item, + df::job_item_ref::T_role role, + int filter_idx = -1, int insert_idx = -1); } DFHACK_EXPORT bool operator== (const df::job_item &a, const df::job_item &b); diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 8eac514be..0b4e78b21 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -31,12 +31,17 @@ distribution. #include #include #include "df/map_block.h" +#include "df/tile_bitmask.h" #include "df/block_square_event_mineralst.h" #include "df/construction.h" #include "df/item.h" using namespace DFHack; +namespace df { + struct world_region_details; +} + namespace MapExtras { @@ -50,6 +55,38 @@ inline bool is_valid_tile_coord(df::coord2d p) { return (p.x & ~15) == 0 && (p.y & ~15) == 0; } +class Block; + +class BlockInfo +{ + Block *mblock; + MapCache *parent; + df::map_block *block; + +public: + t_blockmaterials veinmats; + t_blockmaterials basemats; + t_blockmaterials grass; + std::map plants; + + df::feature_init *global_feature; + df::feature_init *local_feature; + + BlockInfo() + : mblock(NULL), parent(NULL), block(NULL), + global_feature(NULL), local_feature(NULL) {} + + void prepare(Block *mblock); + + t_matpair getBaseMaterial(df::tiletype tt, df::coord2d pos); + + static void SquashVeins(df::map_block *mb, t_blockmaterials & materials); + static void SquashFrozenLiquids (df::map_block *mb, tiletypes40d & frozen); + static void SquashRocks (df::map_block *mb, t_blockmaterials & materials, + std::vector< std::vector > * layerassign); + static void SquashGrass(df::map_block *mb, t_blockmaterials &materials); +}; + class DFHACK_EXPORT Block { public: @@ -62,39 +99,81 @@ public: //Arbitrary tag field for flood fills etc. int16_t &tag(df::coord2d p) { + if (!tags) init_tags(); return index_tile(tags, p); } + // Base layer + df::tiletype baseTiletypeAt(df::coord2d p) + { + if (!tiles) init_tiles(); + return index_tile(tiles->base_tiles,p); + } + t_matpair baseMaterialAt(df::coord2d p) + { + if (!basemats) init_tiles(true); + return t_matpair( + index_tile(basemats->mattype,p), + index_tile(basemats->matindex,p) + ); + } + bool isVeinAt(df::coord2d p) + { + using namespace df::enums::tiletype_material; + auto tm = tileMaterial(baseTiletypeAt(p)); + return tm == MINERAL; + } + bool isLayerAt(df::coord2d p) + { + using namespace df::enums::tiletype_material; + auto tm = tileMaterial(baseTiletypeAt(p)); + return tm == STONE || tm == SOIL; + } + int16_t veinMaterialAt(df::coord2d p) { - return index_tile(veinmats,p); + return isVeinAt(p) ? baseMaterialAt(p).mat_index : -1; } - int16_t baseMaterialAt(df::coord2d p) + int16_t layerMaterialAt(df::coord2d p) { - return index_tile(basemats,p); + if (!basemats) init_tiles(true); + return index_tile(basemats->layermat,p); } - df::tiletype BaseTileTypeAt(df::coord2d p) + // Static layer (base + constructions) + df::tiletype staticTiletypeAt(df::coord2d p) { - auto tt = index_tile(contiles,p); - if (tt != tiletype::Void) return tt; - tt = index_tile(icetiles,p); - if (tt != tiletype::Void) return tt; - return index_tile(rawtiles,p); + if (!tiles) init_tiles(); + if (tiles->con_info) + return index_tile(tiles->con_info->tiles,p); + return baseTiletypeAt(p); } - df::tiletype TileTypeAt(df::coord2d p) + t_matpair staticMaterialAt(df::coord2d p) { - return index_tile(rawtiles,p); + if (!basemats) init_tiles(true); + if (tiles->con_info) + return t_matpair( + index_tile(tiles->con_info->mattype,p), + index_tile(tiles->con_info->matindex,p) + ); + return baseMaterialAt(p); } - bool setTiletypeAt(df::coord2d p, df::tiletype tiletype) + bool hasConstructionAt(df::coord2d p) { - if(!valid) return false; - dirty_tiletypes = true; - //printf("setting block %d/%d/%d , %d %d\n",x,y,z, p.x, p.y); - index_tile(rawtiles,p) = tiletype; - return true; + if (!tiles) init_tiles(); + return tiles->con_info && + tiles->con_info->constructed.getassignment(p); } + df::tiletype tiletypeAt(df::coord2d p) + { + if (!block) return tiletype::Void; + if (tiles) + return index_tile(tiles->raw_tiles,p); + return index_tile(block->tiletype,p); + } + bool setTiletypeAt(df::coord2d, df::tiletype tt, bool force = false); + uint16_t temperature1At(df::coord2d p) { return index_tile(temp1,p); @@ -179,29 +258,32 @@ public: bool is_valid() { return valid; } df::map_block *getRaw() { return block; } + MapCache *getParent() { return parent; } + private: friend class MapCache; + friend class BlockInfo; MapCache *parent; df::map_block *block; - static void SquashVeins(df::map_block *mb, t_blockmaterials & materials); - static void SquashFrozenLiquids (df::map_block *mb, tiletypes40d & frozen); - static void SquashConstructions (df::map_block *mb, tiletypes40d & constructions); - static void SquashRocks (df::map_block *mb, t_blockmaterials & materials, - std::vector< std::vector > * layerassign); + int biomeIndexAt(df::coord2d p); bool valid; bool dirty_designations:1; - bool dirty_tiletypes:1; + bool dirty_tiles:1; bool dirty_temperatures:1; bool dirty_blockflags:1; bool dirty_occupancies:1; DFCoord bcoord; - int16_t tags[16][16]; + // Custom tags for floodfill + typedef int16_t T_tags[16]; + T_tags *tags; + void init_tags(); + // On-ground item count info typedef int T_item_counts[16]; T_item_counts *item_counts; void init_item_counts(); @@ -209,30 +291,53 @@ private: bool addItemOnGround(df::item *item); bool removeItemOnGround(df::item *item); - tiletypes40d rawtiles; + struct ConInfo { + df::tile_bitmask constructed; + df::tiletype tiles[16][16]; + t_blockmaterials mattype; + t_blockmaterials matindex; + }; + struct TileInfo { + df::tile_bitmask frozen; + df::tile_bitmask dirty_raw; + df::tiletype raw_tiles[16][16]; + + ConInfo *con_info; + + df::tile_bitmask dirty_base; + df::tiletype base_tiles[16][16]; + + TileInfo(); + ~TileInfo(); + + void init_coninfo(); + }; + struct BasematInfo { + df::tile_bitmask dirty; + t_blockmaterials mattype; + t_blockmaterials matindex; + t_blockmaterials layermat; + + BasematInfo(); + }; + TileInfo *tiles; + BasematInfo *basemats; + void init_tiles(bool basemat = false); + void ParseTiles(TileInfo *tiles); + void ParseBasemats(TileInfo *tiles, BasematInfo *bmats); + designations40d designation; occupancies40d occupancy; t_blockflags blockflags; - t_blockmaterials veinmats; - t_blockmaterials basemats; t_temperatures temp1; t_temperatures temp2; - tiletypes40d contiles; // what's underneath constructions - tiletypes40d icetiles; // what's underneath ice }; class DFHACK_EXPORT MapCache { public: - MapCache() - { - valid = 0; - Maps::getSize(x_bmax, y_bmax, z_max); - x_tmax = x_bmax*16; y_tmax = y_bmax*16; - validgeo = Maps::ReadGeology(&layer_mats, &geoidx); - valid = true; - }; + MapCache(); ~MapCache() { trash(); @@ -251,19 +356,60 @@ class DFHACK_EXPORT MapCache df::tiletype baseTiletypeAt (DFCoord tilecoord) { - Block * b= BlockAtTile(tilecoord); - return b ? b->BaseTileTypeAt(tilecoord) : tiletype::Void; + Block *b = BlockAtTile(tilecoord); + return b ? b->baseTiletypeAt(tilecoord) : tiletype::Void; } + t_matpair baseMaterialAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b ? b->baseMaterialAt(tilecoord) : t_matpair(); + } + int16_t veinMaterialAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b ? b->veinMaterialAt(tilecoord) : -1; + } + int16_t layerMaterialAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b ? b->layerMaterialAt(tilecoord) : -1; + } + bool isVeinAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b && b->isVeinAt(tilecoord); + } + bool isLayerAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b && b->isLayerAt(tilecoord); + } + + df::tiletype staticTiletypeAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b ? b->staticTiletypeAt(tilecoord) : tiletype::Void; + } + t_matpair staticMaterialAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b ? b->staticMaterialAt(tilecoord) : t_matpair(); + } + bool hasConstructionAt (DFCoord tilecoord) + { + Block *b = BlockAtTile(tilecoord); + return b && b->hasConstructionAt(tilecoord); + } + df::tiletype tiletypeAt (DFCoord tilecoord) { - Block * b= BlockAtTile(tilecoord); - return b ? b->TileTypeAt(tilecoord) : tiletype::Void; + Block *b = BlockAtTile(tilecoord); + return b ? b->tiletypeAt(tilecoord) : tiletype::Void; } - bool setTiletypeAt(DFCoord tilecoord, df::tiletype tiletype) + bool setTiletypeAt (DFCoord tilecoord, df::tiletype tt, bool force = false) { - if (Block * b= BlockAtTile(tilecoord)) - return b->setTiletypeAt(tilecoord, tiletype); - return false; + Block *b = BlockAtTile(tilecoord); + return b && b->setTiletypeAt(tilecoord, tt, force); } uint16_t temperature1At (DFCoord tilecoord) @@ -290,17 +436,6 @@ class DFHACK_EXPORT MapCache return false; } - int16_t veinMaterialAt (DFCoord tilecoord) - { - Block * b= BlockAtTile(tilecoord); - return b ? b->veinMaterialAt(tilecoord) : -1; - } - int16_t baseMaterialAt (DFCoord tilecoord) - { - Block * b= BlockAtTile(tilecoord); - return b ? b->baseMaterialAt(tilecoord) : -1; - } - int16_t tagAt(DFCoord tilecoord) { Block * b= BlockAtTile(tilecoord); @@ -311,6 +446,7 @@ class DFHACK_EXPORT MapCache Block * b= BlockAtTile(tilecoord); if (b) b->tag(tilecoord) = val; } + void resetTags(); df::tile_designation designationAt (DFCoord tilecoord) { @@ -378,6 +514,7 @@ class DFHACK_EXPORT MapCache private: friend class Block; + friend class BlockInfo; bool valid; bool validgeo; @@ -387,7 +524,10 @@ private: uint32_t y_tmax; uint32_t z_max; std::vector geoidx; + std::vector default_soil; + std::vector default_stone; std::vector< std::vector > layer_mats; + std::map region_details; std::map blocks; }; } diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index b7341f87c..fc18ca7e1 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -241,6 +241,20 @@ extern DFHACK_EXPORT df::map_block * getTileBlock (int32_t x, int32_t y, int32_t inline df::map_block * getBlock (df::coord pos) { return getBlock(pos.x, pos.y, pos.z); } inline df::map_block * getTileBlock (df::coord pos) { return getTileBlock(pos.x, pos.y, pos.z); } +extern DFHACK_EXPORT df::tiletype *getTileType(int32_t x, int32_t y, int32_t z); +extern DFHACK_EXPORT df::tile_designation *getTileDesignation(int32_t x, int32_t y, int32_t z); +extern DFHACK_EXPORT df::tile_occupancy *getTileOccupancy(int32_t x, int32_t y, int32_t z); + +inline df::tiletype *getTileType(df::coord pos) { + return getTileType(pos.x, pos.y, pos.z); +} +inline df::tile_designation *getTileDesignation(df::coord pos) { + return getTileDesignation(pos.x, pos.y, pos.z); +} +inline df::tile_occupancy *getTileOccupancy(df::coord pos) { + return getTileOccupancy(pos.x, pos.y, pos.z); +} + DFHACK_EXPORT df::world_data::T_region_map *getRegionBiome(df::coord2d rgn_pos); /// sorts the block event vector into multiple vectors by type @@ -256,34 +270,7 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block, /// remove a block event from the block by address extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, df::block_square_event * which ); -/* - * BURROWS - */ - -DFHACK_EXPORT df::burrow *findBurrowByName(std::string name); - -DFHACK_EXPORT void listBurrowBlocks(std::vector *pvec, df::burrow *burrow); -DFHACK_EXPORT void clearBurrowTiles(df::burrow *burrow); - -DFHACK_EXPORT df::block_burrow *getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create = false); -DFHACK_EXPORT bool deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask); - -inline bool deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block) -{ - return deleteBlockBurrowMask(burrow, block, getBlockBurrowMask(burrow, block)); -} - - -DFHACK_EXPORT bool isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile); -DFHACK_EXPORT bool setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable); - -inline bool isBurrowTile(df::burrow *burrow, df::coord tile) { - return isBlockBurrowTile(burrow, getTileBlock(tile), tile); -} -inline bool setBurrowTile(df::burrow *burrow, df::coord tile, bool enable) { - return setBlockBurrowTile(burrow, getTileBlock(tile), tile, enable); -} - +DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); } } #endif diff --git a/library/include/modules/Materials.h b/library/include/modules/Materials.h index 3d70dc9fa..76c89de30 100644 --- a/library/include/modules/Materials.h +++ b/library/include/modules/Materials.h @@ -60,6 +60,14 @@ namespace df namespace DFHack { + struct t_matpair { + int16_t mat_type; + int32_t mat_index; + + t_matpair(int16_t type = -1, int32_t index = -1) + : mat_type(type), mat_index(index) {} + }; + struct DFHACK_EXPORT MaterialInfo { static const int NUM_BUILTIN = 19; static const int GROUP_SIZE = 200; @@ -91,6 +99,7 @@ namespace DFHack public: MaterialInfo(int16_t type = -1, int32_t index = -1) { decode(type, index); } + MaterialInfo(const t_matpair &mp) { decode(mp.mat_type, mp.mat_index); } template MaterialInfo(T *ptr) { decode(ptr); } bool isValid() const { return material != NULL; } @@ -107,6 +116,7 @@ namespace DFHack bool decode(int16_t type, int32_t index = -1); bool decode(df::item *item); bool decode(const df::material_vec_ref &vr, int idx); + bool decode(const t_matpair &mp) { return decode(mp.mat_type, mp.mat_index); } template bool decode(T *ptr) { // Assume and exploit a certain naming convention diff --git a/library/include/modules/Translation.h b/library/include/modules/Translation.h index 6d74431e3..f75bf6339 100644 --- a/library/include/modules/Translation.h +++ b/library/include/modules/Translation.h @@ -52,6 +52,8 @@ DFHACK_EXPORT bool copyName(df::language_name * address, df::language_name * tar DFHACK_EXPORT void setNickname(df::language_name *name, std::string nick); +DFHACK_EXPORT std::string capitalize(const std::string &str, bool all_words = false); + // translate a name using the loaded dictionaries DFHACK_EXPORT std::string TranslateName (const df::language_name * name, bool inEnglish = true, bool onlyLastPart = false); diff --git a/library/include/modules/Units.h b/library/include/modules/Units.h index e093ed1ef..7bf4f101b 100644 --- a/library/include/modules/Units.h +++ b/library/include/modules/Units.h @@ -37,6 +37,10 @@ namespace df { struct nemesis_record; struct burrow; + struct assumed_identity; + struct historical_entity; + struct entity_position_assignment; + struct entity_position; } /** @@ -201,6 +205,7 @@ DFHACK_EXPORT df::item *getContainer(df::unit *unit); DFHACK_EXPORT void setNickname(df::unit *unit, std::string nick); DFHACK_EXPORT df::language_name *getVisibleName(df::unit *unit); +DFHACK_EXPORT df::assumed_identity *getIdentity(df::unit *unit); DFHACK_EXPORT df::nemesis_record *getNemesis(df::unit *unit); DFHACK_EXPORT bool isDead(df::unit *unit); @@ -209,11 +214,18 @@ DFHACK_EXPORT bool isSane(df::unit *unit); DFHACK_EXPORT bool isCitizen(df::unit *unit); DFHACK_EXPORT bool isDwarf(df::unit *unit); -DFHACK_EXPORT void clearBurrowMembers(df::burrow *burrow); +DFHACK_EXPORT double getAge(df::unit *unit, bool true_age = false); -DFHACK_EXPORT bool isInBurrow(df::unit *unit, df::burrow *burrow); -DFHACK_EXPORT void setInBurrow(df::unit *unit, df::burrow *burrow, bool enable); +struct NoblePosition { + df::historical_entity *entity; + df::entity_position_assignment *assignment; + df::entity_position *position; +}; + +DFHACK_EXPORT bool getNoblePositions(std::vector *pvec, df::unit *unit); +DFHACK_EXPORT std::string getProfessionName(df::unit *unit, bool ignore_noble = false, bool plural = false); +DFHACK_EXPORT std::string getCasteProfessionName(int race, int caste, df::profession pid, bool plural = false); } } #endif diff --git a/library/include/modules/Windows.h b/library/include/modules/Windows.h index 314b6e292..7dbc9f1ad 100644 --- a/library/include/modules/Windows.h +++ b/library/include/modules/Windows.h @@ -117,11 +117,11 @@ namespace Windows for ( auto iter = str.begin(); iter != str.end(); iter++) { auto elem = *iter; - if(cursor_y >= height) + if(cursor_y >= (int)height) break; if(wrap) { - if(cursor_x >= width) + if(cursor_x >= (int)width) cursor_x = wrap_column; } df_screentile & tile = buffer[cursor_x * height + cursor_y]; @@ -224,12 +224,12 @@ namespace Windows virtual void blit_to_parent () { df_tilebuf par = parent->getBuffer(); - for(int xi = 0; xi < width; xi++) + for(unsigned xi = 0; xi < width; xi++) { - for(int yi = 0; yi < height; yi++) + for(unsigned yi = 0; yi < height; yi++) { - int parx = left + xi; - int pary = top + yi; + unsigned parx = left + xi; + unsigned pary = top + yi; if(pary >= par.height) continue; if(parx >= par.width) continue; par.data[parx * par.height + pary] = buffer[xi * height + yi]; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index c7e2669c5..c0ba64375 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -1,6 +1,15 @@ -- Common startup file for all dfhack plugins with lua support -- The global dfhack table is already created by C++ init code. +-- Setup the global environment. +-- BASE_G is the original lua global environment, +-- preserved as a common denominator for all modules. +-- This file uses it instead of the new default one. + +local dfhack = dfhack +local base_env = dfhack.BASE_G +local _ENV = base_env + -- Console color constants COLOR_RESET = -1 @@ -70,7 +79,7 @@ function mkmodule(module,env) if plugname then dfhack.open_plugin(pkg,plugname) end - setmetatable(pkg, { __index = (env or _G) }) + setmetatable(pkg, { __index = (env or base_env) }) return pkg end @@ -116,6 +125,31 @@ function xyz2pos(x,y,z) end end +function rawset_default(target,source) + for k,v in pairs(source) do + if rawget(target,k) == nil then + rawset(target,k,v) + end + end +end + +function safe_index(obj,idx,...) + if obj == nil or idx == nil then + return nil + end + if type(idx) == 'number' and (idx < 0 or idx >= #obj) then + return nil + end + obj = obj[idx] + if select('#',...) > 0 then + return safe_index(obj,...) + else + return obj + end +end + +-- String conversions + function dfhack.event:__tostring() return "" end @@ -139,6 +173,11 @@ function dfhack.maps.getTileSize() return map.x_count, map.y_count, map.z_count end +function dfhack.buildings.getSize(bld) + local x, y = bld.x1, bld.y1 + return bld.x2+1-x, bld.y2+1-y, bld.centerx-x, bld.centery-y +end + -- Interactive local print_banner = true @@ -159,12 +198,33 @@ function dfhack.interpreter(prompt,hfile,env) end local prompt_str = "["..(prompt or 'lua').."]# " + local prompt_cont = string.rep(' ',#prompt_str-4)..">>> " local prompt_env = {} - local t_prompt + local cmdlinelist = {} + local t_prompt = nil local vcnt = 1 + local pfix_handlers = { + ['!'] = function(data) + print(table.unpack(data,2,data.n)) + end, + ['~'] = function(data) + print(table.unpack(data,2,data.n)) + printall(data[2]) + end, + ['='] = function(data) + for i=2,data.n do + local varname = '_'..vcnt + prompt_env[varname] = data[i] + dfhack.print(varname..' = ') + safecall(print, data[i]) + vcnt = vcnt + 1 + end + end + } + setmetatable(prompt_env, { __index = env or _G }) - local cmdlinelist={} + while true do local cmdline = dfhack.lineedit(t_prompt or prompt_str, hfile) @@ -173,42 +233,34 @@ function dfhack.interpreter(prompt,hfile,env) elseif cmdline ~= '' then local pfix = string.sub(cmdline,1,1) - if pfix == '!' or pfix == '=' then + if not t_prompt and pfix_handlers[pfix] then cmdline = 'return '..string.sub(cmdline,2) + else + pfix = nil end - table.insert(cmdlinelist,cmdline) - local code,err = load(table.concat(cmdlinelist,'\n'), '=(interactive)', 't', prompt_env) + + table.insert(cmdlinelist,cmdline) + cmdline = table.concat(cmdlinelist,'\n') + + local code,err = load(cmdline, '=(interactive)', 't', prompt_env) if code == nil then - if err:sub(-5)=="" then - t_prompt="[cont]" - - else - dfhack.printerr(err) - cmdlinelist={} - t_prompt=nil - end + if not pfix and err:sub(-5)=="" then + t_prompt=prompt_cont + else + dfhack.printerr(err) + cmdlinelist={} + t_prompt=nil + end else - - cmdlinelist={} - t_prompt=nil - + cmdlinelist={} + t_prompt=nil + local data = table.pack(safecall(code)) if data[1] and data.n > 1 then prompt_env._ = data[2] - - if pfix == '!' then - safecall(print, table.unpack(data,2,data.n)) - else - for i=2,data.n do - local varname = '_'..vcnt - prompt_env[varname] = data[i] - dfhack.print(varname..' = ') - safecall(print, data[i]) - vcnt = vcnt + 1 - end - end + safecall(pfix_handlers[pfix], data) end end end @@ -217,5 +269,23 @@ function dfhack.interpreter(prompt,hfile,env) return true end +-- Command scripts + +dfhack.scripts = dfhack.scripts or {} + +function dfhack.run_script(file,...) + local env = dfhack.scripts[file] + if env == nil then + env = {} + setmetatable(env, { __index = base_env }) + dfhack.scripts[file] = env + end + local f,perr = loadfile(file, 't', env) + if f == nil then + error(perr) + end + return f(...) +end + -- Feed the table back to the require() mechanism. return dfhack diff --git a/library/lua/dfhack/buildings.lua b/library/lua/dfhack/buildings.lua new file mode 100644 index 000000000..eb0a6a5b5 --- /dev/null +++ b/library/lua/dfhack/buildings.lua @@ -0,0 +1,437 @@ +local dfhack = dfhack +local _ENV = dfhack.BASE_G +local buildings = dfhack.buildings + +local utils = require 'utils' + +--[[ Building input material tables. ]] + +local building_inputs = { + [df.building_type.Chair] = { { item_type=df.item_type.CHAIR, vector_id=df.job_item_vector_id.CHAIR } }, + [df.building_type.Bed] = { { item_type=df.item_type.BED, vector_id=df.job_item_vector_id.BED } }, + [df.building_type.Table] = { { item_type=df.item_type.TABLE, vector_id=df.job_item_vector_id.TABLE } }, + [df.building_type.Coffin] = { { item_type=df.item_type.COFFIN, vector_id=df.job_item_vector_id.COFFIN } }, + [df.building_type.FarmPlot] = { }, + [df.building_type.Furnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, + [df.building_type.TradeDepot] = { { flags2={ building_material=true, non_economic=true }, quantity=3 } }, + [df.building_type.Door] = { { item_type=df.item_type.DOOR, vector_id=df.job_item_vector_id.DOOR } }, + [df.building_type.Floodgate] = { + { + item_type=df.item_type.FLOODGATE, + vector_id=df.job_item_vector_id.FLOODGATE + } + }, + [df.building_type.Box] = { + { + flags1={ empty=true }, + item_type=df.item_type.BOX, + vector_id=df.job_item_vector_id.BOX + } + }, + [df.building_type.Weaponrack] = { + { + item_type=df.item_type.WEAPONRACK, + vector_id=df.job_item_vector_id.WEAPONRACK + } + }, + [df.building_type.Armorstand] = { + { + item_type=df.item_type.ARMORSTAND, + vector_id=df.job_item_vector_id.ARMORSTAND + } + }, + [df.building_type.Workshop] = { { flags2={ building_material=true, non_economic=true } } }, + [df.building_type.Cabinet] = { + { item_type=df.item_type.CABINET, vector_id=df.job_item_vector_id.CABINET } + }, + [df.building_type.Statue] = { { item_type=df.item_type.STATUE, vector_id=df.job_item_vector_id.STATUE } }, + [df.building_type.WindowGlass] = { { item_type=df.item_type.WINDOW, vector_id=df.job_item_vector_id.WINDOW } }, + [df.building_type.WindowGem] = { + { + item_type=df.item_type.SMALLGEM, + quantity=3, + vector_id=df.job_item_vector_id.ANY_GENERIC35 + } + }, + [df.building_type.Well] = { + { + item_type=df.item_type.BLOCKS, + vector_id=df.job_item_vector_id.ANY_GENERIC35 + }, + { + name='bucket', + flags2={ lye_milk_free=true }, + item_type=df.item_type.BUCKET, + vector_id=df.job_item_vector_id.BUCKET + }, + { + name='chain', + item_type=df.item_type.CHAIN, + vector_id=df.job_item_vector_id.CHAIN + }, + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS + } + }, + [df.building_type.Bridge] = { { flags2={ building_material=true, non_economic=true }, quantity=-1 } }, + [df.building_type.RoadDirt] = { }, + [df.building_type.RoadPaved] = { { flags2={ building_material=true, non_economic=true }, quantity=-1 } }, + [df.building_type.AnimalTrap] = { + { + flags1={ empty=true }, + item_type=df.item_type.ANIMALTRAP, + vector_id=df.job_item_vector_id.ANIMALTRAP + } + }, + [df.building_type.Support] = { { flags2={ building_material=true, non_economic=true } } }, + [df.building_type.ArcheryTarget] = { { flags2={ building_material=true, non_economic=true } } }, + [df.building_type.Chain] = { { item_type=df.item_type.CHAIN, vector_id=df.job_item_vector_id.CHAIN } }, + [df.building_type.Cage] = { { item_type=df.item_type.CAGE, vector_id=df.job_item_vector_id.CAGE } }, + [df.building_type.Weapon] = { { vector_id=df.job_item_vector_id.ANY_SPIKE } }, + [df.building_type.ScrewPump] = { + { + item_type=df.item_type.BLOCKS, + vector_id=df.job_item_vector_id.ANY_GENERIC35 + }, + { + name='screw', + flags2={ screw=true }, + item_type=df.item_type.TRAPCOMP, + vector_id=df.job_item_vector_id.ANY_WEAPON + }, + { + name='pipe', + item_type=df.item_type.PIPE_SECTION, + vector_id=df.job_item_vector_id.PIPE_SECTION + } + }, + [df.building_type.Construction] = { { flags2={ building_material=true, non_economic=true } } }, + [df.building_type.Hatch] = { + { + item_type=df.item_type.HATCH_COVER, + vector_id=df.job_item_vector_id.HATCH_COVER + } + }, + [df.building_type.GrateWall] = { { item_type=df.item_type.GRATE, vector_id=df.job_item_vector_id.GRATE } }, + [df.building_type.GrateFloor] = { { item_type=df.item_type.GRATE, vector_id=df.job_item_vector_id.GRATE } }, + [df.building_type.BarsVertical] = { + { item_type=df.item_type.BAR, vector_id=df.job_item_vector_id.ANY_GENERIC35 } + }, + [df.building_type.BarsFloor] = { + { item_type=df.item_type.BAR, vector_id=df.job_item_vector_id.ANY_GENERIC35 } + }, + [df.building_type.GearAssembly] = { + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS + } + }, + [df.building_type.AxleHorizontal] = { + { item_type=df.item_type.WOOD, vector_id=df.job_item_vector_id.WOOD, quantity=-1 } + }, + [df.building_type.AxleVertical] = { { item_type=df.item_type.WOOD, vector_id=df.job_item_vector_id.WOOD } }, + [df.building_type.WaterWheel] = { + { + item_type=df.item_type.WOOD, + quantity=3, + vector_id=df.job_item_vector_id.WOOD + } + }, + [df.building_type.Windmill] = { + { + item_type=df.item_type.WOOD, + quantity=4, + vector_id=df.job_item_vector_id.WOOD + } + }, + [df.building_type.TractionBench] = { + { + item_type=df.item_type.TRACTION_BENCH, + vector_id=df.job_item_vector_id.TRACTION_BENCH + } + }, + [df.building_type.Slab] = { { item_type=df.item_type.SLAB } }, + [df.building_type.NestBox] = { { has_tool_use=df.tool_uses.NEST_BOX, item_type=df.item_type.TOOL } }, + [df.building_type.Hive] = { { has_tool_use=df.tool_uses.HIVE, item_type=df.item_type.TOOL } } +} + +local furnace_inputs = { + [df.furnace_type.WoodFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, + [df.furnace_type.Smelter] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, + [df.furnace_type.GlassFurnace] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, + [df.furnace_type.Kiln] = { { flags2={ building_material=true, fire_safe=true, non_economic=true } } }, + [df.furnace_type.MagmaSmelter] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } }, + [df.furnace_type.MagmaGlassFurnace] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } }, + [df.furnace_type.MagmaKiln] = { { flags2={ building_material=true, magma_safe=true, non_economic=true } } } +} + +local workshop_inputs = { + [df.workshop_type.Carpenters] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Farmers] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Masons] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Craftsdwarfs] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Jewelers] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.MetalsmithsForge] = { + { + name='anvil', + flags2={ fire_safe=true }, + item_type=df.item_type.ANVIL, + vector_id=df.job_item_vector_id.ANVIL + }, + { flags2={ building_material=true, fire_safe=true, non_economic=true } } + }, + [df.workshop_type.MagmaForge] = { + { + name='anvil', + flags2={ magma_safe=true }, + item_type=df.item_type.ANVIL, + vector_id=df.job_item_vector_id.ANVIL + }, + { flags2={ building_material=true, magma_safe=true, non_economic=true } } + }, + [df.workshop_type.Bowyers] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Mechanics] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Siege] = { { flags2={ building_material=true, non_economic=true }, quantity=3 } }, + [df.workshop_type.Butchers] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Leatherworks] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Tanners] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Clothiers] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Fishery] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Still] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Loom] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Quern] = { { item_type=df.item_type.QUERN, vector_id=df.job_item_vector_id.QUERN } }, + [df.workshop_type.Kennels] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Kitchen] = { { flags2={ building_material=true, non_economic=true } } }, + [df.workshop_type.Ashery] = { + { + item_type=df.item_type.BLOCKS, + vector_id=df.job_item_vector_id.ANY_GENERIC35 + }, + { + name='barrel', + flags1={ empty=true }, + item_type=df.item_type.BARREL, + vector_id=df.job_item_vector_id.BARREL + }, + { + name='bucket', + flags2={ lye_milk_free=true }, + item_type=df.item_type.BUCKET, + vector_id=df.job_item_vector_id.BUCKET + } + }, + [df.workshop_type.Dyers] = { + { + name='barrel', + flags1={ empty=true }, + item_type=df.item_type.BARREL, + vector_id=df.job_item_vector_id.BARREL + }, + { + name='bucket', + flags2={ lye_milk_free=true }, + item_type=df.item_type.BUCKET, + vector_id=df.job_item_vector_id.BUCKET + } + }, + [df.workshop_type.Millstone] = { + { + item_type=df.item_type.MILLSTONE, + vector_id=df.job_item_vector_id.MILLSTONE + }, + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS + } + } +} + +local trap_inputs = { + [df.trap_type.StoneFallTrap] = { + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS + } + }, + [df.trap_type.WeaponTrap] = { + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS + }, + { vector_id=df.job_item_vector_id.ANY_WEAPON } + }, + [df.trap_type.Lever] = { + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS + } + }, + [df.trap_type.PressurePlate] = { + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS + } + }, + [df.trap_type.CageTrap] = { + { + name='mechanism', + item_type=df.item_type.TRAPPARTS, + vector_id=df.job_item_vector_id.TRAPPARTS + } + } +} + +local function get_inputs_by_type(type,subtype,custom) + if type == df.building_type.Workshop then + return workshop_inputs[subtype] + elseif type == df.building_type.Furnace then + return furnace_inputs[subtype] + elseif type == df.building_type.Trap then + return trap_inputs[subtype] + else + return building_inputs[type] + end +end + +local function augment_input(input, argtable) + local rv = {} + local arg = argtable[input.name or 'material'] + + if arg then + utils.assign(rv, arg) + end + + utils.assign(rv, input) + + if rv.mat_index and safe_index(rv, 'flags2', 'non_economic') then + rv.flags2.non_economic = false + end + + rv.new = true + rv.name = nil + return rv +end + +function buildings.getFiltersByType(argtable,type,subtype,custom) + local inputs = get_inputs_by_type(type,subtype,custom) + if not inputs then + return nil + end + local rv = {} + for i,v in ipairs(inputs) do + rv[i] = augment_input(v, argtable) + end + return rv +end + +--[[ + Wraps all steps necessary to create a building with + a construct job into one function. + + dfhack.buildings.constructBuilding{ + -- Position: + pos = { x = ..., y = ..., z = ... }, + -- OR + x = ..., y = ..., z = ..., + + -- Type: + type = df.building_type.FOO, subtype = ..., custom = ..., + + -- Field initialization: + fields = { ... }, + + -- Size and orientation: + width = ..., height = ..., direction = ..., + + -- Abort if not all tiles in the rectangle are available: + full_rectangle = true, + + -- Materials: + items = { item, item ... }, + -- OR + filter = { { ... }, { ... }... } + -- OR + abstract = true + -- OR + material = { filter_properties... } + mechanism = { filter_properties... } + barrel, bucket, chain, anvil, screw, pipe + } + + Returns: the created building, or 'nil, error' +--]] + +function buildings.constructBuilding(info) + local btype = info.type + local subtype = info.subtype or -1 + local custom = info.custom or -1 + local filters = info.filters + + if not (info.pos or info.x) then + error('position is required') + end + if not (info.abstract or info.items or filters) then + filters = buildings.getFiltersByType(info,btype,subtype,custom) + if not filters then + error('one of items, filters or abstract is required') + end + elseif filters then + for _,v in ipairs(filters) do + v.new = true + end + end + if type(btype) ~= 'number' or not df.building_type[btype] then + error('Invalid building type: '..tostring(btype)) + end + + local pos = info.pos or xyz2pos(info.x, info.y, info.z) + + local instance = buildings.allocInstance(pos, btype, subtype, custom) + if not instance then + error('Could not create building of type '..df.building_type[btype]) + end + + local to_delete = instance + return dfhack.with_finalize( + function() + df.delete(to_delete) + end, + function() + if info.fields then + instance:assign(info.fields) + end + local ok,w,h,area,r_area = buildings.setSize( + instance,info.width,info.height,info.direction + ) + if not ok then + return nil, "cannot place at this position" + end + if info.full_rectangle and area ~= r_area then + return nil, "not all tiles can be used" + end + if info.abstract then + ok = buildings.constructAbstract(instance) + elseif info.items then + ok = buildings.constructWithItems(instance, info.items) + else + ok = buildings.constructWithFilters(instance, filters) + end + if not ok then + return nil, "could not construct the building" + end + -- Success + to_delete = nil + return instance + end + ) +end + +return buildings \ No newline at end of file diff --git a/library/lua/utils.lua b/library/lua/utils.lua new file mode 100644 index 000000000..3eeb0cc6f --- /dev/null +++ b/library/lua/utils.lua @@ -0,0 +1,139 @@ +local _ENV = mkmodule('utils') + +-- Comparator function +function compare(a,b) + if a < b then + return -1 + elseif a > b then + return 1 + else + return 0 + end +end + +-- Sort strings; compare empty last +function compare_name(a,b) + if a == '' then + if b == '' then + return 0 + else + return 1 + end + elseif b == '' then + return -1 + else + return compare(a,b) + end +end + +--[[ + Sort items in data according to ordering. + + Each ordering spec is a table with possible fields: + + * key = function(value) + Computes comparison key from a data value. Not called on nil. + * key_table = function(data) + Computes a key table from the data table in one go. + * compare = function(a,b) + Comparison function. Defaults to compare above. + Called on non-nil keys; nil sorts last. + * nil_first + If true, nil keys are sorted first instead of last. + * reverse + If true, sort non-nil keys in descending order. + + Returns a table of integer indices into data. +--]] +function make_sort_order(data,ordering) + -- Compute sort keys and comparators + local keys = {} + local cmps = {} + local size = data.n or #data + + for i=1,#ordering do + local order = ordering[i] + + if order.key_table then + keys[i] = order.key_table(data) + elseif order.key then + local kt = {} + local kf = order.key + for j=1,size do + if data[j] == nil then + kt[j] = nil + else + kt[j] = kf(data[j]) + end + end + keys[i] = kt + else + keys[i] = data + end + + cmps[i] = order.compare or compare + end + + -- Make an order table + local index = {} + for i=1,size do + index[i] = i + end + + -- Sort the ordering table + table.sort(index, function(ia,ib) + for i=1,#keys do + local ka = keys[i][ia] + local kb = keys[i][ib] + + -- Sort nil keys to the end + if ka == nil then + if kb ~= nil then + return ordering[i].nil_first + end + elseif kb == nil then + return not ordering[i].nil_first + else + local cmpv = cmps[i](ka,kb) + if ordering[i].reverse then + cmpv = -cmpv + end + if cmpv < 0 then + return true + elseif cmpv > 0 then + return false + end + end + end + return ia < ib -- this should ensure stable sort + end) + + return index +end + +--[[ + Recursively assign data into a table. +--]] +function assign(tgt,src) + if df.isvalid(tgt) == 'ref' then + df.assign(tgt, src) + elseif type(tgt) == 'table' then + for k,v in pairs(src) do + if type(v) == 'table' or df.isvalid(v) == 'ref' then + local cv = tgt[k] + if cv == nil then + cv = {} + tgt[k] = cv + end + assign(cv, v) + else + tgt[k] = v + end + end + else + error('Invalid assign target type: '..tostring(tgt)) + end + return tgt +end + +return _ENV \ No newline at end of file diff --git a/library/modules/Buildings.cpp b/library/modules/Buildings.cpp index 6317f3e10..557eb1881 100644 --- a/library/modules/Buildings.cpp +++ b/library/modules/Buildings.cpp @@ -35,19 +35,57 @@ using namespace std; #include "Types.h" #include "Error.h" #include "modules/Buildings.h" +#include "modules/Maps.h" +#include "modules/Job.h" #include "ModuleFactory.h" #include "Core.h" +#include "TileTypes.h" +#include "MiscUtils.h" using namespace DFHack; #include "DataDefs.h" #include "df/world.h" +#include "df/ui.h" +#include "df/ui_look_list.h" +#include "df/d_init.h" +#include "df/item.h" +#include "df/job.h" +#include "df/job_item.h" +#include "df/general_ref_building_holderst.h" +#include "df/buildings_other_id.h" +#include "df/building_design.h" #include "df/building_def.h" +#include "df/building_axle_horizontalst.h" +#include "df/building_trapst.h" +#include "df/building_bridgest.h" +#include "df/building_coffinst.h" +#include "df/building_civzonest.h" +#include "df/building_stockpilest.h" +#include "df/building_furnacest.h" #include "df/building_workshopst.h" +#include "df/building_screw_pumpst.h" +#include "df/building_water_wheelst.h" +#include "df/building_wellst.h" using namespace df::enums; +using df::global::ui; using df::global::world; +using df::global::d_init; +using df::global::building_next_id; +using df::global::process_jobs; using df::building_def; +static uint8_t *getExtentTile(df::building_extents &extent, df::coord2d tile) +{ + if (!extent.extents) + return NULL; + int dx = tile.x - extent.x; + int dy = tile.y - extent.y; + if (dx < 0 || dy < 0 || dx >= extent.width || dy >= extent.height) + return NULL; + return &extent.extents[dx + dy*extent.width]; +} + uint32_t Buildings::getNumBuildings() { return world->buildings.all.size(); @@ -86,3 +124,836 @@ bool Buildings::ReadCustomWorkshopTypes(map & btypes) return true; } +df::building *Buildings::findAtTile(df::coord pos) +{ + auto occ = Maps::getTileOccupancy(pos); + if (!occ || !occ->bits.building) + return NULL; + + auto &vec = df::building::get_vector(); + for (size_t i = 0; i < vec.size(); i++) + { + auto bld = vec[i]; + + if (pos.z != bld->z || + pos.x < bld->x1 || pos.x > bld->x2 || + pos.y < bld->y1 || pos.y > bld->y2) + continue; + + if (!bld->isSettingOccupancy()) + continue; + + if (bld->room.extents && bld->isExtentShaped()) + { + auto etile = getExtentTile(bld->room, pos); + if (!etile || !*etile) + continue; + } + + return bld; + } + + return NULL; +} + +bool Buildings::findCivzonesAt(std::vector *pvec, df::coord pos) +{ + pvec->clear(); + + auto &vec = world->buildings.other[buildings_other_id::ANY_ZONE]; + + for (size_t i = 0; i < vec.size(); i++) + { + auto bld = strict_virtual_cast(vec[i]); + + if (!bld || bld->z != pos.z || !containsTile(bld, pos)) + continue; + + pvec->push_back(bld); + } + + return !pvec->empty(); +} + +df::building *Buildings::allocInstance(df::coord pos, df::building_type type, int subtype, int custom) +{ + if (!building_next_id) + return NULL; + + // Allocate object + const char *classname = ENUM_ATTR(building_type, classname, type); + if (!classname) + return NULL; + + auto id = virtual_identity::find(classname); + if (!id) + return NULL; + + df::building *bld = (df::building*)id->allocate(); + if (!bld) + return NULL; + + // Init base fields + bld->x1 = bld->x2 = bld->centerx = pos.x; + bld->y1 = bld->y2 = bld->centery = pos.y; + bld->z = pos.z; + + bld->race = ui->race_id; + + if (subtype != -1) + bld->setSubtype(subtype); + if (custom != -1) + bld->setCustomType(custom); + + bld->setMaterialAmount(1); + + // Type specific init + switch (type) + { + case building_type::Well: + { + auto obj = (df::building_wellst*)bld; + obj->bucket_z = bld->z; + break; + } + case building_type::Furnace: + { + auto obj = (df::building_furnacest*)bld; + obj->melt_remainder.resize(df::inorganic_raw::get_vector().size(), 0); + break; + } + case building_type::Coffin: + { + auto obj = (df::building_coffinst*)bld; + obj->initBurialFlags(); // DF has this copy&pasted + break; + } + case building_type::Trap: + { + auto obj = (df::building_trapst*)bld; + if (obj->trap_type == trap_type::PressurePlate) + obj->unk_cc = 500; + break; + } + default: + break; + } + + return bld; +} + +static void makeOneDim(df::coord2d &size, df::coord2d ¢er, bool vertical) +{ + if (vertical) + size.x = 1; + else + size.y = 1; + center = size/2; +} + +bool Buildings::getCorrectSize(df::coord2d &size, df::coord2d ¢er, + df::building_type type, int subtype, int custom, int direction) +{ + using namespace df::enums::building_type; + + if (size.x <= 0) + size.x = 1; + if (size.y <= 0) + size.y = 1; + + switch (type) + { + case FarmPlot: + case Bridge: + case RoadDirt: + case RoadPaved: + case Stockpile: + case Civzone: + center = size/2; + return true; + + case TradeDepot: + case Shop: + size = df::coord2d(5,5); + center = df::coord2d(2,2); + return false; + + case SiegeEngine: + case Windmill: + case Wagon: + size = df::coord2d(3,3); + center = df::coord2d(1,1); + return false; + + case AxleHorizontal: + makeOneDim(size, center, direction); + return true; + + case WaterWheel: + size = df::coord2d(3,3); + makeOneDim(size, center, direction); + return false; + + case Workshop: + { + using namespace df::enums::workshop_type; + + switch ((df::workshop_type)subtype) + { + case Quern: + case Millstone: + case Tool: + size = df::coord2d(1,1); + center = df::coord2d(0,0); + break; + + case Siege: + case Kennels: + size = df::coord2d(5,5); + center = df::coord2d(2,2); + break; + + case Custom: + if (auto def = df::building_def::find(custom)) + { + size = df::coord2d(def->dim_x, def->dim_y); + center = df::coord2d(def->workloc_x, def->workloc_y); + break; + } + + default: + size = df::coord2d(3,3); + center = df::coord2d(1,1); + } + + return false; + } + + case Furnace: + { + using namespace df::enums::furnace_type; + + switch ((df::furnace_type)subtype) + { + case Custom: + if (auto def = df::building_def::find(custom)) + { + size = df::coord2d(def->dim_x, def->dim_y); + center = df::coord2d(def->workloc_x, def->workloc_y); + break; + } + + default: + size = df::coord2d(3,3); + center = df::coord2d(1,1); + } + + return false; + } + + case ScrewPump: + { + using namespace df::enums::screw_pump_direction; + + switch ((df::screw_pump_direction)direction) + { + case FromEast: + size = df::coord2d(2,1); + center = df::coord2d(1,0); + break; + case FromSouth: + size = df::coord2d(1,2); + center = df::coord2d(0,1); + break; + case FromWest: + size = df::coord2d(2,1); + center = df::coord2d(0,0); + break; + default: + size = df::coord2d(1,2); + center = df::coord2d(0,0); + } + + return false; + } + + default: + size = df::coord2d(1,1); + center = df::coord2d(0,0); + return false; + } +} + +bool Buildings::checkFreeTiles(df::coord pos, df::coord2d size, + df::building_extents *ext, + bool create_ext, bool allow_occupied) +{ + bool found_any = false; + + for (int dx = 0; dx < size.x; dx++) + { + for (int dy = 0; dy < size.y; dy++) + { + df::coord tile = pos + df::coord(dx,dy,0); + uint8_t *etile = NULL; + + // Exclude using extents + if (ext && ext->extents) + { + etile = getExtentTile(*ext, tile); + if (!etile || !*etile) + continue; + } + + // Look up map block + df::map_block *block = Maps::getTileBlock(tile); + if (!block) + return false; + + df::coord2d btile = df::coord2d(tile) & 15; + + bool allowed = true; + + // Check occupancy and tile type + if (!allow_occupied && + block->occupancy[btile.x][btile.y].bits.building) + allowed = false; + else + { + auto tile = block->tiletype[btile.x][btile.y]; + if (!HighPassable(tile)) + allowed = false; + } + + // Create extents if requested + if (allowed) + found_any = true; + else + { + if (!ext || !create_ext) + return false; + + if (!ext->extents) + { + ext->extents = new uint8_t[size.x*size.y]; + ext->x = pos.x; + ext->y = pos.y; + ext->width = size.x; + ext->height = size.y; + + memset(ext->extents, 1, size.x*size.y); + etile = getExtentTile(*ext, tile); + } + + if (!etile) + return false; + + *etile = 0; + } + } + } + + return found_any; +} + +std::pair Buildings::getSize(df::building *bld) +{ + CHECK_NULL_POINTER(bld); + + df::coord pos(bld->x1,bld->y1,bld->z); + + return std::pair(pos, df::coord2d(bld->x2+1,bld->y2+1) - pos); +} + +static bool checkBuildingTiles(df::building *bld, bool can_change) +{ + auto psize = Buildings::getSize(bld); + + return Buildings::checkFreeTiles(psize.first, psize.second, &bld->room, + can_change && bld->isExtentShaped(), + !bld->isSettingOccupancy()); +} + +int Buildings::countExtentTiles(df::building_extents *ext, int defval) +{ + if (!ext || !ext->extents) + return defval; + + int cnt = 0; + for (int i = 0; i < ext->width * ext->height; i++) + if (ext->extents[i]) + cnt++; + return cnt; +} + +bool Buildings::containsTile(df::building *bld, df::coord2d tile, bool room) +{ + CHECK_NULL_POINTER(bld); + + if (room) + { + if (!bld->is_room || !bld->room.extents) + return false; + } + else + { + if (tile.x < bld->x1 || tile.x > bld->x2 || tile.y < bld->y1 || tile.y >= bld->y2) + return false; + } + + if (bld->room.extents && (room || bld->isExtentShaped())) + { + uint8_t *etile = getExtentTile(bld->room, tile); + if (!etile || !*etile) + return false; + } + + return true; +} + +bool Buildings::hasSupport(df::coord pos, df::coord2d size) +{ + for (int dx = -1; dx <= size.x; dx++) + { + for (int dy = -1; dy <= size.y; dy++) + { + // skip corners + if ((dx < 0 || dx == size.x) && (dy < 0 || dy == size.y)) + continue; + + df::coord tile = pos + df::coord(dx,dy,0); + df::map_block *block = Maps::getTileBlock(tile); + if (!block) + continue; + + df::coord2d btile = df::coord2d(tile) & 15; + if (!isOpenTerrain(block->tiletype[btile.x][btile.y])) + return true; + } + } + + return false; +} + +static int computeMaterialAmount(df::building *bld) +{ + auto size = Buildings::getSize(bld).second; + int cnt = size.x * size.y; + + if (bld->room.extents && bld->isExtentShaped()) + cnt = Buildings::countExtentTiles(&bld->room, cnt); + + return cnt/4 + 1; +} + +bool Buildings::setSize(df::building *bld, df::coord2d size, int direction) +{ + CHECK_NULL_POINTER(bld); + CHECK_INVALID_ARGUMENT(bld->id == -1); + + // Delete old extents + if (bld->room.extents) + { + delete[] bld->room.extents; + bld->room.extents = NULL; + } + + // Compute correct size and apply it + df::coord2d center; + getCorrectSize(size, center, bld->getType(), bld->getSubtype(), + bld->getCustomType(), direction); + + bld->x2 = bld->x1 + size.x - 1; + bld->y2 = bld->y1 + size.y - 1; + bld->centerx = bld->x1 + center.x; + bld->centery = bld->y1 + center.y; + + auto type = bld->getType(); + + using namespace df::enums::building_type; + + switch (type) + { + case WaterWheel: + { + auto obj = (df::building_water_wheelst*)bld; + obj->is_vertical = !!direction; + break; + } + case AxleHorizontal: + { + auto obj = (df::building_axle_horizontalst*)bld; + obj->is_vertical = !!direction; + break; + } + case ScrewPump: + { + auto obj = (df::building_screw_pumpst*)bld; + obj->direction = (df::screw_pump_direction)direction; + break; + } + case Bridge: + { + auto obj = (df::building_bridgest*)bld; + auto psize = getSize(bld); + obj->gate_flags.bits.has_support = hasSupport(psize.first, psize.second); + obj->direction = (df::building_bridgest::T_direction)direction; + break; + } + default: + break; + } + + bool ok = checkBuildingTiles(bld, true); + + if (type != Construction) + bld->setMaterialAmount(computeMaterialAmount(bld)); + + return ok; +} + +static void markBuildingTiles(df::building *bld, bool remove) +{ + bool use_extents = bld->room.extents && bld->isExtentShaped(); + bool stockpile = (bld->getType() == building_type::Stockpile); + bool complete = (bld->getBuildStage() >= bld->getMaxBuildStage()); + + if (remove) + stockpile = complete = false; + + for (int tx = bld->x1; tx <= bld->x2; tx++) + { + for (int ty = bld->y1; ty <= bld->y2; ty++) + { + df::coord tile(tx,ty,bld->z); + + if (use_extents) + { + uint8_t *etile = getExtentTile(bld->room, tile); + if (!etile || !*etile) + continue; + } + + df::map_block *block = Maps::getTileBlock(tile); + df::coord2d btile = df::coord2d(tile) & 15; + + auto &des = block->designation[btile.x][btile.y]; + + des.bits.pile = stockpile; + if (!remove) + des.bits.dig = tile_dig_designation::No; + + if (complete) + bld->updateOccupancy(tx, ty); + else + { + auto &occ = block->occupancy[btile.x][btile.y]; + + if (remove) + occ.bits.building = tile_building_occ::None; + else + occ.bits.building = tile_building_occ::Planned; + } + } + } +} + +static void linkRooms(df::building *bld) +{ + auto &vec = world->buildings.other[buildings_other_id::ANY_FREE]; + + bool changed = false; + + for (size_t i = 0; i < vec.size(); i++) + { + auto room = vec[i]; + if (!room->is_room || room->z != bld->z) + continue; + + uint8_t *pext = getExtentTile(room->room, df::coord2d(bld->x1, bld->y1)); + if (!pext || !*pext) + continue; + + changed = true; + room->children.push_back(bld); + bld->parents.push_back(room); + + // TODO: the game updates room rent here if economy is enabled + } + + if (changed) + df::global::ui->equipment.update.bits.buildings = true; +} + +static void unlinkRooms(df::building *bld) +{ + for (size_t i = 0; i < bld->parents.size(); i++) + { + auto parent = bld->parents[i]; + int idx = linear_index(parent->children, bld); + vector_erase_at(parent->children, idx); + } + + bld->parents.clear(); +} + +static void linkBuilding(df::building *bld) +{ + bld->id = (*building_next_id)++; + + world->buildings.all.push_back(bld); + bld->categorize(true); + + if (bld->isSettingOccupancy()) + markBuildingTiles(bld, false); + + linkRooms(bld); + + if (process_jobs) + *process_jobs = true; +} + +static void createDesign(df::building *bld, bool rough) +{ + auto job = bld->jobs[0]; + + job->mat_type = bld->mat_type; + job->mat_index = bld->mat_index; + + if (bld->needsDesign()) + { + auto act = (df::building_actual*)bld; + act->design = new df::building_design(); + + act->design->flags.bits.rough = rough; + } +} + +static int getMaxStockpileId() +{ + auto &vec = world->buildings.other[buildings_other_id::STOCKPILE]; + int max_id = 0; + + for (size_t i = 0; i < vec.size(); i++) + { + auto bld = strict_virtual_cast(vec[i]); + if (bld) + max_id = std::max(max_id, bld->stockpile_number); + } + + return max_id; +} + +bool Buildings::constructAbstract(df::building *bld) +{ + CHECK_NULL_POINTER(bld); + CHECK_INVALID_ARGUMENT(bld->id == -1); + CHECK_INVALID_ARGUMENT(!bld->isActual()); + + if (!checkBuildingTiles(bld, false)) + return false; + + switch (bld->getType()) + { + case building_type::Stockpile: + if (auto stock = strict_virtual_cast(bld)) + stock->stockpile_number = getMaxStockpileId() + 1; + break; + + default: + break; + } + + linkBuilding(bld); + + if (!bld->flags.bits.exists) + { + bld->flags.bits.exists = true; + bld->initFarmSeasons(); + } + + return true; +} + +static bool linkForConstruct(df::job* &job, df::building *bld) +{ + if (!checkBuildingTiles(bld, false)) + return false; + + auto ref = df::allocate(); + if (!ref) + { + Core::printerr("Could not allocate general_ref_building_holderst\n"); + return false; + } + + linkBuilding(bld); + + ref->building_id = bld->id; + + job = new df::job(); + job->job_type = df::job_type::ConstructBuilding; + job->pos = df::coord(bld->centerx, bld->centery, bld->z); + job->references.push_back(ref); + + bld->jobs.push_back(job); + + Job::linkIntoWorld(job); + + return true; +} + +static bool needsItems(df::building *bld) +{ + if (!bld->isActual()) + return false; + + switch (bld->getType()) + { + case building_type::FarmPlot: + case building_type::RoadDirt: + return false; + + default: + return true; + } +} + +bool Buildings::constructWithItems(df::building *bld, std::vector items) +{ + CHECK_NULL_POINTER(bld); + CHECK_INVALID_ARGUMENT(bld->id == -1); + CHECK_INVALID_ARGUMENT(bld->isActual()); + CHECK_INVALID_ARGUMENT(!items.empty() == needsItems(bld)); + + for (size_t i = 0; i < items.size(); i++) + { + CHECK_NULL_POINTER(items[i]); + + if (items[i]->flags.bits.in_job) + return false; + } + + df::job *job = NULL; + if (!linkForConstruct(job, bld)) + return false; + + bool rough = false; + + for (size_t i = 0; i < items.size(); i++) + { + Job::attachJobItem(job, items[i], df::job_item_ref::Hauled); + + if (items[i]->getType() == item_type::BOULDER) + rough = true; + if (bld->mat_type == -1) + bld->mat_type = items[i]->getMaterial(); + if (bld->mat_index == -1) + bld->mat_index = items[i]->getMaterialIndex(); + } + + createDesign(bld, rough); + return true; +} + +bool Buildings::constructWithFilters(df::building *bld, std::vector items) +{ + CHECK_NULL_POINTER(bld); + CHECK_INVALID_ARGUMENT(bld->id == -1); + CHECK_INVALID_ARGUMENT(bld->isActual()); + CHECK_INVALID_ARGUMENT(!items.empty() == needsItems(bld)); + + for (size_t i = 0; i < items.size(); i++) + CHECK_NULL_POINTER(items[i]); + + df::job *job = NULL; + if (!linkForConstruct(job, bld)) + { + for (size_t i = 0; i < items.size(); i++) + delete items[i]; + + return false; + } + + bool rough = false; + + for (size_t i = 0; i < items.size(); i++) + { + if (items[i]->quantity < 0) + items[i]->quantity = computeMaterialAmount(bld); + + job->job_items.push_back(items[i]); + + if (items[i]->item_type == item_type::BOULDER) + rough = true; + if (bld->mat_type == -1) + bld->mat_type = items[i]->mat_type; + if (bld->mat_index == -1) + bld->mat_index = items[i]->mat_index; + } + + createDesign(bld, rough); + return true; +} + +bool Buildings::deconstruct(df::building *bld) +{ + using df::global::ui; + using df::global::world; + using df::global::process_jobs; + using df::global::process_dig; + using df::global::ui_look_list; + + CHECK_NULL_POINTER(bld); + + if (bld->isActual() && bld->getBuildStage() > 0) + { + bld->queueDestroy(); + return false; + } + + /* Immediate destruction code path. + Should only happen for abstract and unconstructed buildings.*/ + + if (bld->isSettingOccupancy()) + { + markBuildingTiles(bld, true); + bld->cleanupMap(); + } + + bld->removeUses(false, false); + // Assume: no parties. + unlinkRooms(bld); + // Assume: not unit destroy target + vector_erase_at(ui->unk8.unk10, linear_index(ui->unk8.unk10, bld)); + // Assume: not used in punishment + // Assume: not used in non-own jobs + // Assume: does not affect pathfinding + bld->deconstructItems(false, false); + // Don't clear arrows. + + bld->uncategorize(); + delete bld; + + if (world->selected_building == bld) + { + world->selected_building = NULL; + world->update_selected_building = true; + } + + for (int i = ui_look_list->items.size()-1; i >= 0; i--) + { + auto item = ui_look_list->items[i]; + if (item->type == df::ui_look_list::T_items::Building && + item->building == bld) + { + vector_erase_at(ui_look_list->items, i); + delete item; + } + } + + if (process_dig) *process_dig = true; + if (process_jobs) *process_jobs = true; + + return true; +} diff --git a/library/modules/Burrows.cpp b/library/modules/Burrows.cpp new file mode 100644 index 000000000..62f62779a --- /dev/null +++ b/library/modules/Burrows.cpp @@ -0,0 +1,278 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + + +#include "Internal.h" + +#include +#include +using namespace std; + +#include "Error.h" +#include "Core.h" + +#include "modules/Burrows.h" +#include "modules/Maps.h" +#include "modules/Units.h" + +#include "MiscUtils.h" + +#include "DataDefs.h" +#include "df/ui.h" +#include "df/burrow.h" +#include "df/block_burrow.h" +#include "df/block_burrow_link.h" + +using namespace DFHack; +using namespace df::enums; + +using df::global::world; +using df::global::ui; + +df::burrow *Burrows::findByName(std::string name) +{ + auto &vec = df::burrow::get_vector(); + + for (size_t i = 0; i < vec.size(); i++) + if (vec[i]->name == name) + return vec[i]; + + return NULL; +} + +void Burrows::clearUnits(df::burrow *burrow) +{ + CHECK_NULL_POINTER(burrow); + + for (size_t i = 0; i < burrow->units.size(); i++) + { + auto unit = df::unit::find(burrow->units[i]); + + if (unit) + erase_from_vector(unit->burrows, burrow->id); + } + + burrow->units.clear(); + + // Sync ui if active + if (ui && ui->main.mode == ui_sidebar_mode::Burrows && + ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) + { + auto &sel = ui->burrows.sel_units; + + for (size_t i = 0; i < sel.size(); i++) + sel[i] = false; + } +} + +bool Burrows::isAssignedUnit(df::burrow *burrow, df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + CHECK_NULL_POINTER(burrow); + + return binsearch_index(unit->burrows, burrow->id) >= 0; +} + +void Burrows::setAssignedUnit(df::burrow *burrow, df::unit *unit, bool enable) +{ + using df::global::ui; + + CHECK_NULL_POINTER(unit); + CHECK_NULL_POINTER(burrow); + + if (enable) + { + insert_into_vector(unit->burrows, burrow->id); + insert_into_vector(burrow->units, unit->id); + } + else + { + erase_from_vector(unit->burrows, burrow->id); + erase_from_vector(burrow->units, unit->id); + } + + // Sync ui if active + if (ui && ui->main.mode == ui_sidebar_mode::Burrows && + ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) + { + int idx = linear_index(ui->burrows.list_units, unit); + if (idx >= 0) + ui->burrows.sel_units[idx] = enable; + } +} + +void Burrows::listBlocks(std::vector *pvec, df::burrow *burrow) +{ + CHECK_NULL_POINTER(burrow); + + pvec->clear(); + pvec->reserve(burrow->block_x.size()); + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + + for (size_t i = 0; i < burrow->block_x.size(); i++) + { + df::coord pos(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); + + auto block = Maps::getBlock(pos - base); + if (block) + pvec->push_back(block); + } +} + +static void destroyBurrowMask(df::block_burrow *mask) +{ + if (!mask) return; + + auto link = mask->link; + + link->prev->next = link->next; + if (link->next) + link->next->prev = link->prev; + delete link; + + delete mask; +} + +void Burrows::clearTiles(df::burrow *burrow) +{ + CHECK_NULL_POINTER(burrow); + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + + for (size_t i = 0; i < burrow->block_x.size(); i++) + { + df::coord pos(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); + + auto block = Maps::getBlock(pos - base); + if (!block) + continue; + + destroyBurrowMask(getBlockMask(burrow, block)); + } + + burrow->block_x.clear(); + burrow->block_y.clear(); + burrow->block_z.clear(); +} + +df::block_burrow *Burrows::getBlockMask(df::burrow *burrow, df::map_block *block, bool create) +{ + CHECK_NULL_POINTER(burrow); + CHECK_NULL_POINTER(block); + + int32_t id = burrow->id; + df::block_burrow_link *prev = &block->block_burrows; + df::block_burrow_link *link = prev->next; + + for (; link; prev = link, link = link->next) + if (link->item->id == id) + return link->item; + + if (create) + { + link = new df::block_burrow_link; + link->item = new df::block_burrow; + + link->item->id = burrow->id; + link->item->tile_bitmask.clear(); + link->item->link = link; + + link->next = NULL; + link->prev = prev; + prev->next = link; + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + df::coord pos = base + block->map_pos/16; + + burrow->block_x.push_back(pos.x); + burrow->block_y.push_back(pos.y); + burrow->block_z.push_back(pos.z); + + return link->item; + } + + return NULL; +} + +bool Burrows::deleteBlockMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask) +{ + CHECK_NULL_POINTER(burrow); + CHECK_NULL_POINTER(block); + + if (!mask) + return false; + + df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); + df::coord pos = base + block->map_pos/16; + + destroyBurrowMask(mask); + + for (size_t i = 0; i < burrow->block_x.size(); i++) + { + df::coord cur(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); + + if (cur == pos) + { + vector_erase_at(burrow->block_x, i); + vector_erase_at(burrow->block_y, i); + vector_erase_at(burrow->block_z, i); + + break; + } + } + + return true; +} + +bool Burrows::isAssignedBlockTile(df::burrow *burrow, df::map_block *block, df::coord2d tile) +{ + CHECK_NULL_POINTER(burrow); + + if (!block) return false; + + auto mask = getBlockMask(burrow, block); + + return mask ? mask->getassignment(tile & 15) : false; +} + +bool Burrows::setAssignedBlockTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable) +{ + CHECK_NULL_POINTER(burrow); + + if (!block) return false; + + auto mask = getBlockMask(burrow, block, enable); + + if (mask) + { + mask->setassignment(tile & 15, enable); + + if (!enable && !mask->has_assignments()) + deleteBlockMask(burrow, block, mask); + } + + return true; +} + diff --git a/library/modules/Constructions.cpp b/library/modules/Constructions.cpp index 8dc81911c..2d61c447a 100644 --- a/library/modules/Constructions.cpp +++ b/library/modules/Constructions.cpp @@ -35,9 +35,20 @@ using namespace std; #include "MemAccess.h" #include "Types.h" #include "Core.h" + #include "modules/Constructions.h" +#include "modules/Buildings.h" +#include "modules/Maps.h" + +#include "TileTypes.h" + #include "df/world.h" +#include "df/job_item.h" +#include "df/building_type.h" +#include "df/building_constructionst.h" + using namespace DFHack; +using namespace df::enums; using df::global::world; bool Constructions::isValid() @@ -52,14 +63,14 @@ uint32_t Constructions::getCount() df::construction * Constructions::getConstruction(const int32_t index) { - if (index < 0 || index >= getCount()) + if (uint32_t(index) >= getCount()) return NULL; return world->constructions[index]; } bool Constructions::copyConstruction(const int32_t index, t_construction &out) { - if (index < 0 || index >= getCount()) + if (uint32_t(index) >= getCount()) return false; out.origin = world->constructions[index]; @@ -73,3 +84,88 @@ bool Constructions::copyConstruction(const int32_t index, t_construction &out) out.original_tile = out.origin->original_tile; return true; } + +bool Constructions::designateNew(df::coord pos, df::construction_type type, + df::item_type item, int mat_index) +{ + auto ttype = Maps::getTileType(pos); + if (!ttype || tileMaterial(*ttype) == tiletype_material::CONSTRUCTION) + return false; + + auto current = Buildings::findAtTile(pos); + if (current) + { + auto cons = strict_virtual_cast(current); + if (!cons) + return false; + + cons->type = type; + return true; + } + + auto newinst = Buildings::allocInstance(pos, building_type::Construction); + if (!newinst) + return false; + + auto newcons = strict_virtual_cast(newinst); + newcons->type = type; + + df::job_item *filter = new df::job_item(); + filter->item_type = item; + filter->mat_index = mat_index; + filter->flags2.bits.building_material = true; + if (mat_index < 0) + filter->flags2.bits.non_economic = true; + + std::vector filters; + filters.push_back(filter); + + if (!Buildings::constructWithFilters(newinst, filters)) + { + delete newinst; + return false; + } + + return true; +} + +bool Constructions::designateRemove(df::coord pos, bool *immediate) +{ + using df::global::process_dig; + + if (immediate) + *immediate = false; + + if (auto current = Buildings::findAtTile(pos)) + { + auto cons = strict_virtual_cast(current); + if (!cons) + return false; + + if (Buildings::deconstruct(cons)) + { + if (immediate) + *immediate = true; + } + + return true; + } + + auto block = Maps::getTileBlock(pos); + if (!block) + return false; + + auto ttype = block->tiletype[pos.x&15][pos.y&15]; + + if (tileMaterial(ttype) == tiletype_material::CONSTRUCTION) + { + auto &dsgn = block->designation[pos.x&15][pos.y&15]; + dsgn.bits.dig = tile_dig_designation::Default; + block->flags.bits.designated = true; + if (process_dig) + *process_dig = true; + return true; + } + + return false; +} diff --git a/library/modules/Engravings.cpp b/library/modules/Engravings.cpp index 3621bc736..bff17ef5f 100644 --- a/library/modules/Engravings.cpp +++ b/library/modules/Engravings.cpp @@ -53,14 +53,14 @@ uint32_t Engravings::getCount() df::engraving * Engravings::getEngraving(int index) { - if (index < 0 || index >= getCount()) + if (uint32_t(index) >= getCount()) return NULL; return world->engravings[index]; } bool Engravings::copyEngraving(const int32_t index, t_engraving &out) { - if (index < 0 || index >= getCount()) + if (uint32_t(index) >= getCount()) return false; out.origin = world->engravings[index]; diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 592f27c0b..2d9402fca 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -50,6 +50,11 @@ using namespace DFHack; #include "df/viewscreen_joblistst.h" #include "df/viewscreen_unitlistst.h" #include "df/viewscreen_itemst.h" +#include "df/viewscreen_layerst.h" +#include "df/viewscreen_layer_workshop_profilest.h" +#include "df/viewscreen_layer_noblelistst.h" +#include "df/viewscreen_layer_overall_healthst.h" +#include "df/viewscreen_petst.h" #include "df/ui_unit_view_mode.h" #include "df/ui_sidebar_menus.h" #include "df/ui_look_list.h" @@ -63,12 +68,18 @@ using namespace DFHack; #include "df/popup_message.h" #include "df/interfacest.h" #include "df/graphic.h" +#include "df/layer_object_listst.h" using namespace df::enums; using df::global::gview; using df::global::init; using df::global::gps; +static df::layer_object_listst *getLayerList(df::viewscreen_layerst *layer, int idx) +{ + return virtual_cast(vector_get(layer->layer_objects,idx)); +} + // Predefined common guard functions bool Gui::default_hotkey(df::viewscreen *top) @@ -194,7 +205,7 @@ bool Gui::view_unit_hotkey(df::viewscreen *top) if (!ui_selected_unit) // allow missing return false; - return vector_get(world->units.other[0], *ui_selected_unit) != NULL; + return vector_get(world->units.active, *ui_selected_unit) != NULL; } bool Gui::unit_inventory_hotkey(df::viewscreen *top) @@ -223,7 +234,7 @@ df::job *Gui::getSelectedWorkshopJob(color_ostream &out, bool quiet) df::building *selected = world->selected_building; int idx = *ui_workshop_job_cursor; - if (idx < 0 || idx >= selected->jobs.size()) + if (size_t(idx) >= selected->jobs.size()) { out.printerr("Invalid job cursor index: %d\n", idx); return NULL; @@ -294,6 +305,62 @@ static df::unit *getAnyUnit(df::viewscreen *top) return ref ? ref->getUnit() : NULL; } + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_workshop_profilest, top)) + { + if (auto list1 = getLayerList(screen, 0)) + return vector_get(screen->workers, list1->cursor); + return NULL; + } + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_noblelistst, top)) + { + switch (screen->mode) + { + case df::viewscreen_layer_noblelistst::List: + if (auto list1 = getLayerList(screen, 0)) + { + if (auto info = vector_get(screen->info, list1->cursor)) + return info->unit; + } + return NULL; + + case df::viewscreen_layer_noblelistst::Appoint: + if (auto list2 = getLayerList(screen, 1)) + { + if (auto info = vector_get(screen->candidates, list2->cursor)) + return info->unit; + } + return NULL; + + default: + return NULL; + } + } + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_petst, top)) + { + switch (screen->mode) + { + case df::viewscreen_petst::List: + if (!vector_get(screen->is_vermin, screen->cursor)) + return (df::unit*)vector_get(screen->animal, screen->cursor); + return NULL; + + case df::viewscreen_petst::SelectTrainer: + return vector_get(screen->trainer_unit, screen->trainer_cursor); + + default: + return NULL; + } + } + + if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_overall_healthst, top)) + { + if (auto list1 = getLayerList(screen, 0)) + return vector_get(screen->unit, list1->cursor); + return NULL; + } + if (!Gui::dwarfmode_hotkey(top)) return NULL; @@ -303,7 +370,7 @@ static df::unit *getAnyUnit(df::viewscreen *top) if (!ui_selected_unit) return NULL; - return vector_get(world->units.other[0], *ui_selected_unit); + return vector_get(world->units.active, *ui_selected_unit); } case LookAround: { diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index 9d0657e19..f1d495bf1 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -421,18 +421,25 @@ bool Items::copyItem(df::item * itembase, DFHack::dfh_item &item) return true; } -df::unit *Items::getOwner(df::item * item) +df::general_ref *Items::getGeneralRef(df::item *item, df::general_ref_type type) { CHECK_NULL_POINTER(item); - for (size_t i = 0; i < item->itemrefs.size(); i++) - { - df::general_ref *ref = item->itemrefs[i]; - if (strict_virtual_cast(ref)) - return ref->getUnit(); - } + return findRef(item->itemrefs, type); +} + +df::specific_ref *Items::getSpecificRef(df::item *item, df::specific_ref_type type) +{ + CHECK_NULL_POINTER(item); + + return findRef(item->specific_refs, type); +} + +df::unit *Items::getOwner(df::item * item) +{ + auto ref = getGeneralRef(item, general_ref_type::UNIT_ITEMOWNER); - return NULL; + return ref ? ref->getUnit() : NULL; } bool Items::setOwner(df::item *item, df::unit *unit) @@ -478,16 +485,9 @@ bool Items::setOwner(df::item *item, df::unit *unit) df::item *Items::getContainer(df::item * item) { - CHECK_NULL_POINTER(item); + auto ref = getGeneralRef(item, general_ref_type::CONTAINED_IN_ITEM); - for (size_t i = 0; i < item->itemrefs.size(); i++) - { - df::general_ref *ref = item->itemrefs[i]; - if (ref->getType() == general_ref_type::CONTAINED_IN_ITEM) - return ref->getItem(); - } - - return NULL; + return ref ? ref->getItem() : NULL; } void Items::getContainedItems(df::item *item, std::vector *items) @@ -536,6 +536,9 @@ df::coord Items::getPosition(df::item *item) if (auto bld = ref->getBuilding()) return df::coord(bld->centerx, bld->centery, bld->z); break; + + default: + break; } } } @@ -543,19 +546,6 @@ df::coord Items::getPosition(df::item *item) return item->pos; } -static void removeRef(std::vector &vec, df::general_ref_type type, int id) -{ - for (int i = vec.size()-1; i >= 0; i--) - { - df::general_ref *ref = vec[i]; - if (ref->getType() != type || ref->getID() != id) - continue; - - vector_erase_at(vec, i); - delete ref; - } -} - static bool detachItem(MapExtras::MapCache &mc, df::item *item) { if (item->flags.bits.on_ground) diff --git a/library/modules/Job.cpp b/library/modules/Job.cpp index 5ce6647d9..149707c6e 100644 --- a/library/modules/Job.cpp +++ b/library/modules/Job.cpp @@ -46,6 +46,7 @@ using namespace std; #include "df/job.h" #include "df/job_item.h" #include "df/job_list_link.h" +#include "df/specific_ref.h" #include "df/general_ref.h" #include "df/general_ref_unit_workerst.h" #include "df/general_ref_building_holderst.h" @@ -67,7 +68,7 @@ df::job *DFHack::Job::cloneJobStruct(df::job *job) pnew->list_link = NULL; pnew->completion_timer = -1; pnew->items.clear(); - pnew->misc_links.clear(); + pnew->specific_refs.clear(); // Clone refs for (int i = pnew->references.size()-1; i >= 0; i--) @@ -93,7 +94,7 @@ void DFHack::Job::deleteJobStruct(df::job *job) return; // Only allow free-floating job structs - assert(!job->list_link && job->items.empty() && job->misc_links.empty()); + assert(!job->list_link && job->items.empty() && job->specific_refs.empty()); for (int i = job->references.size()-1; i >= 0; i--) delete job->references[i]; @@ -311,3 +312,40 @@ bool DFHack::Job::listNewlyCreated(std::vector *pvec, int *id_var) return true; } + +bool DFHack::Job::attachJobItem(df::job *job, df::item *item, + df::job_item_ref::T_role role, + int filter_idx, int insert_idx) +{ + CHECK_NULL_POINTER(job); + CHECK_NULL_POINTER(item); + + /* + * Functionality 100% reverse-engineered from DF code. + */ + + if (role != df::job_item_ref::TargetContainer) + { + if (item->flags.bits.in_job) + return false; + + item->flags.bits.in_job = true; + } + + auto item_link = new df::specific_ref(); + item_link->type = specific_ref_type::JOB; + item_link->job = job; + item->specific_refs.push_back(item_link); + + auto job_link = new df::job_item_ref(); + job_link->item = item; + job_link->role = role; + job_link->job_item_idx = filter_idx; + + if (size_t(insert_idx) < job->items.size()) + vector_insert_at(job->items, insert_idx, job_link); + else + job->items.push_back(job_link); + + return true; +} diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 327e26986..69f591a0f 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -51,6 +51,9 @@ using namespace std; #include "df/burrow.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" +#include "df/world_region_details.h" +#include "df/builtin_mats.h" +#include "df/block_square_event_grassst.h" using namespace DFHack; using namespace df::enums; @@ -142,6 +145,24 @@ df::map_block *Maps::getTileBlock (int32_t x, int32_t y, int32_t z) return world->map.block_index[x >> 4][y >> 4][z]; } +df::tiletype *Maps::getTileType(int32_t x, int32_t y, int32_t z) +{ + df::map_block *block = getTileBlock(x,y,z); + return block ? &block->tiletype[x&15][y&15] : NULL; +} + +df::tile_designation *Maps::getTileDesignation(int32_t x, int32_t y, int32_t z) +{ + df::map_block *block = getTileBlock(x,y,z); + return block ? &block->designation[x&15][y&15] : NULL; +} + +df::tile_occupancy *Maps::getTileOccupancy(int32_t x, int32_t y, int32_t z) +{ + df::map_block *block = getTileBlock(x,y,z); + return block ? &block->occupancy[x&15][y&15] : NULL; +} + df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos) { auto data = world->world_data; @@ -158,7 +179,7 @@ df::world_data::T_region_map *Maps::getRegionBiome(df::coord2d rgn_pos) df::feature_init *Maps::getGlobalInitFeature(int32_t index) { auto data = world->world_data; - if (!data) + if (!data || index < 0) return NULL; auto rgn = vector_get(data->underground_regions, index); @@ -186,7 +207,7 @@ bool Maps::GetGlobalFeature(t_feature &feature, int32_t index) df::feature_init *Maps::getLocalInitFeature(df::coord2d rgn_pos, int32_t index) { auto data = world->world_data; - if (!data) + if (!data || index < 0) return NULL; if (rgn_pos.x < 0 || rgn_pos.x >= data->world_width || @@ -353,7 +374,7 @@ bool Maps::ReadGeology(vector > *layer_mats, vector int bioRX = world->map.region_x / 16 + ((i % 3) - 1); int bioRY = world->map.region_y / 16 + ((i / 3) - 1); - df::coord2d rgn_pos(clip_range(bioRX,0,world_width-1),clip_range(bioRX,0,world_height-1)); + df::coord2d rgn_pos(clip_range(bioRX,0,world_width-1),clip_range(bioRY,0,world_height-1)); (*geoidx)[i] = rgn_pos; @@ -384,12 +405,26 @@ bool Maps::ReadGeology(vector > *layer_mats, vector return true; } +bool Maps::canWalkBetween(df::coord pos1, df::coord pos2) +{ + auto block1 = getTileBlock(pos1); + auto block2 = getTileBlock(pos2); + + if (!block1 || !block2) + return false; + + auto tile1 = MapExtras::index_tile(block1->walkable, pos1); + auto tile2 = MapExtras::index_tile(block2->walkable, pos2); + + return tile1 && tile1 == tile2; +} + #define COPY(a,b) memcpy(&a,&b,sizeof(a)) MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) { dirty_designations = false; - dirty_tiletypes = false; + dirty_tiles = false; dirty_temperatures = false; dirty_blockflags = false; dirty_occupancies = false; @@ -397,12 +432,12 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) bcoord = _bcoord; block = Maps::getBlock(bcoord); item_counts = NULL; - - memset(tags,0,sizeof(tags)); + tags = NULL; + tiles = NULL; + basemats = NULL; if(block) { - COPY(rawtiles, block->tiletype); COPY(designation, block->designation); COPY(occupancy, block->occupancy); blockflags = block->flags; @@ -410,33 +445,189 @@ MapExtras::Block::Block(MapCache *parent, DFCoord _bcoord) : parent(parent) COPY(temp1, block->temperature_1); COPY(temp2, block->temperature_2); - SquashVeins(block,veinmats); - SquashConstructions(block, contiles); - SquashFrozenLiquids(block, icetiles); - if(parent->validgeo) - SquashRocks(block,basemats,&parent->layer_mats); - else - memset(basemats,-1,sizeof(basemats)); valid = true; } else { blockflags.whole = 0; - memset(rawtiles,0,sizeof(rawtiles)); memset(designation,0,sizeof(designation)); memset(occupancy,0,sizeof(occupancy)); memset(temp1,0,sizeof(temp1)); memset(temp2,0,sizeof(temp2)); - memset(veinmats,-1,sizeof(veinmats)); - memset(contiles,0,sizeof(contiles)); - memset(icetiles,0,sizeof(icetiles)); - memset(basemats,-1,sizeof(basemats)); } } MapExtras::Block::~Block() { delete[] item_counts; + delete[] tags; + delete tiles; + delete basemats; +} + +void MapExtras::Block::init_tags() +{ + if (!tags) + tags = new T_tags[16]; + memset(tags,0,sizeof(T_tags)*16); +} + +void MapExtras::Block::init_tiles(bool basemat) +{ + if (!tiles) + { + tiles = new TileInfo(); + + if (block) + ParseTiles(tiles); + } + + if (basemat && !basemats) + { + basemats = new BasematInfo(); + + if (block) + ParseBasemats(tiles, basemats); + } +} + +MapExtras::Block::TileInfo::TileInfo() +{ + frozen.clear(); + dirty_raw.clear(); + memset(raw_tiles,0,sizeof(raw_tiles)); + con_info = NULL; + dirty_base.clear(); + memset(base_tiles,0,sizeof(base_tiles)); +} + +MapExtras::Block::TileInfo::~TileInfo() +{ + delete con_info; +} + +void MapExtras::Block::TileInfo::init_coninfo() +{ + if (con_info) + return; + + con_info = new ConInfo(); + con_info->constructed.clear(); + COPY(con_info->tiles, base_tiles); + memset(con_info->mattype, -1, sizeof(con_info->mattype)); + memset(con_info->matindex, -1, sizeof(con_info->matindex)); +} + +MapExtras::Block::BasematInfo::BasematInfo() +{ + dirty.clear(); + memset(mattype,0,sizeof(mattype)); + memset(matindex,-1,sizeof(matindex)); + memset(layermat,-1,sizeof(layermat)); +} + +bool MapExtras::Block::setTiletypeAt(df::coord2d pos, df::tiletype tt, bool force) +{ + if (!block) + return false; + + if (!basemats) + init_tiles(true); + + pos = pos & 15; + + dirty_tiles = true; + tiles->raw_tiles[pos.x][pos.y] = tt; + tiles->dirty_raw.setassignment(pos, true); + + return true; +} + +void MapExtras::Block::ParseTiles(TileInfo *tiles) +{ + tiletypes40d icetiles; + BlockInfo::SquashFrozenLiquids(block, icetiles); + + COPY(tiles->raw_tiles, block->tiletype); + + for (int x = 0; x < 16; x++) + { + for (int y = 0; y < 16; y++) + { + using namespace df::enums::tiletype_material; + + df::tiletype tt = tiles->raw_tiles[x][y]; + df::coord coord = block->map_pos + df::coord(x,y,0); + + // Frozen liquid comes topmost + if (tileMaterial(tt) == FROZEN_LIQUID) + { + tiles->frozen.setassignment(x,y,true); + if (icetiles[x][y] != tiletype::Void) + { + tt = icetiles[x][y]; + } + } + + // The next layer may be construction + bool is_con = false; + + if (tileMaterial(tt) == CONSTRUCTION) + { + df::construction *con = df::construction::find(coord); + if (con) + { + if (!tiles->con_info) + tiles->init_coninfo(); + + is_con = true; + tiles->con_info->constructed.setassignment(x,y,true); + tiles->con_info->tiles[x][y] = tt; + tiles->con_info->mattype[x][y] = con->mat_type; + tiles->con_info->matindex[x][y] = con->mat_index; + + tt = con->original_tile; + } + } + + // Finally, base material + tiles->base_tiles[x][y] = tt; + + // Copy base info back to construction layer + if (tiles->con_info && !is_con) + tiles->con_info->tiles[x][y] = tt; + } + } +} + +void MapExtras::Block::ParseBasemats(TileInfo *tiles, BasematInfo *bmats) +{ + BlockInfo info; + + info.prepare(this); + + COPY(bmats->layermat, info.basemats); + + for (int x = 0; x < 16; x++) + { + for (int y = 0; y < 16; y++) + { + using namespace df::enums::tiletype_material; + + auto tt = tiles->base_tiles[x][y]; + auto mat = info.getBaseMaterial(tt, df::coord2d(x,y)); + + bmats->mattype[x][y] = mat.mat_type; + bmats->matindex[x][y] = mat.mat_index; + + // Copy base info back to construction layer + if (tiles->con_info && !tiles->con_info->constructed.getassignment(x,y)) + { + tiles->con_info->mattype[x][y] = mat.mat_type; + tiles->con_info->matindex[x][y] = mat.mat_index; + } + } + } } bool MapExtras::Block::Write () @@ -454,10 +645,21 @@ bool MapExtras::Block::Write () block->flags.bits.designated = true; dirty_designations = false; } - if(dirty_tiletypes) + if(dirty_tiles && tiles) { - COPY(block->tiletype, rawtiles); - dirty_tiletypes = false; + dirty_tiles = false; + + for (int x = 0; x < 16; x++) + { + for (int y = 0; y < 16; y++) + { + if (tiles->dirty_raw.getassignment(x,y)) + block->tiletype[x][y] = tiles->raw_tiles[x][y]; + } + } + + delete tiles; tiles = NULL; + delete basemats; basemats = NULL; } if(dirty_temperatures) { @@ -473,65 +675,187 @@ bool MapExtras::Block::Write () return true; } -void MapExtras::Block::SquashVeins(df::map_block *mb, t_blockmaterials & materials) +void MapExtras::BlockInfo::prepare(Block *mblock) { - memset(materials,-1,sizeof(materials)); - std::vector veins; - Maps::SortBlockEvents(mb,&veins); - for (uint32_t x = 0;x<16;x++) for (uint32_t y = 0; y< 16;y++) + this->mblock = mblock; + + block = mblock->getRaw(); + parent = mblock->getParent(); + + SquashVeins(block,veinmats); + SquashGrass(block, grass); + + if (parent->validgeo) + SquashRocks(block,basemats,&parent->layer_mats); + else + memset(basemats,-1,sizeof(basemats)); + + for (size_t i = 0; i < block->plants.size(); i++) { - df::tiletype tt = mb->tiletype[x][y]; - if (tileMaterial(tt) == tiletype_material::MINERAL) + auto pp = block->plants[i]; + plants[pp->pos] = pp; + } + + global_feature = Maps::getGlobalInitFeature(block->global_feature); + local_feature = Maps::getLocalInitFeature(block->region_pos, block->local_feature); +} + +t_matpair MapExtras::BlockInfo::getBaseMaterial(df::tiletype tt, df::coord2d pos) +{ + using namespace df::enums::tiletype_material; + + t_matpair rv(0,-1); + int x = pos.x, y = pos.y; + + switch (tileMaterial(tt)) { + case NONE: + case AIR: + rv.mat_type = -1; + break; + + case DRIFTWOOD: + case SOIL: + { + rv.mat_index = basemats[x][y]; + + if (auto raw = df::inorganic_raw::find(rv.mat_index)) { - for (size_t i = 0; i < veins.size(); i++) + if (raw->flags.is_set(inorganic_flags::SOIL_ANY)) + break; + + int biome = mblock->biomeIndexAt(pos); + int idx = vector_get(parent->default_soil, biome, -1); + if (idx >= 0) + rv.mat_index = idx; + } + + break; + } + + case STONE: + { + rv.mat_index = basemats[x][y]; + + if (auto raw = df::inorganic_raw::find(rv.mat_index)) + { + if (!raw->flags.is_set(inorganic_flags::SOIL_ANY)) + break; + + int biome = mblock->biomeIndexAt(pos); + int idx = vector_get(parent->default_stone, biome, -1); + if (idx >= 0) + rv.mat_index = idx; + } + + break; + } + + case MINERAL: + rv.mat_index = veinmats[x][y]; + break; + + case LAVA_STONE: + if (auto details = parent->region_details[mblock->biomeRegionAt(pos)]) + rv.mat_index = details->lava_stone; + break; + + case PLANT: + rv.mat_type = MaterialInfo::PLANT_BASE; + if (auto plant = plants[block->map_pos + df::coord(x,y,0)]) + { + if (auto raw = df::plant_raw::find(plant->material)) { - if (veins[i]->getassignment(x,y)) - materials[x][y] = veins[i]->inorganic_mat; + rv.mat_type = raw->material_defs.type_basic_mat; + rv.mat_index = raw->material_defs.idx_basic_mat; } } + break; + + case GRASS_LIGHT: + case GRASS_DARK: + case GRASS_DRY: + case GRASS_DEAD: + rv.mat_type = MaterialInfo::PLANT_BASE; + if (auto raw = df::plant_raw::find(grass[x][y])) + { + rv.mat_type = raw->material_defs.type_basic_mat; + rv.mat_index = raw->material_defs.idx_basic_mat; + } + break; + + case FEATURE: + { + auto dsgn = block->designation[x][y]; + + if (dsgn.bits.feature_local && local_feature) + local_feature->getMaterial(&rv.mat_type, &rv.mat_index); + else if (dsgn.bits.feature_global && global_feature) + global_feature->getMaterial(&rv.mat_type, &rv.mat_index); + + break; + } + + case CONSTRUCTION: // just a fallback + case MAGMA: + case HFS: + // use generic 'rock' + break; + + case FROZEN_LIQUID: + rv.mat_type = builtin_mats::WATER; + break; + + case POOL: + case BROOK: + case RIVER: + rv.mat_index = basemats[x][y]; + break; + + case ASHES: + case FIRE: + case CAMPFIRE: + rv.mat_type = builtin_mats::ASH; + break; } + + return rv; } -void MapExtras::Block::SquashFrozenLiquids(df::map_block *mb, tiletypes40d & frozen) +void MapExtras::BlockInfo::SquashVeins(df::map_block *mb, t_blockmaterials & materials) { - std::vector ices; - Maps::SortBlockEvents(mb,NULL,&ices); - for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) + std::vector veins; + Maps::SortBlockEvents(mb,&veins); + memset(materials,-1,sizeof(materials)); + for (uint32_t x = 0;x<16;x++) for (uint32_t y = 0; y< 16;y++) { - df::tiletype tt = mb->tiletype[x][y]; - frozen[x][y] = tiletype::Void; - if (tileMaterial(tt) == tiletype_material::FROZEN_LIQUID) + for (size_t i = 0; i < veins.size(); i++) { - for (size_t i = 0; i < ices.size(); i++) - { - df::tiletype tt2 = ices[i]->tiles[x][y]; - if (tt2 != tiletype::Void) - { - frozen[x][y] = tt2; - break; - } - } + if (veins[i]->getassignment(x,y)) + materials[x][y] = veins[i]->inorganic_mat; } } } -void MapExtras::Block::SquashConstructions (df::map_block *mb, tiletypes40d & constructions) +void MapExtras::BlockInfo::SquashFrozenLiquids(df::map_block *mb, tiletypes40d & frozen) { + std::vector ices; + Maps::SortBlockEvents(mb,NULL,&ices); + memset(frozen,0,sizeof(frozen)); for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) { - df::tiletype tt = mb->tiletype[x][y]; - constructions[x][y] = tiletype::Void; - if (tileMaterial(tt) == tiletype_material::CONSTRUCTION) + for (size_t i = 0; i < ices.size(); i++) { - DFCoord coord = mb->map_pos + df::coord(x,y,0); - df::construction *con = df::construction::find(coord); - if (con) - constructions[x][y] = con->original_tile; + df::tiletype tt2 = ices[i]->tiles[x][y]; + if (tt2 != tiletype::Void) + { + frozen[x][y] = tt2; + break; + } } } } -void MapExtras::Block::SquashRocks (df::map_block *mb, t_blockmaterials & materials, +void MapExtras::BlockInfo::SquashRocks (df::map_block *mb, t_blockmaterials & materials, std::vector< std::vector > * layerassign) { // get the layer materials @@ -547,18 +871,49 @@ void MapExtras::Block::SquashRocks (df::map_block *mb, t_blockmaterials & materi } } -df::coord2d MapExtras::Block::biomeRegionAt(df::coord2d p) +void MapExtras::BlockInfo::SquashGrass(df::map_block *mb, t_blockmaterials &materials) +{ + std::vector grasses; + Maps::SortBlockEvents(mb, NULL, NULL, NULL, &grasses); + memset(materials,-1,sizeof(materials)); + for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++) + { + int amount = 0; + for (size_t i = 0; i < grasses.size(); i++) + { + if (grasses[i]->amount[x][y] >= amount) + { + amount = grasses[i]->amount[x][y]; + materials[x][y] = grasses[i]->plant_index; + } + } + } +} + +int MapExtras::Block::biomeIndexAt(df::coord2d p) { if (!block) - return df::coord2d(-30000,-30000); + return -1; auto des = index_tile(designation,p); uint8_t idx = des.bits.biome; if (idx >= 9) - return block->region_pos; + return -1; idx = block->region_offset[idx]; if (idx >= parent->geoidx.size()) + return -1; + return idx; +} + +df::coord2d MapExtras::Block::biomeRegionAt(df::coord2d p) +{ + if (!block) + return df::coord2d(-30000,-30000); + + int idx = biomeIndexAt(p); + if (idx < 0) return block->region_pos; + return parent->geoidx[idx]; } @@ -662,191 +1017,74 @@ bool MapExtras::Block::removeItemOnGround(df::item *item) return true; } -MapExtras::Block *MapExtras::MapCache::BlockAt(DFCoord blockcoord) +MapExtras::MapCache::MapCache() { - if(!valid) - return 0; - std::map ::iterator iter = blocks.find(blockcoord); - if(iter != blocks.end()) - { - return (*iter).second; - } - else + valid = 0; + Maps::getSize(x_bmax, y_bmax, z_max); + x_tmax = x_bmax*16; y_tmax = y_bmax*16; + validgeo = Maps::ReadGeology(&layer_mats, &geoidx); + valid = true; + + if (auto data = df::global::world->world_data) { - if(blockcoord.x >= 0 && blockcoord.x < x_bmax && - blockcoord.y >= 0 && blockcoord.y < y_bmax && - blockcoord.z >= 0 && blockcoord.z < z_max) + for (size_t i = 0; i < data->region_details.size(); i++) { - Block * nblo = new Block(this, blockcoord); - blocks[blockcoord] = nblo; - return nblo; + auto info = data->region_details[i]; + region_details[info->pos] = info; } - return 0; - } -} - -df::burrow *Maps::findBurrowByName(std::string name) -{ - auto &vec = df::burrow::get_vector(); - - for (size_t i = 0; i < vec.size(); i++) - if (vec[i]->name == name) - return vec[i]; - - return NULL; -} - -void Maps::listBurrowBlocks(std::vector *pvec, df::burrow *burrow) -{ - CHECK_NULL_POINTER(burrow); - - pvec->clear(); - pvec->reserve(burrow->block_x.size()); - - df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); - - for (size_t i = 0; i < burrow->block_x.size(); i++) - { - df::coord pos(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); - - auto block = getBlock(pos - base); - if (block) - pvec->push_back(block); } -} - -static void destroyBurrowMask(df::block_burrow *mask) -{ - if (!mask) return; - - auto link = mask->link; - link->prev->next = link->next; - if (link->next) - link->next->prev = link->prev; - delete link; - - delete mask; -} - -void Maps::clearBurrowTiles(df::burrow *burrow) -{ - CHECK_NULL_POINTER(burrow); + default_soil.resize(layer_mats.size()); + default_stone.resize(layer_mats.size()); - df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); - - for (size_t i = 0; i < burrow->block_x.size(); i++) + for (size_t i = 0; i < layer_mats.size(); i++) { - df::coord pos(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); - - auto block = getBlock(pos - base); - if (!block) - continue; + default_soil[i] = -1; + default_stone[i] = -1; - destroyBurrowMask(getBlockBurrowMask(burrow, block)); + for (size_t j = 0; j < layer_mats[i].size(); j++) + { + auto raw = df::inorganic_raw::find(layer_mats[i][j]); + if (!raw) + continue; + + bool is_soil = raw->flags.is_set(inorganic_flags::SOIL_ANY); + if (is_soil) + default_soil[i] = layer_mats[i][j]; + else if (default_stone[i] == -1) + default_stone[i] = layer_mats[i][j]; + } } - - burrow->block_x.clear(); - burrow->block_y.clear(); - burrow->block_z.clear(); } -df::block_burrow *Maps::getBlockBurrowMask(df::burrow *burrow, df::map_block *block, bool create) +MapExtras::Block *MapExtras::MapCache::BlockAt(DFCoord blockcoord) { - CHECK_NULL_POINTER(burrow); - CHECK_NULL_POINTER(block); - - int32_t id = burrow->id; - df::block_burrow_link *prev = &block->block_burrows; - df::block_burrow_link *link = prev->next; - - for (; link; prev = link, link = link->next) - if (link->item->id == id) - return link->item; - - if (create) + if(!valid) + return 0; + std::map ::iterator iter = blocks.find(blockcoord); + if(iter != blocks.end()) { - link = new df::block_burrow_link; - link->item = new df::block_burrow; - - link->item->id = burrow->id; - memset(link->item->tile_bitmask,0,sizeof(link->item->tile_bitmask)); - link->item->link = link; - - link->next = NULL; - link->prev = prev; - prev->next = link; - - df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); - df::coord pos = base + block->map_pos/16; - - burrow->block_x.push_back(pos.x); - burrow->block_y.push_back(pos.y); - burrow->block_z.push_back(pos.z); - - return link->item; + return (*iter).second; } - - return NULL; -} - -bool Maps::deleteBlockBurrowMask(df::burrow *burrow, df::map_block *block, df::block_burrow *mask) -{ - CHECK_NULL_POINTER(burrow); - CHECK_NULL_POINTER(block); - - if (!mask) - return false; - - df::coord base(world->map.region_x*3,world->map.region_y*3,world->map.region_z); - df::coord pos = base + block->map_pos/16; - - destroyBurrowMask(mask); - - for (size_t i = 0; i < burrow->block_x.size(); i++) + else { - df::coord cur(burrow->block_x[i], burrow->block_y[i], burrow->block_z[i]); - - if (cur == pos) + if(unsigned(blockcoord.x) < x_bmax && + unsigned(blockcoord.y) < y_bmax && + unsigned(blockcoord.z) < z_max) { - vector_erase_at(burrow->block_x, i); - vector_erase_at(burrow->block_y, i); - vector_erase_at(burrow->block_z, i); - - break; + Block * nblo = new Block(this, blockcoord); + blocks[blockcoord] = nblo; + return nblo; } + return 0; } - - return true; } -bool Maps::isBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile) +void MapExtras::MapCache::resetTags() { - CHECK_NULL_POINTER(burrow); - - if (!block) return false; - - auto mask = getBlockBurrowMask(burrow, block); - - return mask ? mask->getassignment(tile & 15) : false; -} - -bool Maps::setBlockBurrowTile(df::burrow *burrow, df::map_block *block, df::coord2d tile, bool enable) -{ - CHECK_NULL_POINTER(burrow); - - if (!block) return false; - - auto mask = getBlockBurrowMask(burrow, block, enable); - - if (mask) + for (auto it = blocks.begin(); it != blocks.end(); ++it) { - mask->setassignment(tile & 15, enable); - - if (!enable && !mask->has_assignments()) - deleteBlockBurrowMask(burrow, block, mask); + delete[] it->second->tags; + it->second->tags = NULL; } - - return true; } - diff --git a/library/modules/Materials.cpp b/library/modules/Materials.cpp index 0172e24f8..f49dd82e2 100644 --- a/library/modules/Materials.cpp +++ b/library/modules/Materials.cpp @@ -80,7 +80,7 @@ bool MaterialInfo::decode(df::item *item) bool MaterialInfo::decode(const df::material_vec_ref &vr, int idx) { - if (idx < 0 || idx >= vr.mat_type.size() || idx >= vr.mat_index.size()) + if (size_t(idx) >= vr.mat_type.size() || size_t(idx) >= vr.mat_index.size()) return decode(-1); else return decode(vr.mat_type[idx], vr.mat_index[idx]); @@ -103,7 +103,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) df::world_raws &raws = world->raws; - if (type >= sizeof(raws.mat_table.builtin)/sizeof(void*)) + if (size_t(type) >= sizeof(raws.mat_table.builtin)/sizeof(void*)) return false; if (index < 0) @@ -127,7 +127,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) mode = Creature; subtype = type-CREATURE_BASE; creature = df::creature_raw::find(index); - if (!creature || subtype >= creature->material.size()) + if (!creature || size_t(subtype) >= creature->material.size()) return false; material = creature->material[subtype]; } @@ -139,7 +139,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) if (!figure) return false; creature = df::creature_raw::find(figure->race); - if (!creature || subtype >= creature->material.size()) + if (!creature || size_t(subtype) >= creature->material.size()) return false; material = creature->material[subtype]; } @@ -148,7 +148,7 @@ bool MaterialInfo::decode(int16_t type, int32_t index) mode = Plant; subtype = type-PLANT_BASE; plant = df::plant_raw::find(index); - if (!plant || subtype >= plant->material.size()) + if (!plant || size_t(subtype) >= plant->material.size()) return false; material = plant->material[subtype]; } diff --git a/library/modules/Translation.cpp b/library/modules/Translation.cpp index 6b055a4ac..db1305161 100644 --- a/library/modules/Translation.cpp +++ b/library/modules/Translation.cpp @@ -81,15 +81,32 @@ bool Translation::copyName(df::language_name * source, df::language_name * targe return true; } +std::string Translation::capitalize(const std::string &str, bool all_words) +{ + string upper = str; + + if (!upper.empty()) + { + upper[0] = toupper(upper[0]); + + if (all_words) + { + for (size_t i = 1; i < upper.size(); i++) + if (isspace(upper[i-1])) + upper[i] = toupper(upper[i]); + } + } + + return upper; +} + void addNameWord (string &out, const string &word) { if (word.empty()) return; - string upper = word; - upper[0] = toupper(upper[0]); if (out.length() > 0) out.append(" "); - out.append(upper); + out.append(Translation::capitalize(word)); } void Translation::setNickname(df::language_name *name, std::string nick) diff --git a/library/modules/Units.cpp b/library/modules/Units.cpp index 8b44d4cf9..6c417b6e1 100644 --- a/library/modules/Units.cpp +++ b/library/modules/Units.cpp @@ -55,8 +55,13 @@ using namespace std; #include "df/historical_entity.h" #include "df/historical_figure.h" #include "df/historical_figure_info.h" +#include "df/entity_position.h" +#include "df/entity_position_assignment.h" +#include "df/histfig_entity_link_positionst.h" #include "df/assumed_identity.h" #include "df/burrow.h" +#include "df/creature_raw.h" +#include "df/caste_raw.h" using namespace DFHack; using namespace df::enums; @@ -78,7 +83,7 @@ df::unit * Units::GetCreature (const int32_t index) if (!isValid()) return NULL; // read pointer from vector at position - if(index > world->units.all.size()) + if(size_t(index) > world->units.all.size()) return 0; return world->units.all[index]; } @@ -146,7 +151,7 @@ void Units::CopyCreature(df::unit * source, t_unit & furball) // mood stuff furball.mood = source->mood; - furball.mood_skill = source->job.unk_2f8; // FIXME: really? More like currently used skill anyway. + furball.mood_skill = source->job.mood_skill; // FIXME: really? More like currently used skill anyway. Translation::readName(furball.artifact_name, &source->status.artifact_name); // labors @@ -529,6 +534,23 @@ df::item *Units::getContainer(df::unit *unit) return NULL; } +static df::assumed_identity *getFigureIdentity(df::historical_figure *figure) +{ + if (figure && figure->info && figure->info->reputation) + return df::assumed_identity::find(figure->info->reputation->cur_identity); + + return NULL; +} + +df::assumed_identity *Units::getIdentity(df::unit *unit) +{ + CHECK_NULL_POINTER(unit); + + df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); + + return getFigureIdentity(figure); +} + void Units::setNickname(df::unit *unit, std::string nick) { CHECK_NULL_POINTER(unit); @@ -547,25 +569,19 @@ void Units::setNickname(df::unit *unit, std::string nick) { Translation::setNickname(&figure->name, nick); - // v0.34.01: added the vampire's assumed identity - if (figure->info && figure->info->reputation) + if (auto identity = getFigureIdentity(figure)) { - auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); + auto id_hfig = df::historical_figure::find(identity->histfig_id); - if (identity) + if (id_hfig) { - auto id_hfig = df::historical_figure::find(identity->histfig_id); - - if (id_hfig) - { - // Even DF doesn't do this bit, because it's apparently - // only used for demons masquerading as gods, so you - // can't ever change their nickname in-game. - Translation::setNickname(&id_hfig->name, nick); - } - else - Translation::setNickname(&identity->name, nick); + // Even DF doesn't do this bit, because it's apparently + // only used for demons masquerading as gods, so you + // can't ever change their nickname in-game. + Translation::setNickname(&id_hfig->name, nick); } + else + Translation::setNickname(&identity->name, nick); } } } @@ -574,25 +590,14 @@ df::language_name *Units::getVisibleName(df::unit *unit) { CHECK_NULL_POINTER(unit); - df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id); - - if (figure) + if (auto identity = getIdentity(unit)) { - // v0.34.01: added the vampire's assumed identity - if (figure->info && figure->info->reputation) - { - auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity); + auto id_hfig = df::historical_figure::find(identity->histfig_id); - if (identity) - { - auto id_hfig = df::historical_figure::find(identity->histfig_id); + if (id_hfig) + return &id_hfig->name; - if (id_hfig) - return &id_hfig->name; - - return &identity->name; - } - } + return &identity->name; } return &unit->name; @@ -672,65 +677,224 @@ bool DFHack::Units::isDwarf(df::unit *unit) return unit->race == ui->race_id; } -void DFHack::Units::clearBurrowMembers(df::burrow *burrow) +double DFHack::Units::getAge(df::unit *unit, bool true_age) { - CHECK_NULL_POINTER(burrow); + using df::global::cur_year; + using df::global::cur_year_tick; - for (size_t i = 0; i < burrow->units.size(); i++) - { - auto unit = df::unit::find(burrow->units[i]); + CHECK_NULL_POINTER(unit); - if (unit) - erase_from_vector(unit->burrows, burrow->id); - } + if (!cur_year || !cur_year_tick) + return -1; - burrow->units.clear(); + double year_ticks = 403200.0; + double birth_time = unit->relations.birth_year + unit->relations.birth_time/year_ticks; + double cur_time = *cur_year + *cur_year_tick / year_ticks; - // Sync ui if active - if (ui && ui->main.mode == ui_sidebar_mode::Burrows && - ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) + if (!true_age && unit->relations.curse_year >= 0) { - auto &sel = ui->burrows.sel_units; - - for (size_t i = 0; i < sel.size(); i++) - sel[i] = false; + if (auto identity = getIdentity(unit)) + { + if (identity->histfig_id < 0) + birth_time = identity->birth_year + identity->birth_second/year_ticks; + } } + + return cur_time - birth_time; } +static bool noble_pos_compare(const Units::NoblePosition &a, const Units::NoblePosition &b) +{ + if (a.position->precedence < b.position->precedence) + return true; + if (a.position->precedence > b.position->precedence) + return false; + return a.position->id < b.position->id; +} -bool DFHack::Units::isInBurrow(df::unit *unit, df::burrow *burrow) +bool DFHack::Units::getNoblePositions(std::vector *pvec, df::unit *unit) { CHECK_NULL_POINTER(unit); - CHECK_NULL_POINTER(burrow); - return binsearch_index(unit->burrows, burrow->id) >= 0; + pvec->clear(); + + auto histfig = df::historical_figure::find(unit->hist_figure_id); + if (!histfig) + return false; + + for (size_t i = 0; i < histfig->entity_links.size(); i++) + { + auto link = histfig->entity_links[i]; + auto epos = strict_virtual_cast(link); + if (!epos) + continue; + + NoblePosition pos; + + pos.entity = df::historical_entity::find(epos->entity_id); + if (!pos.entity) + continue; + + pos.assignment = binsearch_in_vector(pos.entity->positions.assignments, epos->assignment_id); + if (!pos.assignment) + continue; + + pos.position = binsearch_in_vector(pos.entity->positions.own, pos.assignment->position_id); + if (!pos.position) + continue; + + pvec->push_back(pos); + } + + if (pvec->empty()) + return false; + + std::sort(pvec->begin(), pvec->end(), noble_pos_compare); + return true; } -void DFHack::Units::setInBurrow(df::unit *unit, df::burrow *burrow, bool enable) +std::string DFHack::Units::getProfessionName(df::unit *unit, bool ignore_noble, bool plural) { - using df::global::ui; + std::string prof = unit->custom_profession; + if (!prof.empty()) + return prof; - CHECK_NULL_POINTER(unit); - CHECK_NULL_POINTER(burrow); + std::vector np; - if (enable) + if (!ignore_noble && getNoblePositions(&np, unit)) { - insert_into_vector(unit->burrows, burrow->id); - insert_into_vector(burrow->units, unit->id); + switch (unit->sex) + { + case 0: + prof = np[0].position->name_female[plural ? 1 : 0]; + break; + case 1: + prof = np[0].position->name_male[plural ? 1 : 0]; + break; + default: + break; + } + + if (prof.empty()) + prof = np[0].position->name[plural ? 1 : 0]; + if (!prof.empty()) + return prof; } - else + + return getCasteProfessionName(unit->race, unit->caste, unit->profession, plural); +} + +std::string DFHack::Units::getCasteProfessionName(int race, int casteid, df::profession pid, bool plural) +{ + std::string prof, race_prefix; + + if (pid < 0 || !is_valid_enum_item(pid)) + return ""; + + bool use_race_prefix = (race >= 0 && race != df::global::ui->race_id); + + if (auto creature = df::creature_raw::find(race)) + { + if (auto caste = vector_get(creature->caste, casteid)) + { + race_prefix = caste->caste_name[0]; + + if (plural) + prof = caste->caste_profession_name.plural[pid]; + else + prof = caste->caste_profession_name.singular[pid]; + + if (prof.empty()) + { + switch (pid) + { + case profession::CHILD: + prof = caste->child_name[plural ? 1 : 0]; + if (!prof.empty()) + use_race_prefix = false; + break; + + case profession::BABY: + prof = caste->baby_name[plural ? 1 : 0]; + if (!prof.empty()) + use_race_prefix = false; + break; + + default: + break; + } + } + } + + if (race_prefix.empty()) + race_prefix = creature->name[0]; + + if (prof.empty()) + { + if (plural) + prof = creature->profession_name.plural[pid]; + else + prof = creature->profession_name.singular[pid]; + + if (prof.empty()) + { + switch (pid) + { + case profession::CHILD: + prof = creature->general_child_name[plural ? 1 : 0]; + if (!prof.empty()) + use_race_prefix = false; + break; + + case profession::BABY: + prof = creature->general_baby_name[plural ? 1 : 0]; + if (!prof.empty()) + use_race_prefix = false; + break; + + default: + break; + } + } + } + } + + if (race_prefix.empty()) + race_prefix = "Animal"; + + if (prof.empty()) { - erase_from_vector(unit->burrows, burrow->id); - erase_from_vector(burrow->units, unit->id); + switch (pid) + { + case profession::TRAINED_WAR: + prof = "War " + (use_race_prefix ? race_prefix : "Peasant"); + use_race_prefix = false; + break; + + case profession::TRAINED_HUNTER: + prof = "Hunting " + (use_race_prefix ? race_prefix : "Peasant"); + use_race_prefix = false; + break; + + case profession::STANDARD: + if (!use_race_prefix) + prof = "Peasant"; + break; + + default: + if (auto caption = ENUM_ATTR(profession, caption, pid)) + prof = caption; + else + prof = ENUM_KEY_STR(profession, pid); + } } - // Sync ui if active - if (ui && ui->main.mode == ui_sidebar_mode::Burrows && - ui->burrows.in_add_units_mode && ui->burrows.sel_id == burrow->id) + if (use_race_prefix) { - int idx = linear_index(ui->burrows.list_units, unit); - if (idx >= 0) - ui->burrows.sel_units[idx] = enable; + if (!prof.empty()) + race_prefix += " "; + prof = race_prefix + prof; } -} + return Translation::capitalize(prof, true); +} diff --git a/library/modules/Vegetation.cpp b/library/modules/Vegetation.cpp index bd0eeb656..a76c3a2cd 100644 --- a/library/modules/Vegetation.cpp +++ b/library/modules/Vegetation.cpp @@ -54,14 +54,14 @@ uint32_t Vegetation::getCount() df::plant * Vegetation::getPlant(const int32_t index) { - if (index < 0 || index >= getCount()) + if (uint32_t(index) >= getCount()) return NULL; return world->plants.all[index]; } bool Vegetation::copyPlant(const int32_t index, t_plant &out) { - if (index < 0 || index >= getCount()) + if (uint32_t(index) >= getCount()) return false; out.origin = world->plants.all[index]; diff --git a/library/modules/Windows.cpp b/library/modules/Windows.cpp index 0cdd7e831..e069d964e 100644 --- a/library/modules/Windows.cpp +++ b/library/modules/Windows.cpp @@ -41,7 +41,7 @@ Windows::df_screentile *Windows::getScreenBuffer() } Windows::df_window::df_window(int x, int y, unsigned int width, unsigned int height) -:buffer(0), parent(0), left(x), top(y), width(width), height(height), current_painter(NULL) +:buffer(0), width(width), height(height), parent(0), left(x), top(y), current_painter(NULL) { buffer = 0; }; diff --git a/library/xml b/library/xml index e8036d3f1..5707a6aa0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit e8036d3f13c6be0141899baae90f605ad11d5385 +Subproject commit 5707a6aa0c035348c8769058ed77b320f9b1436c diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 4bfb392a2..70b40cedb 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -106,6 +106,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(regrass regrass.cpp) # this one exports functions to lua DFHACK_PLUGIN(burrows burrows.cpp LINK_LIBRARIES lua) + DFHACK_PLUGIN(sort sort.cpp LINK_LIBRARIES lua) # not yet. busy with other crud again... #DFHACK_PLUGIN(versionosd versionosd.cpp) endif() diff --git a/plugins/Dfusion/dfusion.cpp b/plugins/Dfusion/dfusion.cpp index d8710c2db..2b36a9747 100644 --- a/plugins/Dfusion/dfusion.cpp +++ b/plugins/Dfusion/dfusion.cpp @@ -121,10 +121,26 @@ command_result lua_run_file (color_ostream &out, std::vector ¶ } command_result lua_run (color_ostream &out, std::vector ¶meters) { - if (!parameters.empty() && parameters[0] == "--core-context") + if (!parameters.empty()) { - Lua::InterpreterLoop(out, Lua::Core::State, "core lua"); - return CR_OK; + if (parameters[0] == "--core-context") + { + Lua::InterpreterLoop(out, Lua::Core::State, "core lua"); + return CR_OK; + } + else if (parameters[0] == "--core-reload") + { + CoreSuspender suspend; + + for (size_t i = 1; i < parameters.size(); i++) + { + lua_getglobal(Lua::Core::State, "reload"); + lua_pushstring(Lua::Core::State, parameters[i].c_str()); + Lua::SafeCall(out, Lua::Core::State, 1, 0); + } + + return CR_OK; + } } mymutex->lock(); diff --git a/plugins/Dfusion/luafiles/adv_tools/init.lua b/plugins/Dfusion/luafiles/adv_tools/init.lua index f5fd9a473..5504f32bc 100644 --- a/plugins/Dfusion/luafiles/adv_tools/init.lua +++ b/plugins/Dfusion/luafiles/adv_tools/init.lua @@ -5,7 +5,7 @@ function adv_tools.reincarnate(swap_soul) --only for adventurer i guess if swap_soul==nil then swap_soul=true end - local adv=df.global.world.units.other[0][0] + local adv=df.global.world.units.active[0] if adv.flags1.dead==false then error("You are not dead (yet)!") end diff --git a/plugins/Dfusion/luafiles/common.lua b/plugins/Dfusion/luafiles/common.lua index 7e41dc4e5..951468bc7 100644 --- a/plugins/Dfusion/luafiles/common.lua +++ b/plugins/Dfusion/luafiles/common.lua @@ -472,8 +472,8 @@ function getSelectedUnit() return nil end local unit_indx=df.global.ui_selected_unit - if unit_indx<#df.global.world.units.other[0]-1 then - return df.global.world.units.other[0][unit_indx] + if unit_indx<#df.global.world.units.active-1 then + return df.global.world.units.active[unit_indx] else return nil end diff --git a/plugins/Dfusion/luafiles/tools/init.lua b/plugins/Dfusion/luafiles/tools/init.lua index b01157c87..e3a4607cf 100644 --- a/plugins/Dfusion/luafiles/tools/init.lua +++ b/plugins/Dfusion/luafiles/tools/init.lua @@ -113,7 +113,7 @@ function tools.change_adv(unit,nemesis) if unit==nil then error("Invalid unit!") end - local other=df.global.world.units.other[0] + local other=df.global.world.units.active local unit_indx for k,v in pairs(other) do if v==unit then @@ -157,7 +157,7 @@ function tools.MakeFollow(unit,trgunit) error("Invalid creature") end if trgunit==nil then - trgunit=df.global.world.units.other[0][0] + trgunit=df.global.world.units.active[0] end unit.relations.group_leader_id=trgunit.id local u_nem=getNemesis(unit) diff --git a/plugins/Dfusion/src/lua_Console.cpp b/plugins/Dfusion/src/lua_Console.cpp index 881ba5d21..1d52d6158 100644 --- a/plugins/Dfusion/src/lua_Console.cpp +++ b/plugins/Dfusion/src/lua_Console.cpp @@ -1,6 +1,7 @@ -#include "lua_Console.h" #include "LuaTools.h" +#include "lua_Console.h" + #include //TODO error management. Using lua error? or something other? diff --git a/plugins/advtools.cpp b/plugins/advtools.cpp index 69841849a..4823d362c 100644 --- a/plugins/advtools.cpp +++ b/plugins/advtools.cpp @@ -169,7 +169,7 @@ bool bodySwap(color_ostream &out, df::unit *player) return false; } - auto &vec = world->units.other[0]; + auto &vec = world->units.active; int idx = linear_index(vec, player); if (idx < 0) @@ -195,7 +195,7 @@ df::nemesis_record *getPlayerNemesis(color_ostream &out, bool restore_swap) if (restore_swap) { - df::unit *ctl = world->units.other[0][0]; + df::unit *ctl = world->units.active[0]; auto ctl_nemesis = Units::getNemesis(ctl); if (ctl_nemesis != real_nemesis) diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index cfd9419db..81c890000 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -23,6 +23,16 @@ #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include using std::string; using std::endl; @@ -446,21 +456,45 @@ static const struct labor_default default_labor_infos[] = { /* WAX_WORKING */ {AUTOMATIC, false, 1, 200, 0}, }; -static const df::job_skill noble_skills[] = { - df::enums::job_skill::APPRAISAL, - df::enums::job_skill::ORGANIZATION, - df::enums::job_skill::RECORD_KEEPING, +static const int responsibility_penalties[] = { + 0, /* LAW_MAKING */ + 0, /* LAW_ENFORCEMENT */ + 3000, /* RECEIVE_DIPLOMATS */ + 0, /* MEET_WORKERS */ + 1000, /* MANAGE_PRODUCTION */ + 3000, /* TRADE */ + 1000, /* ACCOUNTING */ + 0, /* ESTABLISH_COLONY_TRADE_AGREEMENTS */ + 0, /* MAKE_INTRODUCTIONS */ + 0, /* MAKE_PEACE_AGREEMENTS */ + 0, /* MAKE_TOPIC_AGREEMENTS */ + 0, /* COLLECT_TAXES */ + 0, /* ESCORT_TAX_COLLECTOR */ + 0, /* EXECUTIONS */ + 0, /* TAME_EXOTICS */ + 0, /* RELIGION */ + 0, /* ATTACK_ENEMIES */ + 0, /* PATROL_TERRITORY */ + 0, /* MILITARY_GOALS */ + 0, /* MILITARY_STRATEGY */ + 0, /* UPGRADE_SQUAD_EQUIPMENT */ + 0, /* EQUIPMENT_MANIFESTS */ + 0, /* SORT_AMMUNITION */ + 0, /* BUILD_MORALE */ + 5000 /* HEALTH_MANAGEMENT */ }; struct dwarf_info_t { int highest_skill; int total_skill; - bool is_best_noble; int mastery_penalty; int assigned_jobs; dwarf_state state; bool has_exclusive_labor; + int noble_penalty; // penalty for assignment due to noble status + bool medical; // this dwarf has medical responsibility + bool trader; // this dwarf has trade responsibility }; static bool isOptionEnabled(unsigned flag) @@ -683,6 +717,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) bool has_butchers = false; bool has_fishery = false; + bool trader_requested = false; for (int i = 0; i < world->buildings.all.size(); ++i) { @@ -695,7 +730,14 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) has_butchers = true; if (df::enums::workshop_type::Fishery == subType) has_fishery = true; - } + } + else if (df::enums::building_type::TradeDepot == type) + { + df::building_tradedepotst* depot = (df::building_tradedepotst*) build; + trader_requested = depot->flags.bits.trader_requested; + if (print_debug) + out.print("Trade depot found and trader requested, trader will be excluded from all labors.\n"); + } } for (int i = 0; i < world->units.all.size(); ++i) @@ -714,15 +756,49 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) std::vector dwarf_info(n_dwarfs); - std::vector best_noble(ARRAY_COUNT(noble_skills)); - std::vector highest_noble_skill(ARRAY_COUNT(noble_skills)); - std::vector highest_noble_experience(ARRAY_COUNT(noble_skills)); - // Find total skill and highest skill for each dwarf. More skilled dwarves shouldn't be used for minor tasks. for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) { - assert(dwarfs[dwarf]->status.souls.size() > 0); +// assert(dwarfs[dwarf]->status.souls.size() > 0); +// assert fails can cause DF to crash, so don't do that + + if (dwarfs[dwarf]->status.souls.size() <= 0) + continue; + + // compute noble penalty + + int noble_penalty = 0; + + df::historical_figure* hf = df::historical_figure::find(dwarfs[dwarf]->hist_figure_id); + for (int i = 0; i < hf->entity_links.size(); i++) { + df::histfig_entity_link* hfelink = hf->entity_links.at(i); + if (hfelink->getType() == df::histfig_entity_link_type::POSITION) { + df::histfig_entity_link_positionst *epos = + (df::histfig_entity_link_positionst*) hfelink; + df::historical_entity* entity = df::historical_entity::find(epos->entity_id); + if (!entity) + continue; + df::entity_position_assignment* assignment = binsearch_in_vector(entity->positions.assignments, epos->assignment_id); + if (!assignment) + continue; + df::entity_position* position = binsearch_in_vector(entity->positions.own, assignment->position_id); + if (!position) + continue; + + for (int n = 0; n < 25; n++) + if (position->responsibilities[n]) + noble_penalty += responsibility_penalties[n]; + + if (position->responsibilities[df::entity_position_responsibility::HEALTH_MANAGEMENT]) + dwarf_info[dwarf].medical = true; + + if (position->responsibilities[df::entity_position_responsibility::TRADE]) + dwarf_info[dwarf].trader = true; + + } + dwarf_info[dwarf].noble_penalty = noble_penalty; + } for (auto s = dwarfs[dwarf]->status.souls[0]->skills.begin(); s != dwarfs[dwarf]->status.souls[0]->skills.end(); s++) { @@ -733,30 +809,6 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) int skill_level = (*s)->rating; int skill_experience = (*s)->experience; - // Track the dwarfs with the best Appraisal, Organization, and Record Keeping skills. - // They are likely to have appointed noble positions, so should be kept free where possible. - - int noble_skill_id = -1; - for (int i = 0; i < ARRAY_COUNT(noble_skills); i++) - { - if (skill == noble_skills[i]) - noble_skill_id = i; - } - - if (noble_skill_id >= 0) - { - assert(noble_skill_id < ARRAY_COUNT(noble_skills)); - - if (highest_noble_skill[noble_skill_id] < skill_level || - (highest_noble_skill[noble_skill_id] == skill_level && - highest_noble_experience[noble_skill_id] < skill_experience)) - { - highest_noble_skill[noble_skill_id] = skill_level; - highest_noble_experience[noble_skill_id] = skill_experience; - best_noble[noble_skill_id] = dwarf; - } - } - // Track total & highest skill among normal/medical skills. (We don't care about personal or social skills.) if (skill_class != df::enums::job_skill_class::Normal && skill_class != df::enums::job_skill_class::Medical) @@ -768,24 +820,13 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) } } - // Mark the best nobles, so we try to keep them non-busy. (It would be better to find the actual assigned nobles.) - - for (int i = 0; i < ARRAY_COUNT(noble_skills); i++) - { - assert(best_noble[i] >= 0); - assert(best_noble[i] < n_dwarfs); - - dwarf_info[best_noble[i]].is_best_noble = true; - } - // Calculate a base penalty for using each dwarf for a task he isn't good at. for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) { dwarf_info[dwarf].mastery_penalty -= 40 * dwarf_info[dwarf].highest_skill; dwarf_info[dwarf].mastery_penalty -= 10 * dwarf_info[dwarf].total_skill; - if (dwarf_info[dwarf].is_best_noble) - dwarf_info[dwarf].mastery_penalty -= 250; + dwarf_info[dwarf].mastery_penalty -= dwarf_info[dwarf].noble_penalty; for (int labor = ENUM_FIRST_ITEM(unit_labor); labor <= ENUM_LAST_ITEM(unit_labor); labor++) { @@ -832,7 +873,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) { if (is_on_break) dwarf_info[dwarf].state = OTHER; - else if (dwarfs[dwarf]->meetings.size() > 0) + else if (dwarfs[dwarf]->specific_refs.size() > 0) dwarf_info[dwarf].state = OTHER; else dwarf_info[dwarf].state = IDLE; @@ -1032,6 +1073,7 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) * Note that only idle and busy dwarfs count towards the number of dwarfs. "Other" dwarfs * (sleeping, eating, on break, etc.) will have labors assigned, but will not be counted. * Military and children/nobles will not have labors assigned. + * Dwarfs with the "health management" responsibility are always assigned DIAGNOSIS. */ for (int i = 0; i < candidates.size() && labor_infos[labor].active_dwarfs < max_dwarfs; i++) { @@ -1047,6 +1089,10 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) preferred_dwarf = true; if (previously_enabled[dwarf] && labor_infos[labor].is_exclusive) preferred_dwarf = true; + if (dwarf_info[dwarf].medical && labor == df::unit_labor::DIAGNOSE) + preferred_dwarf = true; + if (dwarf_info[dwarf].trader && trader_requested) + continue; if (labor_infos[labor].active_dwarfs >= min_dwarfs && !preferred_dwarf) continue; @@ -1084,6 +1130,8 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out ) std::vector hauler_ids; for (int dwarf = 0; dwarf < n_dwarfs; dwarf++) { + if (dwarf_info[dwarf].trader && trader_requested) + continue; if (dwarf_info[dwarf].state == IDLE || dwarf_info[dwarf].state == BUSY) hauler_ids.push_back(dwarf); } diff --git a/plugins/burrows.cpp b/plugins/burrows.cpp index a629e30c1..a6df3524b 100644 --- a/plugins/burrows.cpp +++ b/plugins/burrows.cpp @@ -13,6 +13,7 @@ #include "modules/MapCache.h" #include "modules/World.h" #include "modules/Units.h" +#include "modules/Burrows.h" #include "TileTypes.h" #include "DataDefs.h" @@ -345,7 +346,7 @@ static void handle_burrow_rename(color_ostream &out, df::burrow *burrow) static void add_to_burrows(std::vector &burrows, df::coord pos) { for (size_t i = 0; i < burrows.size(); i++) - Maps::setBurrowTile(burrows[i], pos, true); + Burrows::setAssignedTile(burrows[i], pos, true); } static void add_walls_to_burrows(color_ostream &out, std::vector &burrows, @@ -379,7 +380,7 @@ static void handle_dig_complete(color_ostream &out, df::job_type job, df::coord for (size_t i = 0; i < grow_burrows.size(); i++) { auto b = df::burrow::find(grow_burrows[i]); - if (b && Maps::isBurrowTile(b, pos)) + if (b && Burrows::isAssignedTile(b, pos)) to_grow.push_back(b); } @@ -446,7 +447,7 @@ static void copyUnits(df::burrow *target, df::burrow *source, bool enable) if (source == target) { if (!enable) - Units::clearBurrowMembers(target); + Burrows::clearUnits(target); return; } @@ -456,7 +457,7 @@ static void copyUnits(df::burrow *target, df::burrow *source, bool enable) auto unit = df::unit::find(source->units[i]); if (unit) - Units::setInBurrow(unit, target, enable); + Burrows::setAssignedUnit(target, unit, enable); } } @@ -468,22 +469,22 @@ static void copyTiles(df::burrow *target, df::burrow *source, bool enable) if (source == target) { if (!enable) - Maps::clearBurrowTiles(target); + Burrows::clearTiles(target); return; } std::vector pvec; - Maps::listBurrowBlocks(&pvec, source); + Burrows::listBlocks(&pvec, source); for (size_t i = 0; i < pvec.size(); i++) { auto block = pvec[i]; - auto smask = Maps::getBlockBurrowMask(source, block); + auto smask = Burrows::getBlockMask(source, block); if (!smask) continue; - auto tmask = Maps::getBlockBurrowMask(target, block, enable); + auto tmask = Burrows::getBlockMask(target, block, enable); if (!tmask) continue; @@ -498,7 +499,7 @@ static void copyTiles(df::burrow *target, df::burrow *source, bool enable) tmask->tile_bitmask[j] &= ~smask->tile_bitmask[j]; if (!tmask->has_assignments()) - Maps::deleteBlockBurrowMask(target, block, tmask); + Burrows::deleteBlockMask(target, block, tmask); } } } @@ -523,7 +524,7 @@ static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mas continue; if (!mask) - mask = Maps::getBlockBurrowMask(target, block, enable); + mask = Burrows::getBlockMask(target, block, enable); if (!mask) goto next_block; @@ -532,7 +533,7 @@ static void setTilesByDesignation(df::burrow *target, df::tile_designation d_mas } if (mask && !enable && !mask->has_assignments()) - Maps::deleteBlockBurrowMask(target, block, mask); + Burrows::deleteBlockMask(target, block, mask); next_block:; } @@ -604,7 +605,7 @@ static command_result burrow(color_ostream &out, vector ¶meters) bool state = (cmd == "enable"); - for (int i = 1; i < parameters.size(); i++) + for (size_t i = 1; i < parameters.size(); i++) { string &option = parameters[i]; @@ -619,13 +620,13 @@ static command_result burrow(color_ostream &out, vector ¶meters) if (parameters.size() < 2) return CR_WRONG_USAGE; - for (int i = 1; i < parameters.size(); i++) + for (size_t i = 1; i < parameters.size(); i++) { auto target = findByName(out, parameters[i]); if (!target) return CR_WRONG_USAGE; - Units::clearBurrowMembers(target); + Burrows::clearUnits(target); } } else if (cmd == "set-units" || cmd == "add-units" || cmd == "remove-units") @@ -638,11 +639,11 @@ static command_result burrow(color_ostream &out, vector ¶meters) return CR_WRONG_USAGE; if (cmd == "set-units") - Units::clearBurrowMembers(target); + Burrows::clearUnits(target); bool enable = (cmd != "remove-units"); - for (int i = 2; i < parameters.size(); i++) + for (size_t i = 2; i < parameters.size(); i++) { auto source = findByName(out, parameters[i]); if (!source) @@ -656,13 +657,13 @@ static command_result burrow(color_ostream &out, vector ¶meters) if (parameters.size() < 2) return CR_WRONG_USAGE; - for (int i = 1; i < parameters.size(); i++) + for (size_t i = 1; i < parameters.size(); i++) { auto target = findByName(out, parameters[i]); if (!target) return CR_WRONG_USAGE; - Maps::clearBurrowTiles(target); + Burrows::clearTiles(target); } } else if (cmd == "set-tiles" || cmd == "add-tiles" || cmd == "remove-tiles") @@ -675,11 +676,11 @@ static command_result burrow(color_ostream &out, vector ¶meters) return CR_WRONG_USAGE; if (cmd == "set-tiles") - Maps::clearBurrowTiles(target); + Burrows::clearTiles(target); bool enable = (cmd != "remove-tiles"); - for (int i = 2; i < parameters.size(); i++) + for (size_t i = 2; i < parameters.size(); i++) { if (setTilesByKeyword(target, parameters[i], enable)) continue; diff --git a/plugins/devel/CMakeLists.txt b/plugins/devel/CMakeLists.txt index 344370d10..5d1d585ab 100644 --- a/plugins/devel/CMakeLists.txt +++ b/plugins/devel/CMakeLists.txt @@ -13,4 +13,7 @@ DFHACK_PLUGIN(frozen frozen.cpp) DFHACK_PLUGIN(dumpmats dumpmats.cpp) #DFHACK_PLUGIN(tiles tiles.cpp) DFHACK_PLUGIN(counters counters.cpp) - +DFHACK_PLUGIN(stockcheck stockcheck.cpp) +DFHACK_PLUGIN(stripcaged stripcaged.cpp) +DFHACK_PLUGIN(rprobe rprobe.cpp) +DFHACK_PLUGIN(nestboxes nestboxes.cpp) diff --git a/plugins/devel/buildprobe.cpp b/plugins/devel/buildprobe.cpp index 6c360455a..17e95237f 100644 --- a/plugins/devel/buildprobe.cpp +++ b/plugins/devel/buildprobe.cpp @@ -119,7 +119,7 @@ command_result writeFlag (color_ostream &out, vector & parameters) MapExtras::MapCache * MCache = new MapExtras::MapCache(); t_occupancy oc = MCache->occupancyAt(cursor); - oc.bits.building = value; + oc.bits.building = df::tile_building_occ(value); MCache->setOccupancyAt(cursor, oc); MCache->WriteAll(); diff --git a/plugins/devel/nestboxes.cpp b/plugins/devel/nestboxes.cpp new file mode 100644 index 000000000..b3d24cd92 --- /dev/null +++ b/plugins/devel/nestboxes.cpp @@ -0,0 +1,116 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "DataDefs.h" +#include "df/world.h" +#include "df/ui.h" +#include "df/building_nest_boxst.h" +#include "df/building_type.h" +#include "df/global_objects.h" +#include "df/item.h" +#include "df/unit.h" +#include "df/building.h" +#include "df/items_other_id.h" +#include "df/creature_raw.h" +#include "modules/MapCache.h" +#include "modules/Items.h" + + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +using df::global::world; +using df::global::ui; + +static command_result nestboxes(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("nestboxes"); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + if (world && ui) { + commands.push_back( + PluginCommand("nestboxes", "Derp.", + nestboxes, false, + "Derp.\n" + ) + ); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + + +static command_result nestboxes(color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + bool clean = false; + int dump_count = 0; + int good_egg = 0; + + if (parameters.size() == 1 && parameters[0] == "clean") + { + clean = true; + } + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (df::enums::building_type::NestBox == type) + { + bool needs_clean = false; + df::building_nest_boxst *nb = virtual_cast(build); + out << "Nestbox at (" << nb->x1 << "," << nb->y1 << ","<< nb->z << "): claimed-by " << nb->claimed_by << ", contained item count " << nb->contained_items.size() << " (" << nb->anon_1 << ")" << endl; + if (nb->contained_items.size() > 1) + needs_clean = true; + if (nb->claimed_by != -1) + { + df::unit* u = df::unit::find(nb->claimed_by); + if (u) + { + out << " Claimed by "; + if (u->name.has_name) + out << u->name.first_name << ", "; + df::creature_raw *raw = df::global::world->raws.creatures.all[u->race]; + out << raw->creature_id + << ", pregnancy timer " << u->relations.pregnancy_timer << endl; + if (u->relations.pregnancy_timer > 0) + needs_clean = false; + } + } + for (int j = 1; j < nb->contained_items.size(); j++) + { + df::item* item = nb->contained_items[j]->item; + if (needs_clean) { + if (clean && !item->flags.bits.dump) + { + item->flags.bits.dump = 1; + dump_count += item->getStackSize(); + + } + } else { + good_egg += item->getStackSize(); + } + } + } + } + + if (clean) + { + out << dump_count << " eggs dumped." << endl; + } + out << good_egg << " fertile eggs found." << endl; + + + return CR_OK; +} + diff --git a/plugins/devel/rprobe.cpp b/plugins/devel/rprobe.cpp new file mode 100644 index 000000000..7a091a962 --- /dev/null +++ b/plugins/devel/rprobe.cpp @@ -0,0 +1,163 @@ +// Produces a list of materials available on the map. +// Options: +// -a : show unrevealed tiles +// -p : don't show plants +// -s : don't show slade +// -t : don't show demon temple + +//#include +#include +#include +#include +#include +#include + +using namespace std; +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/MapCache.h" + +#include "MiscUtils.h" + +#include "DataDefs.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_region_details.h" +#include "df/world_geo_biome.h" +#include "df/world_geo_layer.h" +#include "df/inclusion_type.h" +#include "df/viewscreen_choose_start_sitest.h" + +using namespace DFHack; +using namespace df::enums; +using df::global::world; +using df::coord2d; + + + +command_result rprobe (color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("rprobe"); + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "rprobe", "Display assorted region information from embark screen", + rprobe, false, + "Display assorted region information from embark screen\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + + +command_result rprobe (color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + + bool set = false; + int to_set, set_field, set_val; + + // Embark screen active: estimate using world geology data + VIRTUAL_CAST_VAR(screen, df::viewscreen_choose_start_sitest, Core::getTopViewscreen()); + + if (!screen) + return CR_WRONG_USAGE; + + if (!world || !world->world_data) + { + out.printerr("World data is not available.\n"); + return CR_FAILURE; + } + + + if (parameters.size() == 2) + { + if (parameters[0] == "wet") + set_field = 0; + else if (parameters[0] == "veg") + set_field = 1; + else if (parameters[0] == "tem") + set_field = 2; + else if (parameters[0] == "evi") + set_field = 3; + else if (parameters[0] == "hil") + set_field = 4; + else if (parameters[0] == "sav") + set_field = 5; + else if (parameters[0] == "sal") + set_field = 6; + else + return CR_WRONG_USAGE; + + if (screen->biome_highlighted) + to_set = screen->biome_idx; + else + to_set = 0; + + set = true; + set_val = atoi(parameters[1].c_str()); + } + + df::world_data *data = world->world_data; + coord2d cur_region = screen->region_pos; + + // Compute biomes + for (int i = 0; i < screen->biome_rgn.size(); i++) + { + coord2d rg = screen->biome_rgn[i]; + + df::world_data::T_region_map* rd = &data->region_map[rg.x][rg.y]; + + if (set && i == to_set) { + if (set_field == 0) + rd->wetness = set_val; + else if (set_field == 1) + rd->vegetation = set_val; + else if (set_field == 2) + rd->temperature = set_val; + else if (set_field == 3) + rd->evilness = set_val; + else if (set_field == 4) + rd->hilliness = set_val; + else if (set_field == 5) + rd->savagery = set_val; + else if (set_field == 6) + rd->saltiness = set_val; + } + + out << i << ": x = " << rg.x << ", y = " << rg.y; + + out << + " region_id: " << rd->region_id << + " geo_index: " << rd->geo_index << + " landmass_id: " << rd->landmass_id << + " flags: " << hex << rd->flags.as_int() << dec << endl; + out << + "wet: " << rd->wetness << " " << + "veg: " << rd->vegetation << " " << + "tem: " << rd->temperature << " " << + "evi: " << rd->evilness << " " << + "hil: " << rd->hilliness << " " << + "sav: " << rd->savagery << " " << + "sal: " << rd->saltiness; + + int32_t *p = (int32_t *)rd; + int c = sizeof(*rd) / sizeof(int32_t); + for (int j = 0; j < c; j++) { + if (j % 8 == 0) + out << endl << setfill('0') << setw(8) << hex << (int)(rd+j) << ": "; + out << " " << setfill('0') << setw(8) << hex << p[j]; + } + out << setfill(' ') << setw(0) << dec << endl; + + } + + return CR_OK; +} diff --git a/plugins/devel/stockcheck.cpp b/plugins/devel/stockcheck.cpp new file mode 100644 index 000000000..8ace55cdc --- /dev/null +++ b/plugins/devel/stockcheck.cpp @@ -0,0 +1,282 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "DataDefs.h" +#include "df/world.h" +#include "df/ui.h" +#include "df/building_stockpilest.h" +#include "df/global_objects.h" +#include "df/item.h" +#include "df/unit.h" +#include "df/building.h" +#include "df/items_other_id.h" +#include "df/item_stockpile_ref.h" +#include "modules/MapCache.h" +#include "modules/Items.h" + + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +using df::global::world; +using df::global::ui; +using df::global::selection_rect; + +using df::building_stockpilest; + +static command_result copystock(color_ostream &out, vector & parameters); +static command_result stockcheck(color_ostream &out, vector & parameters); +static bool copystock_guard(df::viewscreen *top); + +DFHACK_PLUGIN("stockcheck"); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + if (world && ui) { + commands.push_back( + PluginCommand("stockcheck", "Check for unprotected rottable items.", + stockcheck, false, + "Scan world for items that are susceptible to rot. Currently just lists the items.\n" + ) + ); + } + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +struct StockpileInfo { + building_stockpilest* sp; + int size; + int free; + int x1, x2, y1, y2, z; + +public: + StockpileInfo(building_stockpilest *sp_) : sp(sp_) + { + MapExtras::MapCache mc; + + z = sp_->z; + x1 = sp_->room.x; + x2 = sp_->room.x + sp_->room.width; + y1 = sp_->room.y; + y2 = sp_->room.y + sp_->room.height; + int e = 0; + size = 0; + free = 0; + for (int y = y1; y < y2; y++) + for (int x = x1; x < x2; x++) + if (sp_->room.extents[e++] == 1) + { + size++; + DFCoord cursor (x,y,z); + uint32_t blockX = x / 16; + uint32_t tileX = x % 16; + uint32_t blockY = y / 16; + uint32_t tileY = y % 16; + MapExtras::Block * b = mc.BlockAt(cursor/16); + if(b && b->is_valid()) + { + auto &block = *b->getRaw(); + df::tile_occupancy &occ = block.occupancy[tileX][tileY]; + if (!occ.bits.item) + free++; + } + } + } + + bool isFull() { return free == 0; } + + bool canHold(df::item *i) + { + return false; + } + + bool inStockpile(df::item *i) + { + df::item *container = Items::getContainer(i); + if (container) + return inStockpile(container); + + if (i->pos.z != z) return false; + if (i->pos.x < x1 || i->pos.x >= x2 || + i->pos.y < y1 || i->pos.y >= y2) return false; + int e = (i->pos.x - x1) + (i->pos.y - y1) * sp->room.width; + return sp->room.extents[e] == 1; + } + + int getId() { return sp->id; } +}; + +static command_result stockcheck(color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + + std::vector stockpiles; + + for (int i = 0; i < world->buildings.all.size(); ++i) + { + df::building *build = world->buildings.all[i]; + auto type = build->getType(); + if (df::enums::building_type::Stockpile == type) + { + building_stockpilest *sp = virtual_cast(build); + StockpileInfo *spi = new StockpileInfo(sp); + stockpiles.push_back(spi); + } + + } + + std::vector &items = world->items.other[items_other_id::ANY_FREE]; + + // Precompute a bitmask with the bad flags + df::item_flags bad_flags; + bad_flags.whole = 0; + +#define F(x) bad_flags.bits.x = true; + F(dump); F(forbid); F(garbage_collect); + F(hostile); F(on_fire); F(rotten); F(trader); + F(in_building); F(construction); F(artifact1); + F(spider_web); F(owned); F(in_job); +#undef F + + for (size_t i = 0; i < items.size(); i++) + { + df::item *item = items[i]; + if (item->flags.whole & bad_flags.whole) + continue; + + // we really only care about MEAT, FISH, FISH_RAW, PLANT, CHEESE, FOOD, and EGG + + df::item_type typ = item->getType(); + if (typ != df::enums::item_type::MEAT && + typ != df::enums::item_type::FISH && + typ != df::enums::item_type::FISH_RAW && + typ != df::enums::item_type::PLANT && + typ != df::enums::item_type::CHEESE && + typ != df::enums::item_type::FOOD && + typ != df::enums::item_type::EGG) + continue; + + df::item *container = 0; + df::unit *holder = 0; + df::building *building = 0; + + for (size_t i = 0; i < item->itemrefs.size(); i++) + { + df::general_ref *ref = item->itemrefs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + container = ref->getItem(); + break; + + case general_ref_type::UNIT_HOLDER: + holder = ref->getUnit(); + break; + + case general_ref_type::BUILDING_HOLDER: + building = ref->getBuilding(); + break; + + default: + break; + } + } + + df::item *nextcontainer = container; + df::item *lastcontainer = 0; + + while(nextcontainer) { + df::item *thiscontainer = nextcontainer; + nextcontainer = 0; + for (size_t i = 0; i < thiscontainer->itemrefs.size(); i++) + { + df::general_ref *ref = thiscontainer->itemrefs[i]; + + switch (ref->getType()) + { + case general_ref_type::CONTAINED_IN_ITEM: + lastcontainer = nextcontainer = ref->getItem(); + break; + + case general_ref_type::UNIT_HOLDER: + holder = ref->getUnit(); + break; + + case general_ref_type::BUILDING_HOLDER: + building = ref->getBuilding(); + break; + + default: + break; + } + } + } + + if (holder) + continue; // carried items do not rot as far as i know + + if (building) { + df::building_type btype = building->getType(); + if (btype == df::enums::building_type::TradeDepot || + btype == df::enums::building_type::Wagon) + continue; // items in trade depot or the embark wagon do not rot + + if (typ == df::enums::item_type::EGG && btype ==df::enums::building_type::NestBox) + continue; // eggs in nest box do not rot + } + + int canHoldCount = 0; + StockpileInfo *current = 0; + + for (int idx = 0; idx < stockpiles.size(); idx++) + { + StockpileInfo *spi = stockpiles[idx]; + if (spi->canHold(item)) canHoldCount++; + if (spi->inStockpile(item)) current=spi; + } + + if (current) + continue; + + std::string description; + item->getItemDescription(&description, 0); + out << " * " << description; + + if (container) { + std::string containerDescription; + container->getItemDescription(&containerDescription, 0); + out << ", in container " << containerDescription; + if (lastcontainer) { + std::string lastcontainerDescription; + lastcontainer->getItemDescription(&lastcontainerDescription, 0); + out << ", in container " << lastcontainerDescription; + } + } + + if (holder) { + out << ", carried"; + } + + if (building) { + out << ", in building " << building->id << " (type=" << building->getType() << ")"; + } + + out << ", flags=" << std::hex << item->flags.whole << std::dec; + out << endl; + + } + + return CR_OK; +} + diff --git a/plugins/devel/stripcaged.cpp b/plugins/devel/stripcaged.cpp new file mode 100644 index 000000000..7e492cb01 --- /dev/null +++ b/plugins/devel/stripcaged.cpp @@ -0,0 +1,103 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; + +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" +#include "modules/Units.h" +#include "modules/Maps.h" +#include "modules/Gui.h" +#include "modules/World.h" +#include "MiscUtils.h" + +#include +#include "df/world.h" +#include "df/world_raws.h" +#include "df/building_def.h" +#include "df/unit_inventory_item.h" +#include +#include + +using std::vector; +using std::string; +using namespace DFHack; +using namespace df::enums; +using df::global::world; +using df::global::cursor; +using df::global::ui; + +using namespace DFHack::Gui; + +command_result df_stripcaged(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("stripcaged"); + +// check if contained in item (e.g. animals in cages) +bool isContainedInItem(df::unit* unit) +{ + bool contained = false; + for (size_t r=0; r < unit->refs.size(); r++) + { + df::general_ref * ref = unit->refs[r]; + auto rtype = ref->getType(); + if(rtype == df::general_ref_type::CONTAINED_IN_ITEM) + { + contained = true; + break; + } + } + return contained; +} + +DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "stripcaged", "strip caged units of all items", + df_stripcaged, false, + "Clears forbid and sets dump for the inventories of all caged units." + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +command_result df_stripcaged(color_ostream &out, vector & parameters) +{ + CoreSuspender suspend; + + size_t count = 0; + for (size_t i=0; i < world->units.all.size(); i++) + { + df::unit* unit = world->units.all[i]; + if (isContainedInItem(unit)) + { + for (size_t j=0; j < unit->inventory.size(); j++) + { + df::unit_inventory_item* uii = unit->inventory[j]; + if (uii->item) + { + uii->item->flags.bits.forbid = 0; + uii->item->flags.bits.dump = 1; + count++; + } + } + } + } + + out << count << " items marked for dumping" << endl; + + return CR_OK; +} diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 64161a3aa..21c199102 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -1196,7 +1196,7 @@ command_result digl (color_ostream &out, vector & parameters) df::tile_designation des = MCache->designationAt(xy); df::tiletype tt = MCache->tiletypeAt(xy); int16_t veinmat = MCache->veinMaterialAt(xy); - int16_t basemat = MCache->baseMaterialAt(xy); + int16_t basemat = MCache->layerMaterialAt(xy); if( veinmat != -1 ) { con.printerr("This is a vein. Use vdig instead!\n"); @@ -1215,7 +1215,7 @@ command_result digl (color_ostream &out, vector & parameters) if (MCache->tagAt(current)) continue; int16_t vmat2 = MCache->veinMaterialAt(current); - int16_t bmat2 = MCache->baseMaterialAt(current); + int16_t bmat2 = MCache->layerMaterialAt(current); tt = MCache->tiletypeAt(current); if(!DFHack::isWallTerrain(tt)) @@ -1282,7 +1282,7 @@ command_result digl (color_ostream &out, vector & parameters) //below = 1; des_minus = MCache->designationAt(current-1); vmat_minus = MCache->veinMaterialAt(current-1); - bmat_minus = MCache->baseMaterialAt(current-1); + bmat_minus = MCache->layerMaterialAt(current-1); tt_minus = MCache->tiletypeAt(current-1); if ( tileMaterial(tt_minus)==tiletype_material::STONE || tileMaterial(tt_minus)==tiletype_material::SOIL) @@ -1293,7 +1293,7 @@ command_result digl (color_ostream &out, vector & parameters) //above = 1; des_plus = MCache->designationAt(current+1); vmat_plus = MCache->veinMaterialAt(current+1); - bmat_plus = MCache->baseMaterialAt(current+1); + bmat_plus = MCache->layerMaterialAt(current+1); tt_plus = MCache->tiletypeAt(current+1); if ( tileMaterial(tt_plus)==tiletype_material::STONE || tileMaterial(tt_plus)==tiletype_material::SOIL) diff --git a/plugins/jobutils.cpp b/plugins/jobutils.cpp index 511095943..24ad4170e 100644 --- a/plugins/jobutils.cpp +++ b/plugins/jobutils.cpp @@ -193,7 +193,7 @@ static bool build_choice_matches(df::ui_build_item_req *req, df::build_req_choic { if (gen->mat_type == new_mat.type && gen->mat_index == new_mat.index && - (ignore_select || gen->used_count < gen->candidates.size())) + (ignore_select || size_t(gen->used_count) < gen->candidates.size())) { return true; } @@ -272,7 +272,7 @@ static command_result job_duplicate(color_ostream &out, vector & parame if (!job) return CR_FAILURE; - if (!job->misc_links.empty() || + if (!job->specific_refs.empty() || (job->job_items.empty() && job->job_type != job_type::CollectSand && job->job_type != job_type::CollectClay)) @@ -305,7 +305,7 @@ static df::job_item *getJobItem(color_ostream &out, df::job *job, std::string id return NULL; int v = atoi(idx.c_str()); - if (v < 1 || v > job->job_items.size()) { + if (v < 1 || size_t(v) > job->job_items.size()) { out.printerr("Invalid item index.\n"); return NULL; } diff --git a/plugins/liquids.cpp b/plugins/liquids.cpp index 1ecf98b1f..2d568c20c 100644 --- a/plugins/liquids.cpp +++ b/plugins/liquids.cpp @@ -365,6 +365,10 @@ command_result df_liquids_execute(color_ostream &out) DFHack::DFCoord cursor(x,y,z); coord_vec all_tiles = brush->points(mcache,cursor); out << "working..." << endl; + + // Force the game to recompute its walkability cache + df::global::world->reindex_pathfinding = true; + if(mode == "obsidian") { coord_vec::iterator iter = all_tiles.begin(); @@ -375,6 +379,7 @@ command_result df_liquids_execute(color_ostream &out) mcache.setTemp2At(*iter,10015); df::tile_designation des = mcache.designationAt(*iter); des.bits.flow_size = 0; + des.bits.flow_forbid = false; mcache.setDesignationAt(*iter, des); iter ++; } @@ -481,6 +486,9 @@ command_result df_liquids_execute(color_ostream &out) mcache.setTemp1At(current,10015); mcache.setTemp2At(current,10015); } + // mark the tile passable or impassable like the game does + des.bits.flow_forbid = des.bits.flow_size && + (des.bits.liquid_type == tile_liquid::Magma || des.bits.flow_size > 3); mcache.setDesignationAt(current,des); } seen_blocks.insert(mcache.BlockAt(current / 16)); diff --git a/plugins/lua/burrows.lua b/plugins/lua/burrows.lua index b1d51f6a6..871295fdc 100644 --- a/plugins/lua/burrows.lua +++ b/plugins/lua/burrows.lua @@ -2,6 +2,11 @@ local _ENV = mkmodule('plugins.burrows') --[[ + Native events: + + * onBurrowRename(burrow) + * onDigComplete(job_type,pos,old_tiletype,new_tiletype) + Native functions: * findByName(name) -> burrow @@ -13,21 +18,6 @@ local _ENV = mkmodule('plugins.burrows') --]] -clearUnits = dfhack.units.clearBurrowMembers - -function isBurrowUnit(burrow,unit) - return dfhack.units.isInBurrow(unit,burrow) -end -function setBurrowUnit(burrow,unit,enable) - return dfhack.units.setInBurrow(unit,burrow,enable) -end - -clearTiles = dfhack.maps.clearBurrowTiles -listBlocks = dfhack.maps.listBurrowBlocks - -isBurrowTile = dfhack.maps.isBurrowTile -setBurrowTile = dfhack.maps.setBurrowTile -isBlockBurrowTile = dfhack.maps.isBlockBurrowTile -setBlockBurrowTile = dfhack.maps.setBlockBurrowTile +rawset_default(_ENV, dfhack.burrows) return _ENV \ No newline at end of file diff --git a/plugins/lua/sort.lua b/plugins/lua/sort.lua new file mode 100644 index 000000000..f042e85cd --- /dev/null +++ b/plugins/lua/sort.lua @@ -0,0 +1,53 @@ +local _ENV = mkmodule('plugins.sort') + +local utils = require('utils') +local units = require('plugins.sort.units') + +orders = orders or {} +orders.units = units.orders + +function parse_ordering_spec(type,...) + local group = orders[type] + if group == nil then + dfhack.printerr('Invalid ordering class: '..tostring(type)) + return nil + end + + local specs = table.pack(...) + local rv = { } + + for _,spec in ipairs(specs) do + local nil_first = false + if string.sub(spec,1,1) == '<' then + nil_first = true + spec = string.sub(spec,2) + end + + local reverse = false + if string.sub(spec,1,1) == '>' then + reverse = true + spec = string.sub(spec,2) + end + + local cm = group[spec] + + if cm == nil then + dfhack.printerr('Unknown order for '..type..': '..tostring(spec)) + return nil + end + + if nil_first or reverse then + cm = copyall(cm) + cm.nil_first = nil_first + cm.reverse = reverse + end + + rv[#rv+1] = cm + end + + return rv +end + +make_sort_order = utils.make_sort_order + +return _ENV \ No newline at end of file diff --git a/plugins/lua/sort/units.lua b/plugins/lua/sort/units.lua new file mode 100644 index 000000000..35795502d --- /dev/null +++ b/plugins/lua/sort/units.lua @@ -0,0 +1,112 @@ +local _ENV = mkmodule('plugins.sort.units') + +local utils = require('utils') + +orders = orders or {} + +-- Relies on NULL being auto-translated to NULL, and then sorted +orders.exists = { + key = function(unit) + return 1 + end +} + +orders.name = { + key = function(unit) + local name = dfhack.units.getVisibleName(unit) + if name and name.has_name then + return dfhack.TranslateName(name) + end + end, + compare = utils.compare_name +} + +orders.age = { + key = function(unit) + return dfhack.units.getAge(unit) + end +} + +-- This assumes that units are added to active in arrival order +orders.arrival = { + key_table = function(units) + local size = units.n or #units + local lookup={} + for i=1,size do + if units[i] then + lookup[units[i].id] = i + end + end + local idx={} + for i,v in ipairs(df.global.world.units.active) do + if lookup[v.id] then + idx[lookup[v.id]] = i + end + end + return idx + end +} + +local function findRaceCaste(unit) + local rraw = df.creature_raw.find(unit.race) + return rraw, safe_index(rraw, 'caste', unit.caste) +end + +orders.noble = { + key = function(unit) + local info = dfhack.units.getNoblePositions(unit) + if info then + return info[1].position.precedence + end + end +} + +orders.profession = { + key = function(unit) + local cp = dfhack.units.getProfessionName(unit) + if cp and cp ~= '' then + return string.lower(cp) + end + end +} + +orders.profession_class = { + key = function(unit) + local pid = unit.profession + local parent = df.profession.attrs[pid].parent + if parent >= 0 then + return parent + else + return pid + end + end +} + +orders.race = { + key = function(unit) + local rraw = findRaceCaste(unit) + if rraw then + return rraw.name[0] + end + end +} + +orders.squad = { + key = function(unit) + local sidx = unit.military.squad_index + if sidx >= 0 then + return sidx + end + end +} + +orders.squad_position = { + key = function(unit) + local sidx = unit.military.squad_index + if sidx >= 0 then + return sidx * 1000 + unit.military.squad_position + end + end +} + +return _ENV \ No newline at end of file diff --git a/plugins/mapexport/mapexport.cpp b/plugins/mapexport/mapexport.cpp index e0a7e5e69..625f4a74d 100644 --- a/plugins/mapexport/mapexport.cpp +++ b/plugins/mapexport/mapexport.cpp @@ -39,6 +39,46 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out ) return CR_OK; } +static dfproto::Tile::TileMaterialType toProto(df::tiletype_material mat) +{ + /* + * This is surely ugly, but casting enums without officially + * defined numerical values to protobuf enums is against the + * way protobufs are supposed to be used, because it defeats + * the backward compatible nature of the protocols. + */ + switch (mat) + { +#define CONVERT(name) case tiletype_material::name: return dfproto::Tile::name; + case tiletype_material::NONE: + CONVERT(AIR) + case tiletype_material::PLANT: + CONVERT(SOIL) + CONVERT(STONE) + CONVERT(FEATURE) + CONVERT(LAVA_STONE) + CONVERT(MINERAL) + CONVERT(FROZEN_LIQUID) + CONVERT(CONSTRUCTION) + CONVERT(GRASS_LIGHT) + CONVERT(GRASS_DARK) + CONVERT(GRASS_DRY) + CONVERT(GRASS_DEAD) + CONVERT(HFS) + CONVERT(CAMPFIRE) + CONVERT(FIRE) + CONVERT(ASHES) + case tiletype_material::MAGMA: + return dfproto::Tile::MAGMA_TYPE; + CONVERT(DRIFTWOOD) + CONVERT(POOL) + CONVERT(BROOK) + CONVERT(RIVER) +#undef CONVERT + } + return dfproto::Tile::AIR; +} + command_result mapexport (color_ostream &out, std::vector & parameters) { bool showHidden = false; @@ -193,9 +233,9 @@ command_result mapexport (color_ostream &out, std::vector & parame prototile->set_flow_size(des.bits.flow_size); } - df::tiletype type = b->TileTypeAt(coord); + df::tiletype type = b->tiletypeAt(coord); prototile->set_type((dfproto::Tile::TileType)tileShape(type)); - prototile->set_tile_material((dfproto::Tile::TileMaterialType)tileMaterial(type)); + prototile->set_tile_material(toProto(tileMaterial(type))); df::coord map_pos = df::coord(b_x*16+x,b_y*16+y,z); @@ -204,7 +244,7 @@ command_result mapexport (color_ostream &out, std::vector & parame case tiletype_material::SOIL: case tiletype_material::STONE: prototile->set_material_type(0); - prototile->set_material_index(b->baseMaterialAt(coord)); + prototile->set_material_index(b->layerMaterialAt(coord)); break; case tiletype_material::MINERAL: prototile->set_material_type(0); diff --git a/plugins/probe.cpp b/plugins/probe.cpp index 4e041f180..eb622e789 100644 --- a/plugins/probe.cpp +++ b/plugins/probe.cpp @@ -103,6 +103,30 @@ command_result df_cprobe (color_ostream &out, vector & parameters) return CR_OK; } +void describeTile(color_ostream &out, df::tiletype tiletype) +{ + out.print("%d", tiletype); + if(tileName(tiletype)) + out.print(" = %s",tileName(tiletype)); + out.print("\n"); + + df::tiletype_shape shape = tileShape(tiletype); + df::tiletype_material material = tileMaterial(tiletype); + df::tiletype_special special = tileSpecial(tiletype); + df::tiletype_variant variant = tileVariant(tiletype); + out.print("%-10s: %4d %s\n","Class" ,shape, + ENUM_KEY_STR(tiletype_shape, shape).c_str()); + out.print("%-10s: %4d %s\n","Material" , + material, ENUM_KEY_STR(tiletype_material, material).c_str()); + out.print("%-10s: %4d %s\n","Special" , + special, ENUM_KEY_STR(tiletype_special, special).c_str()); + out.print("%-10s: %4d %s\n" ,"Variant" , + variant, ENUM_KEY_STR(tiletype_variant, variant).c_str()); + out.print("%-10s: %s\n" ,"Direction", + tileDirection(tiletype).getStr()); + out.print("\n"); +} + command_result df_probe (color_ostream &out, vector & parameters) { //bool showBlock, showDesig, showOccup, showTile, showMisc; @@ -186,35 +210,39 @@ command_result df_probe (color_ostream &out, vector & parameters) */ // tiletype - out.print("tiletype: %d", tiletype); - if(tileName(tiletype)) - out.print(" = %s",tileName(tiletype)); - out.print("\n"); - - df::tiletype_shape shape = tileShape(tiletype); - df::tiletype_material material = tileMaterial(tiletype); - df::tiletype_special special = tileSpecial(tiletype); - df::tiletype_variant variant = tileVariant(tiletype); - out.print("%-10s: %4d %s\n","Class" ,shape, - ENUM_KEY_STR(tiletype_shape, shape).c_str()); - out.print("%-10s: %4d %s\n","Material" , - material, ENUM_KEY_STR(tiletype_material, material).c_str()); - out.print("%-10s: %4d %s\n","Special" , - special, ENUM_KEY_STR(tiletype_special, special).c_str()); - out.print("%-10s: %4d %s\n" ,"Variant" , - variant, ENUM_KEY_STR(tiletype_variant, variant).c_str()); - out.print("%-10s: %s\n" ,"Direction", - tileDirection(tiletype).getStr()); - out.print("\n"); + out.print("tiletype: "); + describeTile(out, tiletype); + out.print("static: "); + describeTile(out, mc.staticTiletypeAt(cursor)); + out.print("base: "); + describeTile(out, mc.baseTiletypeAt(cursor)); out.print("temperature1: %d U\n",mc.temperature1At(cursor)); out.print("temperature2: %d U\n",mc.temperature2At(cursor)); + int offset = block.region_offset[des.bits.biome]; + df::coord2d region_pos = block.region_pos + df::coord2d ((offset % 3) - 1, (offset / 3) -1); + + df::world_data::T_region_map* biome = + &world->world_data->region_map[region_pos.x][region_pos.y]; + + int sav = biome->savagery; + int evi = biome->evilness; + int sindex = sav > 65 ? 2 : sav < 33 ? 0 : 1; + int eindex = evi > 65 ? 2 : evi < 33 ? 0 : 1; + int surr = sindex + eindex * 3; + + char* surroundings[] = { "Serene", "Mirthful", "Joyous Wilds", "Calm", "Wilderness", "Untamed Wilds", "Sinister", "Haunted", "Terrifying" }; + // biome, geolayer - out << "biome: " << des.bits.biome << std::endl; + out << "biome: " << des.bits.biome << " (" << + "region id=" << biome->region_id << ", " << + surroundings[surr] << ", " << + "savagery " << biome->savagery << ", " << + "evilness " << biome->evilness << ")" << std::endl; out << "geolayer: " << des.bits.geolayer_index << std::endl; - int16_t base_rock = mc.baseMaterialAt(cursor); + int16_t base_rock = mc.layerMaterialAt(cursor); if(base_rock != -1) { out << "Layer material: " << dec << base_rock; @@ -238,6 +266,12 @@ command_result df_probe (color_ostream &out, vector & parameters) else out << endl; } + MaterialInfo minfo(mc.baseMaterialAt(cursor)); + if (minfo.isValid()) + out << "Base material: " << minfo.getToken() << " / " << minfo.toString() << endl; + minfo.decode(mc.staticMaterialAt(cursor)); + if (minfo.isValid()) + out << "Static material: " << minfo.getToken() << " / " << minfo.toString() << endl; // liquids if(des.bits.flow_size) { @@ -296,6 +330,39 @@ command_result df_probe (color_ostream &out, vector & parameters) out << "global feature idx: " << block.global_feature << endl; out << std::endl; + + if(block.occupancy[tileX][tileY].bits.no_grow) + out << "no grow" << endl; + + for(size_t e=0; egetType(); + switch(blevtype) + { + case df::block_square_event_type::grass: + { + df::block_square_event_grassst * gr_ev = (df::block_square_event_grassst *)blev; + if(gr_ev->amount[tileX][tileY] > 0) + { + out << "amount of grass: " << (int)gr_ev->amount[tileX][tileY] << endl; + } + break; + } + case df::block_square_event_type::world_construction: + { + df::block_square_event_world_constructionst * co_ev = (df::block_square_event_world_constructionst*)blev; + uint16_t bits = co_ev->tile_bitmask[tileY]; + out << "construction bits: " << bits << endl; + break; + } + default: + //out << "unhandled block event type!" << endl; + break; + } + } + + return CR_OK; } @@ -383,14 +450,10 @@ command_result df_bprobe (color_ostream &out, vector & parameters) break; } if(building.origin->is_room) //isRoom()) - out << ", is room"; - else - out << ", not a room"; + out << ", room"; if(building.origin->getBuildStage()!=building.origin->getMaxBuildStage()) out << ", in construction"; out.print("\n"); - - } return CR_OK; } diff --git a/plugins/prospector.cpp b/plugins/prospector.cpp index c90a66c5a..e2f1e9534 100644 --- a/plugins/prospector.cpp +++ b/plugins/prospector.cpp @@ -474,7 +474,7 @@ command_result prospector (color_ostream &con, vector & parameters) liquidWater.add(global_z); } - df::tiletype type = b->TileTypeAt(coord); + df::tiletype type = b->tiletypeAt(coord); df::tiletype_shape tileshape = tileShape(type); df::tiletype_material tilemat = tileMaterial(type); @@ -506,7 +506,7 @@ command_result prospector (color_ostream &con, vector & parameters) { case tiletype_material::SOIL: case tiletype_material::STONE: - layerMats[b->baseMaterialAt(coord)].add(global_z); + layerMats[b->layerMaterialAt(coord)].add(global_z); break; case tiletype_material::MINERAL: veinMats[b->veinMaterialAt(coord)].add(global_z); diff --git a/plugins/regrass.cpp b/plugins/regrass.cpp index dec091a92..5475862ae 100644 --- a/plugins/regrass.cpp +++ b/plugins/regrass.cpp @@ -83,7 +83,12 @@ command_result df_regrass (color_ostream &out, vector & parameters) { if ( tileShape(cur->tiletype[x][y]) != tiletype_shape::FLOOR || cur->designation[x][y].bits.subterranean - || cur->occupancy[x][y].bits.building) + || cur->occupancy[x][y].bits.building + || cur->occupancy[x][y].bits.no_grow) + continue; + + // don't touch furrowed tiles (dirt roads made on soil) + if(tileSpecial(cur->tiletype[x][y]) == tiletype_special::FURROWED) continue; int mat = tileMaterial(cur->tiletype[x][y]); @@ -93,6 +98,7 @@ command_result df_regrass (color_ostream &out, vector & parameters) ) continue; + // max = set amounts of all grass events on that tile to 100 if(max) { diff --git a/plugins/sort.cpp b/plugins/sort.cpp new file mode 100644 index 000000000..9d63b2c6c --- /dev/null +++ b/plugins/sort.cpp @@ -0,0 +1,528 @@ +#include "Core.h" +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" + +#include "modules/Gui.h" +#include "modules/Translation.h" +#include "modules/Units.h" + +#include "LuaTools.h" + +#include "DataDefs.h" +#include "df/ui.h" +#include "df/world.h" +#include "df/viewscreen_joblistst.h" +#include "df/viewscreen_unitlistst.h" +#include "df/viewscreen_layer_militaryst.h" +#include "df/viewscreen_layer_workshop_profilest.h" +#include "df/viewscreen_layer_noblelistst.h" +#include "df/viewscreen_layer_overall_healthst.h" +#include "df/viewscreen_dwarfmodest.h" +#include "df/viewscreen_petst.h" +#include "df/layer_object_listst.h" + +#include "MiscUtils.h" + +#include + +using std::vector; +using std::string; +using std::endl; +using namespace DFHack; +using namespace df::enums; + +using df::global::ui; +using df::global::world; +using df::global::ui_building_in_assign; +using df::global::ui_building_item_cursor; +using df::global::ui_building_assign_type; +using df::global::ui_building_assign_is_marked; +using df::global::ui_building_assign_units; +using df::global::ui_building_assign_items; + +static bool unit_list_hotkey(df::viewscreen *top); + +static command_result sort_units(color_ostream &out, vector & parameters); + +DFHACK_PLUGIN("sort"); + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &commands) +{ + commands.push_back(PluginCommand( + "sort-units", "Sort the visible unit list.", sort_units, unit_list_hotkey, + " sort-units order [order...]\n" + " Sort the unit list using the given sequence of comparisons.\n" + " The '<' prefix for an order makes undefined values sort first.\n" + " The '>' prefix reverses the sort order for defined values.\n" + " Unit order examples:\n" + " name, age, arrival, squad, squad_position, profession\n" + "The orderings are defined in hack/lua/plugins/sort/*.lua\n" + )); + return CR_OK; +} + +DFhackCExport command_result plugin_shutdown ( color_ostream &out ) +{ + return CR_OK; +} + +template +void reorder_vector(std::vector *pvec, const std::vector &order) +{ + assert(pvec->size() == order.size()); + + std::vector tmp(*pvec); + for (size_t i = 0; i < order.size(); i++) + (*pvec)[i] = tmp[order[i]]; +} + +template +void reorder_cursor(T *cursor, const std::vector &order) +{ + if (*cursor == 0) + return; + + for (size_t i = 0; i < order.size(); i++) + { + if (unsigned(*cursor) == order[i]) + { + *cursor = T(i); + break; + } + } +} + +bool parse_ordering_spec(color_ostream &out, lua_State *L, std::string type, const std::vector ¶ms) +{ + if (!lua_checkstack(L, params.size() + 2)) + return false; + + if (!Lua::PushModulePublic(out, L, "plugins.sort", "parse_ordering_spec")) + return false; + + Lua::Push(L, type); + for (size_t i = 0; i < params.size(); i++) + Lua::Push(L, params[i]); + + if (!Lua::SafeCall(out, L, params.size()+1, 1)) + return false; + + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return false; + } + + return true; +} + +bool read_order(color_ostream &out, lua_State *L, std::vector *order, size_t size) +{ + std::vector found; + + Lua::StackUnwinder frame(L, 1); + + if (!lua_istable(L, -1)) + { + out.printerr("Not a table returned as ordering.\n"); + return false; + } + + if (lua_rawlen(L, -1) != size) + { + out.printerr("Invalid ordering size: expected %d, actual %d\n", size, lua_rawlen(L, -1)); + return false; + } + + order->clear(); + order->resize(size); + found.resize(size); + + for (size_t i = 1; i <= size; i++) + { + lua_rawgeti(L, frame[1], i); + int v = lua_tointeger(L, -1); + lua_pop(L, 1); + + if (v < 1 || size_t(v) > size) + { + out.printerr("Order value out of range: %d\n", v); + return false; + } + + if (found[v-1]) + { + out.printerr("Duplicate order value: %d\n", v); + return false; + } + + found[v-1] = 1; + (*order)[i-1] = v-1; + } + + return true; +} + +template +bool compute_order(color_ostream &out, lua_State *L, int base, std::vector *order, const std::vector &key) +{ + lua_pushvalue(L, base+1); + Lua::PushVector(L, key, true); + lua_pushvalue(L, base+2); + + if (!Lua::SafeCall(out, L, 2, 1)) + return false; + + return read_order(out, L, order, key.size()); +} + +static bool ParseSpec(color_ostream &out, lua_State *L, const char *type, vector ¶ms) +{ + if (!parse_ordering_spec(out, L, "units", params)) + { + out.printerr("Invalid ordering specification for %s.\n", type); + return false; + } + + return true; +} + +#define PARSE_SPEC(type, params) \ + if (!ParseSpec(*pout, L, type, params)) return false; + +static void sort_null_first(vector ¶meters) +{ + vector_insert_at(parameters, 0, std::string("(vector_get(layer->layer_objects,idx)); +} + +static bool maybe_sort_units(color_ostream *pout, lua_State *L, + df::viewscreen *screen, vector ¶meters) +{ + Lua::StackUnwinder top(L); + + if (L) + { + if (!Lua::PushModulePublic(*pout, L, "plugins.sort", "make_sort_order")) + { + pout->printerr("Cannot access the sorter function.\n"); + return false; + } + } + + std::vector order; + + if (auto units = strict_virtual_cast(screen)) + { + if (!L) return true; + + /* + * Sort units in the 'u'nit list screen. + */ + + PARSE_SPEC("units", parameters); + + int page = units->page; + + if (compute_order(*pout, L, top, &order, units->units[page])) + { + reorder_cursor(&units->cursor_pos[page], order); + reorder_vector(&units->units[page], order); + reorder_vector(&units->jobs[page], order); + } + + return true; + } + else if (auto jobs = strict_virtual_cast(screen)) + { + if (!L) return true; + + /* + * Sort units in the 'j'ob list screen. + */ + + PARSE_SPEC("units", parameters); + + if (compute_order(*pout, L, top, &order, jobs->units)) + { + reorder_cursor(&jobs->cursor_pos, order); + reorder_vector(&jobs->units, order); + reorder_vector(&jobs->jobs, order); + } + + return true; + } + else if (auto military = strict_virtual_cast(screen)) + { + switch (military->page) + { + case df::viewscreen_layer_militaryst::Positions: + { + auto &candidates = military->positions.candidates; + auto list3 = getLayerList(military, 2); + + /* + * Sort candidate units in the 'p'osition page of the 'm'ilitary screen. + */ + + if (list3 && !candidates.empty() && list3->bright) + { + if (!L) return true; + + PARSE_SPEC("units", parameters); + + if (compute_order(*pout, L, top, &order, candidates)) + { + reorder_cursor(&list3->cursor, order); + reorder_vector(&candidates, order); + } + + return true; + } + + return false; + } + + default: + return false; + } + } + else if (auto profile = strict_virtual_cast(screen)) + { + auto list1 = getLayerList(profile, 0); + + if (!list1) return false; + if (!L) return true; + + /* + * Sort units in the workshop 'q'uery 'P'rofile modification screen. + */ + + PARSE_SPEC("units", parameters); + + if (compute_order(*pout, L, top, &order, profile->workers)) + { + reorder_cursor(&list1->cursor, order); + reorder_vector(&profile->workers, order); + } + + return true; + } + else if (auto nobles = strict_virtual_cast(screen)) + { + switch (nobles->mode) + { + case df::viewscreen_layer_noblelistst::Appoint: + { + auto list2 = getLayerList(nobles, 1); + + /* + * Sort units in the appointment candidate list of the 'n'obles screen. + */ + + if (list2) + { + if (!L) return true; + + sort_null_first(parameters); + PARSE_SPEC("units", parameters); + + std::vector units; + for (size_t i = 0; i < nobles->candidates.size(); i++) + units.push_back(nobles->candidates[i]->unit); + + if (compute_order(*pout, L, top, &order, units)) + { + reorder_cursor(&list2->cursor, order); + reorder_vector(&nobles->candidates, order); + } + + return true; + } + + return false; + } + + default: + return false; + } + } + else if (auto animals = strict_virtual_cast(screen)) + { + switch (animals->mode) + { + case df::viewscreen_petst::List: + { + if (!L) return true; + + /* + * Sort animal units in the Animal page of the 'z' status screen. + */ + + PARSE_SPEC("units", parameters); + + std::vector units; + for (size_t i = 0; i < animals->animal.size(); i++) + units.push_back(animals->is_vermin[i] ? NULL : (df::unit*)animals->animal[i]); + + if (compute_order(*pout, L, top, &order, units)) + { + reorder_cursor(&animals->cursor, order); + reorder_vector(&animals->animal, order); + reorder_vector(&animals->is_vermin, order); + reorder_vector(&animals->pet_info, order); + reorder_vector(&animals->is_tame, order); + reorder_vector(&animals->is_adopting, order); + } + + return true; + } + + case df::viewscreen_petst::SelectTrainer: + { + if (!L) return true; + + /* + * Sort candidate trainers in the Animal page of the 'z' status screen. + */ + + sort_null_first(parameters); + PARSE_SPEC("units", parameters); + + if (compute_order(*pout, L, top, &order, animals->trainer_unit)) + { + reorder_cursor(&animals->trainer_cursor, order); + reorder_vector(&animals->trainer_unit, order); + reorder_vector(&animals->trainer_mode, order); + } + + return true; + } + + default: + return false; + } + } + else if (auto health = strict_virtual_cast(screen)) + { + auto list1 = getLayerList(health, 0); + + if (!list1) return false; + if (!L) return true; + + /* + * Sort units in the Health page of the 'z' status screen. + */ + + PARSE_SPEC("units", parameters); + + if (compute_order(*pout, L, top, &order, health->unit)) + { + reorder_cursor(&list1->cursor, order); + reorder_vector(&health->unit, order); + reorder_vector(&health->bits1, order); + reorder_vector(&health->bits2, order); + reorder_vector(&health->bits3, order); + } + + return true; + } + else if (strict_virtual_cast(screen)) + { + switch (ui->main.mode) + { + case ui_sidebar_mode::Burrows: + if (!L) return true; + + /* + * Sort burrow member candidate units in the 'w' sidebar mode. + */ + + PARSE_SPEC("units", parameters); + + if (compute_order(*pout, L, top, &order, ui->burrows.list_units)) + { + reorder_cursor(&ui->burrows.unit_cursor_pos, order); + reorder_vector(&ui->burrows.list_units, order); + reorder_vector(&ui->burrows.sel_units, order); + } + + return true; + + case ui_sidebar_mode::QueryBuilding: + if (!ui_building_in_assign || !*ui_building_in_assign) + return false; + // fall through for building owner / chain assign animal + + case ui_sidebar_mode::ZonesPenInfo: + if (ui_building_item_cursor && + ui_building_assign_type && + ui_building_assign_is_marked && + ui_building_assign_units && + ui_building_assign_items && + ui_building_assign_type->size() == ui_building_assign_units->size() && + !ui_building_assign_type->empty()) + { + if (!L) return true; + + /* + * Sort building owner candidate units in the 'q' sidebar mode, + * or pen assignment candidate units in 'z'->'N', or cage assignment. + */ + + // TODO: better way + bool is_assign_owner = ((*ui_building_assign_type)[0] == -1); + + if (is_assign_owner) + sort_null_first(parameters); + + PARSE_SPEC("units", parameters); + + if (compute_order(*pout, L, top, &order, *ui_building_assign_units)) + { + reorder_cursor(ui_building_item_cursor, order); + reorder_vector(ui_building_assign_type, order); + reorder_vector(ui_building_assign_units, order); + + if (ui_building_assign_units->size() == ui_building_assign_items->size()) + reorder_vector(ui_building_assign_items, order); + if (ui_building_assign_units->size() == ui_building_assign_is_marked->size()) + reorder_vector(ui_building_assign_is_marked, order); + } + + return true; + } + return false; + + default: + return false; + } + } + else + return false; +} + +static bool unit_list_hotkey(df::viewscreen *screen) +{ + vector dummy; + return maybe_sort_units(NULL, NULL, screen, dummy); +} + +static command_result sort_units(color_ostream &out, vector ¶meters) +{ + if (parameters.empty()) + return CR_WRONG_USAGE; + + auto L = Lua::Core::State; + auto screen = Core::getInstance().getTopViewscreen(); + + if (!maybe_sort_units(&out, L, screen, parameters)) + return CR_WRONG_USAGE; + + return CR_OK; +} diff --git a/plugins/tiletypes.cpp b/plugins/tiletypes.cpp index 2cd1770b2..190bda7cd 100644 --- a/plugins/tiletypes.cpp +++ b/plugins/tiletypes.cpp @@ -670,6 +670,9 @@ command_result executePaintJob(color_ostream &out) coord_vec all_tiles = brush->points(map, cursor); out.print("working...\n"); + // Force the game to recompute its walkability cache + df::global::world->reindex_pathfinding = true; + for (coord_vec::iterator iter = all_tiles.begin(); iter != all_tiles.end(); ++iter) { const df::tiletype source = map.tiletypeAt(*iter); diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index aa3c94a55..1092c86ae 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -364,7 +364,7 @@ static ProtectedJob *get_known(int id) static bool isSupportedJob(df::job *job) { - return job->misc_links.empty() && + return job->specific_refs.empty() && Job::getHolder(job) && (!job->job_items.empty() || job->job_type == job_type::CollectClay || @@ -892,6 +892,9 @@ static void guess_job_material(df::job *job, MaterialInfo &mat, df::dfhack_mater case item_type::WOOD: mat_mask.bits.wood = mat_mask.bits.wood2 = true; break; + + default: + break; } } } @@ -1080,12 +1083,11 @@ static bool itemInRealJob(df::item *item) if (!item->flags.bits.in_job) return false; - if (item->jobs.size() != 1 || - item->jobs[0]->unk1 != 2 || - item->jobs[0]->job == NULL) + auto ref = Items::getSpecificRef(item, specific_ref_type::JOB); + if (!ref || !ref->job) return true; - return ENUM_ATTR(job_type, type, item->jobs[0]->job->job_type) + return ENUM_ATTR(job_type, type, ref->job->job_type) != job_type_class::Hauling; } @@ -1148,6 +1150,9 @@ static void map_job_items(color_ostream &out) if (item->getTotalDimension() < 10000) is_invalid = true; break; + + default: + break; } if (item->flags.bits.melt && !item->flags.bits.owned && !itemBusy(item)) diff --git a/plugins/zone.cpp b/plugins/zone.cpp index ee6abf327..ce610128b 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -366,6 +366,8 @@ void cageInfo(color_ostream & out, df::building* building, bool verbose); void chainInfo(color_ostream & out, df::building* building, bool verbose); bool isBuiltCageAtPos(df::coord pos); bool isInBuiltCageRoom(df::unit*); +bool isNaked(df::unit *); +bool isTamable(df::unit *); int32_t getUnitAge(df::unit* unit) { @@ -620,6 +622,20 @@ bool isTrainableHunting(df::unit* unit) return false; } +bool isTamable(df::unit* unit) +{ + df::creature_raw *raw = df::global::world->raws.creatures.all[unit->race]; + size_t sizecas = raw->caste.size(); + for (size_t j = 0; j < sizecas;j++) + { + df::caste_raw *caste = raw->caste[j]; + if(caste->flags.is_set(caste_raw_flags::PET) || + caste->flags.is_set(caste_raw_flags::PET_EXOTIC)) + return true; + } + return false; +} + bool isMale(df::unit* unit) { return unit->sex == 1; @@ -644,6 +660,12 @@ bool hasValidMapPos(df::unit* unit) return false; } +bool isNaked(df::unit* unit) +{ + return (unit->inventory.empty()); +} + + int getUnitIndexFromId(df::unit* unit_) { for (size_t i=0; i < world->units.all.size(); i++) @@ -1606,8 +1628,8 @@ void zoneInfo(color_ostream & out, df::building* building, bool verbose) out << ", pen/pasture"; else if (civ->zone_flags.bits.pit_pond) { - out << " (pit flags:" << civ->pit_flags << ")"; - if(civ->pit_flags & 1) + out << " (pit flags:" << civ->pit_flags.whole << ")"; + if(civ->pit_flags.bits.is_pond) out << ", pond"; else out << ", pit"; @@ -1748,6 +1770,10 @@ command_result df_zone (color_ostream &out, vector & parameters) bool find_not_milkable = false; bool find_named = false; bool find_not_named = false; + bool find_naked = false; + bool find_not_naked = false; + bool find_tamable = false; + bool find_not_tamable = false; bool find_agemin = false; bool find_agemax = false; @@ -2126,6 +2152,24 @@ command_result df_zone (color_ostream &out, vector & parameters) find_not_egglayer = true; invert_filter=false; } + else if(p == "naked" && !invert_filter) + { + find_naked = true; + } + else if(p == "naked" && invert_filter) + { + find_not_naked = true; + invert_filter=false; + } + else if(p == "tamable" && !invert_filter) + { + find_tamable = true; + } + else if(p == "tamable" && invert_filter) + { + find_not_tamable = true; + invert_filter=false; + } else if(p == "grazer" && !invert_filter) { find_grazer = true; @@ -2393,6 +2437,10 @@ command_result df_zone (color_ostream &out, vector & parameters) || (find_not_female && isFemale(unit)) || (find_named && !unit->name.has_name) || (find_not_named && unit->name.has_name) + || (find_naked && !isNaked(unit)) + || (find_not_naked && isNaked(unit)) + || (find_tamable && !isTamable(unit)) + || (find_not_tamable && isTamable(unit)) || (find_trainable_war && (isWar(unit) || isHunter(unit) || !isTrainableWar(unit))) || (find_not_trainable_war && isTrainableWar(unit)) // hm, is this check enough? || (find_trainable_hunting && (isWar(unit) || isHunter(unit) || !isTrainableHunting(unit))) diff --git a/scripts/lua-example.lua b/scripts/lua-example.lua new file mode 100644 index 000000000..044849ed7 --- /dev/null +++ b/scripts/lua-example.lua @@ -0,0 +1,6 @@ +-- Example of a lua script. + +run_count = (run_count or 0) + 1 + +print('Arguments: ',...) +print('Command called '..run_count..' times.') diff --git a/scripts/quicksave.lua b/scripts/quicksave.lua new file mode 100644 index 000000000..c54cc730b --- /dev/null +++ b/scripts/quicksave.lua @@ -0,0 +1,28 @@ +-- Makes the game immediately save the state. + +if not dfhack.isMapLoaded() then + dfhack.printerr("World and map aren't loaded.") + return +end + +local ui_main = df.global.ui.main +local flags4 = df.global.d_init.flags4 + +local function restore_autobackup() + if ui_main.autosave_request and dfhack.isMapLoaded() then + dfhack.timeout(10, 'frames', restore_autobackup) + else + flags4.AUTOBACKUP = true + end +end + +-- Request auto-save +ui_main.autosave_request = true + +-- And since it will overwrite the backup, disable it temporarily +if flags4.AUTOBACKUP then + flags4.AUTOBACKUP = false + restore_autobackup() +end + +print 'The game should save the state now.'