Merge branch 'develop' into avoid_rebuilding_df_statics_if_core_h_changes

develop
lethosor 2019-10-03 23:23:36 -04:00
commit f3658db85f
65 changed files with 4982 additions and 1558 deletions

7
.gitignore vendored

@ -34,6 +34,10 @@ build/install_manifest.txt
build/_CPack_Packages build/_CPack_Packages
build/dfhack-*.zip build/dfhack-*.zip
build/dfhack-*.bz2 build/dfhack-*.bz2
build/*ninja*
build/compile_commands.json
build/dfhack_setarch.txt
build/ImportExecutables.cmake
# Python binding binaries # Python binding binaries
*.pyc *.pyc
@ -58,5 +62,8 @@ tags
/build/win32/DF_PATH.txt /build/win32/DF_PATH.txt
/.vs /.vs
# CLion
.idea
# custom plugins # custom plugins
/plugins/CMakeLists.custom.txt /plugins/CMakeLists.custom.txt

@ -28,7 +28,7 @@ matrix:
- g++-4.8 - g++-4.8
before_install: before_install:
- export DF_VERSION=$(sh travis/get-df-version.sh) - export DF_VERSION=$(sh travis/get-df-version.sh)
- export DF_FOLDER="$HOME/DF-travis/$DF_VERSION" - export DF_FOLDER="$HOME/DF-travis/$DF_VERSION/df_linux"
- pip install --user "sphinx==1.4" "requests[security]" - pip install --user "sphinx==1.4" "requests[security]"
- sh travis/build-lua.sh - sh travis/build-lua.sh
- sh travis/download-df.sh - sh travis/download-df.sh
@ -56,9 +56,7 @@ script:
- python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" - python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt"
before_cache: before_cache:
- cat "$DF_FOLDER/stderr.log" - cat "$DF_FOLDER/stderr.log"
- rm -rf "$DF_FOLDER/hack" - rm -rf "$DF_FOLDER"
- rm -rf "$DF_FOLDER/dfhack-config"
- rm -f "$DF_FOLDER"/*.log
notifications: notifications:
email: false email: false
irc: irc:

@ -21,6 +21,12 @@ OPTION(REMOVE_SYMBOLS_FROM_DF_STUBS "Remove debug symbols from DF stubs. (Reduce
cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR) cmake_minimum_required(VERSION 2.8.12 FATAL_ERROR)
project(dfhack) project(dfhack)
if("${CMAKE_GENERATOR}" STREQUAL Ninja)
if("${CMAKE_VERSION}" VERSION_LESS 3.9)
message(WARNING "You are using an old version of CMake (${CMAKE_VERSION}) with Ninja. This may result in ninja errors - see docs/Compile.rst for more details. Upgrading your CMake version is recommended.")
endif()
endif()
macro(CHECK_GCC COMPILER_PATH) macro(CHECK_GCC COMPILER_PATH)
execute_process(COMMAND ${COMPILER_PATH} -dumpversion OUTPUT_VARIABLE GCC_VERSION_OUT) execute_process(COMMAND ${COMPILER_PATH} -dumpversion OUTPUT_VARIABLE GCC_VERSION_OUT)
string(STRIP "${GCC_VERSION_OUT}" GCC_VERSION_OUT) string(STRIP "${GCC_VERSION_OUT}" GCC_VERSION_OUT)
@ -290,6 +296,7 @@ if(WIN32)
endif() endif()
endif() endif()
option(EXTERNAL_LIBSTDCXX "macOS only: Avoid installing the DFHack-provided libstdc++." OFF)
if(APPLE) if(APPLE)
# libstdc++ (GCC 4.8.5 for OS X 10.6) # libstdc++ (GCC 4.8.5 for OS X 10.6)
# fixes crash-on-unwind bug in DF's libstdc++ # fixes crash-on-unwind bug in DF's libstdc++
@ -340,9 +347,10 @@ if(APPLE)
endif() endif()
endif() endif()
install(PROGRAMS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib if(NOT EXTERNAL_LIBSTDCXX)
DESTINATION ./hack/) install(PROGRAMS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib
DESTINATION ./hack/)
endif()
endif() endif()
#### expose depends #### #### expose depends ####

@ -3,8 +3,6 @@
[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)](https://travis-ci.org/DFHack/dfhack) [![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)](https://travis-ci.org/DFHack/dfhack)
[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)](https://dfhack.readthedocs.org) [![Documentation Status](https://readthedocs.org/projects/dfhack/badge)](https://dfhack.readthedocs.org)
[![License](https://img.shields.io/badge/license-ZLib-blue.svg)](https://en.wikipedia.org/wiki/Zlib_License) [![License](https://img.shields.io/badge/license-ZLib-blue.svg)](https://en.wikipedia.org/wiki/Zlib_License)
[![Github Issues](http://githubbadges.herokuapp.com/DFHack/dfhack/issues)](https://github.com/DFHack/dfhack/issues)
[![Open Pulls](http://githubbadges.herokuapp.com/DFHack/dfhack/pulls)](https://github.com/DFHack/dfhack/pulls)
DFHack is a Dwarf Fortress memory access library, distributed with scripts DFHack is a Dwarf Fortress memory access library, distributed with scripts
and plugins implementing a wide variety of useful functions and tools. and plugins implementing a wide variety of useful functions and tools.

@ -95,6 +95,12 @@ LIST(APPEND SRC_LIBLUA ${HDR_LIBLUA})
ADD_LIBRARY ( lua SHARED ${SRC_LIBLUA} ) ADD_LIBRARY ( lua SHARED ${SRC_LIBLUA} )
TARGET_LINK_LIBRARIES ( lua ${LIBS}) TARGET_LINK_LIBRARIES ( lua ${LIBS})
if (MSVC)
target_compile_options(lua PRIVATE /FI dfhack_llimits.h)
else ()
target_compile_options(lua PRIVATE -include dfhack_llimits.h)
endif ()
install(TARGETS lua install(TARGETS lua
LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION}
RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION})

@ -0,0 +1,61 @@
/**
Copyright © 2018 Pauli <suokkos@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
#ifdef _MSC_VER
#include <windows.h>
#else
#include <pthread.h>
#endif
#include <stdlib.h>
/*! \file dfhack_llimits.h
* dfhack specific lua porting header that overrides lua defaults for thread
* safety.
*/
#ifdef _MSC_VER
typedef CRITICAL_SECTION mutex_t;
#else
typedef pthread_mutex_t mutex_t;
#endif
struct lua_extra_state {
mutex_t* mutex;
};
#define luai_mutex(L) ((lua_extra_state*)lua_getextraspace(L))->mutex
#ifdef _MSC_VER
#define luai_userstateopen(L) luai_mutex(L) = (mutex_t*)malloc(sizeof(mutex_t)); InitializeCriticalSection(luai_mutex(L))
#define luai_userstateclose(L) lua_unlock(L); DeleteCriticalSection(luai_mutex(L)); free(luai_mutex(L))
#define lua_lock(L) EnterCriticalSection(luai_mutex(L))
#define lua_unlock(L) LeaveCriticalSection(luai_mutex(L))
#else
#define luai_userstateopen(L) luai_mutex(L) = (mutex_t*)malloc(sizeof(mutex_t)); *luai_mutex(L) = PTHREAD_MUTEX_INITIALIZER
#define luai_userstateclose(L) lua_unlock(L); pthread_mutex_destroy(luai_mutex(L)); free(luai_mutex(L))
#define lua_lock(L) pthread_mutex_lock(luai_mutex(L))
#define lua_unlock(L) pthread_mutex_unlock(luai_mutex(L))
#endif

@ -262,6 +262,9 @@ enable \
# enable mouse controls and sand indicator in embark screen # enable mouse controls and sand indicator in embark screen
embark-tools enable sticky sand mouse embark-tools enable sticky sand mouse
# enable option to enter embark assistant
enable embark-assistant
########### ###########
# Scripts # # Scripts #
########### ###########

@ -51,6 +51,7 @@ gchristopher gchristopher
grubsteak grubsteak grubsteak grubsteak
Harlan Playford playfordh Harlan Playford playfordh
Hayati Ayguen hayguen Hayati Ayguen hayguen
Herwig Hochleitner bendlas
IndigoFenix IndigoFenix
James Logsdon jlogsdon James Logsdon jlogsdon
Japa JapaMala Japa JapaMala
@ -68,6 +69,7 @@ Kris Parker kaypy
Kromtec Kromtec Kromtec Kromtec
Kurik Amudnil Kurik Amudnil
Lethosor lethosor Lethosor lethosor
LordGolias LordGolias
Mason11987 Mason11987 Mason11987 Mason11987
Matt Regul mattregul Matt Regul mattregul
Matthew Cline Matthew Cline
@ -86,6 +88,7 @@ Milo Christiansen milochristiansen
MithrilTuxedo MithrilTuxedo MithrilTuxedo MithrilTuxedo
mizipzor mizipzor mizipzor mizipzor
moversti moversti moversti moversti
napagokc napagokc
Neil Little nmlittle Neil Little nmlittle
Nick Rart nickrart comestible Nick Rart nickrart comestible
Nikolay Amiantov abbradar Nikolay Amiantov abbradar

@ -68,6 +68,10 @@ and more, please see `contributing-code`.
Build settings Build settings
============== ==============
This section describes build configuration options that apply to all platforms.
If you don't have a working build environment set up yet, follow the instructions
in the platform-specific sections below first, then come back here.
Generator Generator
--------- ---------
@ -86,6 +90,12 @@ much slower than Ninja builds.
generator cannot be changed after ``cmake`` has been run without creating a generator cannot be changed after ``cmake`` has been run without creating a
new build folder. Do not forget to specify this option. new build folder. Do not forget to specify this option.
CMake versions 3.6 and older, and possibly as recent as 3.9, are known to
produce project files with dependency cycles that fail to build
(see :issue:`1369`). Obtaining a recent version of CMake is recommended, either from
`cmake.org <https://cmake.org/download/>`_ or through a package manager. See
the sections below for more platform-specific directions for installing CMake.
Build type Build type
---------- ----------
@ -142,6 +152,12 @@ Before you can build anything, you'll also need ``cmake``. It is advisable to
also get ``ccmake`` on distributions that split the cmake package into multiple also get ``ccmake`` on distributions that split the cmake package into multiple
parts. parts.
You will need pthread; most systems should have this already. Note that older
CMake versions may have trouble detecting pthread, so if you run into
pthread-related errors and pthread is installed, you may need to upgrade CMake,
either by downloading it from `cmake.org <https://cmake.org/download/>`_ or
through your package manager, if possible.
You also need zlib, libsdl (1.2, not sdl2, like DF), perl, and the XML::LibXML You also need zlib, libsdl (1.2, not sdl2, like DF), perl, and the XML::LibXML
and XML::LibXSLT perl packages (for the code generation parts). You should be and XML::LibXSLT perl packages (for the code generation parts). You should be
able to find them in your distro repositories. able to find them in your distro repositories.
@ -158,6 +174,10 @@ Here are some package install commands for various platforms:
apt-get install gcc cmake ninja-build git zlib1g-dev libsdl1.2-dev libxml-libxml-perl libxml-libxslt-perl apt-get install gcc cmake ninja-build git zlib1g-dev libsdl1.2-dev libxml-libxml-perl libxml-libxslt-perl
* On Fedora::
yum install gcc-c++ cmake ninja-build git zlib-devel SDL-devel perl-core perl-XML-LibXML perl-XML-LibXSLT ruby
* Debian and derived distros should have similar requirements to Ubuntu. * Debian and derived distros should have similar requirements to Ubuntu.

@ -3920,6 +3920,22 @@ A class with all the tcp functionality.
Tries connecting to that address and port. Returns ``client`` object. Tries connecting to that address and port. Returns ``client`` object.
.. _map-render:
map-render
==========
A way to ask df to render a slice of map. This uses native df rendering function so it's highly dependant on
df settings (e.g. used tileset, colors, if using graphics or not and so on...)
Functions
---------
- ``render_map_rect(x,y,z,w,h)``
returns a table with w*h*4 entries of rendered tiles. The format is same as ``df.global.gps.screen`` (tile,foreground,bright,background).
.. _cxxrandom: .. _cxxrandom:
cxxrandom cxxrandom

@ -561,16 +561,19 @@ directly to the main dwarf mode screen.
Professions Professions
----------- -----------
The manipulator plugin supports saving Professions: a named set of Labors labors that can be The manipulator plugin supports saving professions: a named set of labors that can be
quickly applied to one or multiple Dwarves. quickly applied to one or multiple dwarves.
To save a Profession highlight a Dwarf and press :kbd:`P`. The Profession will be saved using To save a profession, highlight a dwarf and press :kbd:`P`. The profession will be saved using
the Custom Profession Name of the Dwarf, or the default for that Dwarf if no Custom Profession the custom profession name of the dwarf, or the default for that dwarf if no custom profession
Name has been set. name has been set.
To apply a Profession either highlight a single Dwarf, or select multiple with :kbd:`x`, and press To apply a profession, either highlight a single dwarf or select multiple with
:kbd:`p` to select the Profession to apply. All labors for the selected Dwarves will be reset to :kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for
the labors of the chosen Profession. the selected dwarves will be reset to the labors of the chosen profession.
Professions are saved as human-readable text files in the "professions" folder
within the DF folder, and can be edited or deleted there.
.. comment - the link target "search" is reserved for the Sphinx search page .. comment - the link target "search" is reserved for the Sphinx search page
.. _search-plugin: .. _search-plugin:
@ -2845,4 +2848,5 @@ in the `lua-api` file under `lua-plugins`:
* `eventful` * `eventful`
* `building-hacks` * `building-hacks`
* `luasocket` * `luasocket`
* `map-render`
* `cxxrandom` * `cxxrandom`

@ -37,6 +37,75 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
================================================================================ ================================================================================
# Future # Future
## New Scripts
- `dwarf-op`: optimizes dwarves for fort-mode work; makes managing labors easier
- `forget-dead-body`: removes emotions associated with seeing a dead body
- `gui/create-tree`: creates a tree at the selected tile
- `linger`: takes over your killer in adventure mode
- `modtools/create-tree`: creates a tree
- `modtools/spawn-liquid`: spawns water or lava at the specified coordinates
- `unretire-anyone`: turns any historical figure into a playable adventurer
## Fixes
- `bodyswap`: fixed companion list not being updated often enough
- `digfort`: now accounts for z-level changes when calculating maximum y dimension
- `embark-assistant`:
- fixed bug causing crash on worlds without generated metals (as well as pruning vectors as originally intended).
- fixed bug causing mineral matching to fail to cut off at the magma sea, reporting presence of things that aren't (like DF does currently).
- `gui/autogems`: fixed error when no world is loaded
- `gui/companion-order`:
- fixed error when resetting group leaders
- ``leave`` now properly removes companion links
- `gui/create-item`: fixed module support - can now be used from other scripts
- `gui/stamper`:
- stopped "invert" from resetting the designation type
- switched to using DF's designation keybindings instead of custom bindings
- fixed some typos and text overlapping
- `modtools/create-unit`:
- fixed an error associating historical entities with units
- stopped recalculating health to avoid newly-created citizens triggering a "recover wounded" job
- fixed units created in arena mode having blank names
- fixed units created in arena mode having the wrong race and/or interaction effects applied after creating units manually in-game
- `modtools/reaction-product-trigger`:
- fixed an error dealing with reactions in adventure mode
- blocked ``\\BUILDING_ID`` for adventure mode reactions
- fixed ``-clear`` to work without passing other unneeded arguments
- `modtools/reaction-trigger`:
- fixed a bug when determining whether a command was run
- fixed handling of ``-resetPolicy``
- `stonesense`:
- fixed crash due to wagons and other soul-less creatures
- `tame`: now sets the civ ID of tamed animals (fixes compatibility with `autobutcher`)
## Misc Improvements
- `bodyswap`: added arena mode support
- `combine-drinks`: added more default output, similar to `combine-plants`
- `embark-assistant`:
- changed waterfall detection to look for level drop rather than just presence
- changed matching to take incursions, i.e. parts of other biomes, into consideration when evaluating tiles. This allows for e.g. finding multiple biomes on single tile embarks.
- changed overlay display to show when incursion surveying is incomplete
- changed overlay display to show evil weather
- `exportlegends`: added rivers to custom XML export
- `exterminate`: added support for a special ``enemy`` caste
- `modtools/create-unit`:
- added the ability to specify ``\\LOCAL`` for the fort group entity
- `modtools/reaction-trigger`:
- added ``-ignoreWorker``: ignores the worker when selecting the targets
- changed the default behavior to skip inactive/dead units; added ``-dontSkipInactive`` to include creatures that are inactive
- added ``-range``: controls how far elligible targets can be from the workshop
- syndromes now are applied before commands are run, not after
- if both a command and a syndrome are given, the command only runs if the syndrome could be applied
- `RemoteFortressReader`:
- added a basic framework for controlling and reading the menus in DF (currently only supports the building menu)
- added support for reading item raws
- added a check for whether or not the game is currently saving or loading, for utilities to check if it's safe to read from DF
- added unit facing direction estimate and position within tiles
- added unit age
- added unit wounds
## Internals
- `stonesense`: fixed some OpenGL build issues on Linux
# 0.44.12-r2 # 0.44.12-r2
## New Plugins ## New Plugins

@ -131,6 +131,7 @@ include/modules/Maps.h
include/modules/Materials.h include/modules/Materials.h
include/modules/Notes.h include/modules/Notes.h
include/modules/Once.h include/modules/Once.h
include/modules/Persistence.h
include/modules/Random.h include/modules/Random.h
include/modules/Renderer.h include/modules/Renderer.h
include/modules/Screen.h include/modules/Screen.h
@ -157,6 +158,7 @@ modules/Maps.cpp
modules/Materials.cpp modules/Materials.cpp
modules/Notes.cpp modules/Notes.cpp
modules/Once.cpp modules/Once.cpp
modules/Persistence.cpp
modules/Random.cpp modules/Random.cpp
modules/Renderer.cpp modules/Renderer.cpp
modules/Screen.cpp modules/Screen.cpp
@ -264,10 +266,14 @@ FILE(GLOB GENERATE_INPUT_SCRIPTS ${dfapi_SOURCE_DIR}/xml/*.pm ${dfapi_SOURCE_DIR
FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/df.*.xml) FILE(GLOB GENERATE_INPUT_XMLS ${dfapi_SOURCE_DIR}/xml/df.*.xml)
set(CODEGEN_OUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml) set(CODEGEN_OUT ${dfapi_SOURCE_DIR}/include/df/codegen.out.xml)
LIST(APPEND CODEGEN_OUT ${GENERATED_HDRS}) IF(NOT("${CMAKE_GENERATOR}" STREQUAL Ninja))
# use BYPRODUCTS instead under Ninja to avoid rebuilds
LIST(APPEND CODEGEN_OUT ${GENERATED_HDRS})
ENDIF()
ADD_CUSTOM_COMMAND( ADD_CUSTOM_COMMAND(
OUTPUT ${CODEGEN_OUT} OUTPUT ${CODEGEN_OUT}
BYPRODUCTS ${GENERATED_HDRS}
COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/xml/codegen.pl COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/xml/codegen.pl
${CMAKE_CURRENT_SOURCE_DIR}/xml ${CMAKE_CURRENT_SOURCE_DIR}/xml
${CMAKE_CURRENT_SOURCE_DIR}/include/df ${CMAKE_CURRENT_SOURCE_DIR}/include/df

@ -480,7 +480,7 @@ namespace DFHack
{ {
char seq[64]; char seq[64];
int cols = get_columns(); int cols = get_columns();
int plen = prompt.size(); int plen = prompt.size() % cols;
int len = raw_buffer.size(); int len = raw_buffer.size();
int begin = 0; int begin = 0;
int cooked_cursor = raw_cursor; int cooked_cursor = raw_cursor;
@ -493,7 +493,15 @@ namespace DFHack
} }
if (plen+len > cols) if (plen+len > cols)
len -= plen+len - cols; len -= plen+len - cols;
std::string mbstr = toLocaleMB(raw_buffer.substr(begin,len)); std::string mbstr;
try {
mbstr = toLocaleMB(raw_buffer.substr(begin,len));
}
catch (std::out_of_range&) {
// fallback check in case begin is still out of range
// (this behaves badly but at least doesn't crash)
mbstr = toLocaleMB(raw_buffer);
}
/* Cursor to left edge */ /* Cursor to left edge */
snprintf(seq,64,"\x1b[1G"); snprintf(seq,64,"\x1b[1G");
if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return;

@ -54,6 +54,7 @@ using namespace std;
#include "modules/World.h" #include "modules/World.h"
#include "modules/Graphic.h" #include "modules/Graphic.h"
#include "modules/Windows.h" #include "modules/Windows.h"
#include "modules/Persistence.h"
#include "RemoteServer.h" #include "RemoteServer.h"
#include "RemoteTools.h" #include "RemoteTools.h"
#include "LuaTools.h" #include "LuaTools.h"
@ -135,6 +136,9 @@ struct Core::Private
{ {
std::thread iothread; std::thread iothread;
std::thread hotkeythread; std::thread hotkeythread;
bool last_autosave_request{false};
bool was_load_save{false};
}; };
struct CommandDepthCounter struct CommandDepthCounter
@ -1427,6 +1431,7 @@ bool Core::loadScriptFile(color_ostream &out, string fname, bool silent)
static void run_dfhack_init(color_ostream &out, Core *core) static void run_dfhack_init(color_ostream &out, Core *core)
{ {
CoreSuspender lock;
if (!df::global::world || !df::global::ui || !df::global::gview) if (!df::global::world || !df::global::ui || !df::global::gview)
{ {
out.printerr("Key globals are missing, skipping loading dfhack.init.\n"); out.printerr("Key globals are missing, skipping loading dfhack.init.\n");
@ -1528,6 +1533,7 @@ Core::Core() :
HotkeyMutex{}, HotkeyMutex{},
HotkeyCond{}, HotkeyCond{},
alias_mutex{}, alias_mutex{},
started{false},
misc_data_mutex{}, misc_data_mutex{},
CoreSuspendMutex{}, CoreSuspendMutex{},
CoreWakeup{}, CoreWakeup{},
@ -1537,7 +1543,7 @@ Core::Core() :
// init the console. This must be always the first step! // init the console. This must be always the first step!
plug_mgr = 0; plug_mgr = 0;
errorstate = false; errorstate = false;
started = false; vinfo = 0;
memset(&(s_mods), 0, sizeof(s_mods)); memset(&(s_mods), 0, sizeof(s_mods));
// set up hotkey capture // set up hotkey capture
@ -1547,7 +1553,6 @@ Core::Core() :
last_pause_state = false; last_pause_state = false;
top_viewscreen = NULL; top_viewscreen = NULL;
screen_window = NULL; screen_window = NULL;
server = NULL;
color_ostream::log_errors_to_stderr = true; color_ostream::log_errors_to_stderr = true;
@ -1765,6 +1770,8 @@ bool Core::Init()
// create plugin manager // create plugin manager
plug_mgr = new PluginManager(this); plug_mgr = new PluginManager(this);
plug_mgr->init(); plug_mgr->init();
cerr << "Starting the TCP listener.\n";
auto listen = ServerMain::listen(RemoteClient::GetDefaultPort());
IODATA *temp = new IODATA; IODATA *temp = new IODATA;
temp->core = this; temp->core = this;
temp->plug_mgr = plug_mgr; temp->plug_mgr = plug_mgr;
@ -1789,9 +1796,7 @@ bool Core::Init()
started = true; started = true;
modstate = 0; modstate = 0;
cerr << "Starting the TCP listener.\n"; if (!listen.get())
server = new ServerMain();
if (!server->listen(RemoteClient::GetDefaultPort()))
cerr << "TCP listen failed.\n"; cerr << "TCP listen failed.\n";
if (df::global::ui_sidebar_menus) if (df::global::ui_sidebar_menus)
@ -1968,6 +1973,13 @@ void Core::doUpdate(color_ostream &out, bool first_update)
strict_virtual_cast<df::viewscreen_loadgamest>(screen) || strict_virtual_cast<df::viewscreen_loadgamest>(screen) ||
strict_virtual_cast<df::viewscreen_savegamest>(screen); strict_virtual_cast<df::viewscreen_savegamest>(screen);
// save data (do this before updating last_world_data_ptr and triggering unload events)
if ((df::global::ui->main.autosave_request && !d->last_autosave_request) ||
(is_load_save && !d->was_load_save && strict_virtual_cast<df::viewscreen_savegamest>(screen)))
{
doSaveData(out);
}
// detect if the game was loaded or unloaded in the meantime // detect if the game was loaded or unloaded in the meantime
void *new_wdata = NULL; void *new_wdata = NULL;
void *new_mapdata = NULL; void *new_mapdata = NULL;
@ -1989,8 +2001,6 @@ void Core::doUpdate(color_ostream &out, bool first_update)
last_world_data_ptr = new_wdata; last_world_data_ptr = new_wdata;
last_local_map_ptr = new_mapdata; last_local_map_ptr = new_mapdata;
World::ClearPersistentCache();
// and if the world is going away, we report the map change first // and if the world is going away, we report the map change first
if(had_map) if(had_map)
onStateChange(out, SC_MAP_UNLOADED); onStateChange(out, SC_MAP_UNLOADED);
@ -2007,7 +2017,6 @@ void Core::doUpdate(color_ostream &out, bool first_update)
if (isMapLoaded() != had_map) if (isMapLoaded() != had_map)
{ {
World::ClearPersistentCache();
onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED); onStateChange(out, new_mapdata ? SC_MAP_LOADED : SC_MAP_UNLOADED);
} }
} }
@ -2027,6 +2036,9 @@ void Core::doUpdate(color_ostream &out, bool first_update)
// Execute per-frame handlers // Execute per-frame handlers
onUpdate(out); onUpdate(out);
d->last_autosave_request = df::global::ui->main.autosave_request;
d->was_load_save = is_load_save;
out << std::flush; out << std::flush;
} }
@ -2270,6 +2282,27 @@ void Core::onStateChange(color_ostream &out, state_change_event event)
Lua::Core::onStateChange(out, event); Lua::Core::onStateChange(out, event);
handleLoadAndUnloadScripts(out, event); handleLoadAndUnloadScripts(out, event);
if (event == SC_WORLD_UNLOADED)
{
Persistence::Internal::clear();
}
if (event == SC_WORLD_LOADED)
{
doLoadData(out);
}
}
void Core::doSaveData(color_ostream &out)
{
plug_mgr->doSaveData(out);
Persistence::Internal::save();
}
void Core::doLoadData(color_ostream &out)
{
Persistence::Internal::load();
plug_mgr->doLoadData(out);
} }
int Core::Shutdown ( void ) int Core::Shutdown ( void )
@ -2294,6 +2327,8 @@ int Core::Shutdown ( void )
HotkeyCond.notify_one(); HotkeyCond.notify_one();
} }
ServerMain::block();
d->hotkeythread.join(); d->hotkeythread.join();
d->iothread.join(); d->iothread.join();

@ -197,6 +197,9 @@ Plugin::Plugin(Core * core, const std::string & path,
plugin_rpcconnect = 0; plugin_rpcconnect = 0;
plugin_enable = 0; plugin_enable = 0;
plugin_is_enabled = 0; plugin_is_enabled = 0;
plugin_save_data = 0;
plugin_load_data = 0;
plugin_eval_ruby = 0;
state = PS_UNLOADED; state = PS_UNLOADED;
access = new RefLock(); access = new RefLock();
} }
@ -348,6 +351,8 @@ bool Plugin::load(color_ostream &con)
plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect"); plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect");
plugin_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable"); plugin_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable");
plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled"); plugin_is_enabled = (bool*) LookupPlugin(plug, "plugin_is_enabled");
plugin_save_data = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_save_data");
plugin_load_data = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_load_data");
plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby"); plugin_eval_ruby = (command_result (*)(color_ostream &, const char*)) LookupPlugin(plug, "plugin_eval_ruby");
index_lua(plug); index_lua(plug);
plugin_lib = plug; plugin_lib = plug;
@ -359,6 +364,8 @@ bool Plugin::load(color_ostream &con)
parent->registerCommands(this); parent->registerCommands(this);
if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled)
con.printerr("Plugin %s has no enabled var!\n", name.c_str()); con.printerr("Plugin %s has no enabled var!\n", name.c_str());
if (Core::getInstance().isWorldLoaded() && plugin_load_data && plugin_load_data(con) != CR_OK)
con.printerr("Plugin %s has failed to load saved data.\n", name.c_str());
fprintf(stderr, "loaded plugin %s; DFHack build %s\n", name.c_str(), plug_git_desc); fprintf(stderr, "loaded plugin %s; DFHack build %s\n", name.c_str(), plug_git_desc);
fflush(stderr); fflush(stderr);
return true; return true;
@ -403,6 +410,8 @@ bool Plugin::unload(color_ostream &con)
// enter suspend // enter suspend
CoreSuspender suspend; CoreSuspender suspend;
access->lock(); access->lock();
if (Core::getInstance().isWorldLoaded() && plugin_save_data && plugin_save_data(con) != CR_OK)
con.printerr("Plugin %s has failed to save data.\n", name.c_str());
// notify plugin about shutdown, if it has a shutdown function // notify plugin about shutdown, if it has a shutdown function
command_result cr = CR_OK; command_result cr = CR_OK;
if(plugin_shutdown) if(plugin_shutdown)
@ -410,6 +419,8 @@ bool Plugin::unload(color_ostream &con)
// cleanup... // cleanup...
plugin_is_enabled = 0; plugin_is_enabled = 0;
plugin_onupdate = 0; plugin_onupdate = 0;
plugin_save_data = 0;
plugin_load_data = 0;
reset_lua(); reset_lua();
parent->unregisterCommands(this); parent->unregisterCommands(this);
commands.clear(); commands.clear();
@ -570,6 +581,32 @@ command_result Plugin::on_state_change(color_ostream &out, state_change_event ev
return cr; return cr;
} }
command_result Plugin::save_data(color_ostream &out)
{
command_result cr = CR_NOT_IMPLEMENTED;
access->lock_add();
if(state == PS_LOADED && plugin_save_data)
{
cr = plugin_save_data(out);
Lua::Core::Reset(out, "plugin_save_data");
}
access->lock_sub();
return cr;
}
command_result Plugin::load_data(color_ostream &out)
{
command_result cr = CR_NOT_IMPLEMENTED;
access->lock_add();
if(state == PS_LOADED && plugin_load_data)
{
cr = plugin_load_data(out);
Lua::Core::Reset(out, "plugin_load_data");
}
access->lock_sub();
return cr;
}
RPCService *Plugin::rpc_connect(color_ostream &out) RPCService *Plugin::rpc_connect(color_ostream &out)
{ {
RPCService *rv = NULL; RPCService *rv = NULL;
@ -1014,6 +1051,28 @@ void PluginManager::unregisterCommands( Plugin * p )
cmdlist_mutex->unlock(); cmdlist_mutex->unlock();
} }
void PluginManager::doSaveData(color_ostream &out)
{
for (auto it = begin(); it != end(); ++it)
{
command_result cr = it->second->save_data(out);
if (cr != CR_OK && cr != CR_NOT_IMPLEMENTED)
out.printerr("Plugin %s has failed to save data.\n", it->first.c_str());
}
}
void PluginManager::doLoadData(color_ostream &out)
{
for (auto it = begin(); it != end(); ++it)
{
command_result cr = it->second->load_data(out);
if (cr != CR_OK && cr != CR_NOT_IMPLEMENTED)
out.printerr("Plugin %s has failed to load saved data.\n", it->first.c_str());
}
}
Plugin *PluginManager::operator[] (std::string name) Plugin *PluginManager::operator[] (std::string name)
{ {
MUTEX_GUARD(plugin_mutex); MUTEX_GUARD(plugin_mutex);

@ -57,12 +57,11 @@ POSSIBILITY OF SUCH DAMAGE.
#include <sstream> #include <sstream>
#include <memory> #include <memory>
#include <thread>
#include "json/json.h" #include "json/json.h"
#include "tinythread.h"
using namespace DFHack; using namespace DFHack;
using namespace tthread;
using dfproto::CoreTextNotification; using dfproto::CoreTextNotification;
using dfproto::CoreTextFragment; using dfproto::CoreTextFragment;
@ -72,6 +71,30 @@ bool readFullBuffer(CSimpleSocket *socket, void *buf, int size);
bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, bool sendRemoteMessage(CSimpleSocket *socket, int16_t id,
const ::google::protobuf::MessageLite *msg, bool size_ready); const ::google::protobuf::MessageLite *msg, bool size_ready);
std::mutex ServerMain::access_{};
bool ServerMain::blocked_{};
namespace {
struct BlockedException : std::exception {
const char* what() const noexcept override
{
return "Core has blocked all connection. This should have been catched.";
}
};
}
namespace DFHack {
struct BlockGuard {
std::lock_guard<std::mutex> lock;
BlockGuard() :
lock{ServerMain::access_}
{
if (ServerMain::blocked_)
throw BlockedException{};
}
};
}
RPCService::RPCService() RPCService::RPCService()
{ {
@ -134,9 +157,6 @@ ServerConnection::ServerConnection(CActiveSocket *socket)
core_service = new CoreService(); core_service = new CoreService();
core_service->finalize(this, &functions); core_service->finalize(this, &functions);
thread = new tthread::thread(threadFn, (void*)this);
thread->detach();
} }
ServerConnection::~ServerConnection() ServerConnection::~ServerConnection()
@ -144,7 +164,6 @@ ServerConnection::~ServerConnection()
in_error = true; in_error = true;
socket->Close(); socket->Close();
delete socket; delete socket;
delete thread;
for (auto it = plugin_services.begin(); it != plugin_services.end(); ++it) for (auto it = plugin_services.begin(); it != plugin_services.end(); ++it)
delete it->second; delete it->second;
@ -216,13 +235,14 @@ void ServerConnection::connection_ostream::flush_proxy()
} }
} }
void ServerConnection::threadFn(void *arg) void ServerConnection::Accepted(CActiveSocket* socket)
{ {
ServerConnection *me = (ServerConnection*)arg; std::thread{[](CActiveSocket* socket) {
try {
me->threadFn(); ServerConnection(socket).threadFn();
} catch (BlockedException e) {
delete me; }
}, socket}.detach();
} }
void ServerConnection::threadFn() void ServerConnection::threadFn()
@ -292,6 +312,7 @@ void ServerConnection::threadFn()
// Find and call the function // Find and call the function
int in_size = header.size; int in_size = header.size;
BlockGuard lock;
ServerFunctionBase *fn = vector_get(functions, header.id); ServerFunctionBase *fn = vector_get(functions, header.id);
MessageLite *reply = NULL; MessageLite *reply = NULL;
@ -378,25 +399,21 @@ void ServerConnection::threadFn()
std::cerr << "Shutting down client connection." << endl; std::cerr << "Shutting down client connection." << endl;
} }
ServerMain::ServerMain() namespace {
{
socket = new CPassiveSocket(); struct ServerMainImpl : public ServerMain {
thread = NULL; CPassiveSocket socket;
} static void threadFn(std::promise<bool> promise, int port);
ServerMainImpl(std::promise<bool> promise, int port);
~ServerMainImpl();
};
ServerMain::~ServerMain()
{
socket->Close();
delete socket;
delete thread;
} }
bool ServerMain::listen(int port) ServerMainImpl::ServerMainImpl(std::promise<bool> promise, int port) :
socket{}
{ {
if (thread) socket.Initialize();
return true;
socket->Initialize();
std::string filename("dfhack-config/remote-server.json"); std::string filename("dfhack-config/remote-server.json");
@ -427,29 +444,49 @@ bool ServerMain::listen(int port)
} }
std::cerr << "Listening on port " << port << (allow_remote ? " (remote enabled)" : "") << std::endl; std::cerr << "Listening on port " << port << (allow_remote ? " (remote enabled)" : "") << std::endl;
if (allow_remote) const char* addr = allow_remote ? NULL : "127.0.0.1";
{ if (!socket.Listen(addr, port)) {
if (!socket->Listen(NULL, port)) promise.set_value(false);
return false; return;
}
else
{
if (!socket->Listen("127.0.0.1", port))
return false;
} }
promise.set_value(true);
}
ServerMainImpl::~ServerMainImpl()
{
socket.Close();
}
thread = new tthread::thread(threadFn, this); std::future<bool> ServerMain::listen(int port)
thread->detach(); {
return true; std::promise<bool> promise;
auto rv = promise.get_future();
std::thread{&ServerMainImpl::threadFn, std::move(promise), port}.detach();
return rv;
} }
void ServerMain::threadFn(void *arg) void ServerMainImpl::threadFn(std::promise<bool> promise, int port)
{ {
ServerMain *me = (ServerMain*)arg; ServerMainImpl server{std::move(promise), port};
CActiveSocket *client;
while ((client = me->socket->Accept()) != NULL) CActiveSocket *client = nullptr;
{
new ServerConnection(client); try {
while ((client = server.socket.Accept()) != NULL)
{
BlockGuard lock;
ServerConnection::Accepted(client);
client = nullptr;
}
} catch(BlockedException e) {
if (client)
client->Close();
delete client;
} }
} }
void ServerMain::block()
{
std::lock_guard<std::mutex> lock{access_};
blocked_ = true;
}

@ -63,7 +63,20 @@ int main (int argc, char *argv[])
if (argc <= 1) if (argc <= 1)
{ {
fprintf(stderr, "Usage: dfhack-run <command> [args...]\n"); fprintf(stderr, "Usage: dfhack-run <command> [args...]\n\n");
fprintf(stderr, "Note: this command does not start DFHack; it is intended to connect\n"
"to a running DFHack instance. If you were trying to start DFHack, run\n"
#ifdef _WIN32
" Dwarf Fortress.exe\n"
#else
" ./dfhack\n"
#endif
"or see the documentation in hack/docs/index.html for more help.\n"
);
#ifdef _WIN32
fprintf(stderr, "\nPress Enter to quit.\n");
fgetc(stdin);
#endif
return 2; return 2;
} }

@ -222,6 +222,8 @@ namespace DFHack
void onUpdate(color_ostream &out); void onUpdate(color_ostream &out);
void onStateChange(color_ostream &out, state_change_event event); void onStateChange(color_ostream &out, state_change_event event);
void handleLoadAndUnloadScripts(color_ostream &out, state_change_event event); void handleLoadAndUnloadScripts(color_ostream &out, state_change_event event);
void doSaveData(color_ostream &out);
void doLoadData(color_ostream &out);
Core(Core const&); // Don't Implement Core(Core const&); // Don't Implement
void operator=(Core const&); // Don't implement void operator=(Core const&); // Don't implement
@ -283,7 +285,7 @@ namespace DFHack
df::viewscreen *top_viewscreen; df::viewscreen *top_viewscreen;
bool last_pause_state; bool last_pause_state;
// Very important! // Very important!
bool started; std::atomic<bool> started;
// Additional state change scripts // Additional state change scripts
std::vector<StateChangeScript> state_change_scripts; std::vector<StateChangeScript> state_change_scripts;
@ -308,7 +310,6 @@ namespace DFHack
friend class CoreSuspenderBase; friend class CoreSuspenderBase;
friend struct CoreSuspendClaimMain; friend struct CoreSuspendClaimMain;
friend struct CoreSuspendReleaseMain; friend struct CoreSuspendReleaseMain;
ServerMain *server;
}; };
class CoreSuspenderBase : protected std::unique_lock<std::recursive_mutex> { class CoreSuspenderBase : protected std::unique_lock<std::recursive_mutex> {

@ -148,6 +148,8 @@ namespace DFHack
~Plugin(); ~Plugin();
command_result on_update(color_ostream &out); command_result on_update(color_ostream &out);
command_result on_state_change(color_ostream &out, state_change_event event); command_result on_state_change(color_ostream &out, state_change_event event);
command_result save_data(color_ostream &out);
command_result load_data(color_ostream &out);
void detach_connection(RPCService *svc); void detach_connection(RPCService *svc);
public: public:
enum plugin_state enum plugin_state
@ -238,6 +240,8 @@ namespace DFHack
command_result (*plugin_enable)(color_ostream &, bool); command_result (*plugin_enable)(color_ostream &, bool);
RPCService* (*plugin_rpcconnect)(color_ostream &); RPCService* (*plugin_rpcconnect)(color_ostream &);
command_result (*plugin_eval_ruby)(color_ostream &, const char*); command_result (*plugin_eval_ruby)(color_ostream &, const char*);
command_result (*plugin_save_data)(color_ostream &);
command_result (*plugin_load_data)(color_ostream &);
}; };
class DFHACK_EXPORT PluginManager class DFHACK_EXPORT PluginManager
{ {
@ -251,6 +255,8 @@ namespace DFHack
void OnStateChange(color_ostream &out, state_change_event event); void OnStateChange(color_ostream &out, state_change_event event);
void registerCommands( Plugin * p ); void registerCommands( Plugin * p );
void unregisterCommands( Plugin * p ); void unregisterCommands( Plugin * p );
void doSaveData(color_ostream &out);
void doLoadData(color_ostream &out);
// PUBLIC METHODS // PUBLIC METHODS
public: public:
// list names of all plugins present in hack/plugins // list names of all plugins present in hack/plugins

@ -28,6 +28,8 @@ distribution.
#include "RemoteClient.h" #include "RemoteClient.h"
#include "Core.h" #include "Core.h"
#include <future>
class CPassiveSocket; class CPassiveSocket;
class CActiveSocket; class CActiveSocket;
class CSimpleSocket; class CSimpleSocket;
@ -233,26 +235,25 @@ namespace DFHack
CoreService *core_service; CoreService *core_service;
std::map<std::string, RPCService*> plugin_services; std::map<std::string, RPCService*> plugin_services;
tthread::thread *thread;
static void threadFn(void *);
void threadFn(); void threadFn();
ServerConnection(CActiveSocket* socket);
~ServerConnection();
public: public:
ServerConnection(CActiveSocket *socket);
~ServerConnection(); static void Accepted(CActiveSocket* socket);
ServerFunctionBase *findFunction(color_ostream &out, const std::string &plugin, const std::string &name); ServerFunctionBase *findFunction(color_ostream &out, const std::string &plugin, const std::string &name);
}; };
class ServerMain { class ServerMain {
CPassiveSocket *socket; static std::mutex access_;
static bool blocked_;
friend struct BlockGuard;
tthread::thread *thread;
static void threadFn(void *);
public: public:
ServerMain();
~ServerMain();
bool listen(int port); static std::future<bool> listen(int port);
static void block();
}; };
} }

@ -36,6 +36,7 @@ distribution.
#include "BitArray.h" #include "BitArray.h"
#include "modules/Materials.h" #include "modules/Materials.h"
#include "df/biome_type.h"
#include "df/block_flags.h" #include "df/block_flags.h"
#include "df/feature_type.h" #include "df/feature_type.h"
#include "df/flow_type.h" #include "df/flow_type.h"
@ -333,6 +334,12 @@ extern DFHACK_EXPORT bool RemoveBlockEvent(uint32_t x, uint32_t y, uint32_t z, d
DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canWalkBetween(df::coord pos1, df::coord pos2);
DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2); DFHACK_EXPORT bool canStepBetween(df::coord pos1, df::coord pos2);
DFHACK_EXPORT df::enums::biome_type::biome_type GetBiomeType(int world_coord_x, int world_coord_y);
DFHACK_EXPORT df::enums::biome_type::biome_type GetBiomeTypeWithRef(int world_coord_x, int world_coord_y, int world_ref_y_coord);
} }
} }
#endif #endif

@ -0,0 +1,232 @@
/*
https://github.com/DFHack/dfhack
Copyright (c) 2009-2018 DFHack Team
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
#ifndef CL_MOD_PERSISTENCE
#define CL_MOD_PERSISTENCE
/**
* \defgroup grp_persistence Persistence: code related to saving and loading data
* @ingroup grp_modules
*/
#include <fstream>
#include <memory>
#include <string>
#include <vector>
#include "Export.h"
#include "Error.h"
namespace DFHack
{
class Core;
namespace Persistence
{
struct LegacyData;
class Internal;
}
class DFHACK_EXPORT PersistentDataItem {
size_t index;
std::shared_ptr<Persistence::LegacyData> data;
public:
static const int NumInts = 7;
bool isValid() const;
size_t get_index() const
{
CHECK_INVALID_ARGUMENT(isValid());
return index;
}
int entry_id() const { return isValid() ? int(index) + 100 : 0; }
int raw_id() const { return isValid() ? -int(index) - 100 : 0; }
const std::string &key() const;
std::string &val();
const std::string &val() const;
int &ival(int i);
int ival(int i) const;
// Pack binary data into string field.
// Since DF serialization chokes on NUL bytes,
// use bit magic to ensure none of the bytes is 0.
// Choose the lowest bit for padding so that
// sign-extend can be used normally.
size_t data_size() const
{
return val().size();
}
bool check_data(size_t off, size_t sz = 1) const
{
return (data_size() >= off + sz);
}
void ensure_data(size_t off, size_t sz = 0)
{
if (data_size() < off + sz)
{
val().resize(off + sz, '\x01');
}
}
template<size_t N>
uint8_t (&pdata(size_t off))[N]
{
ensure_data(off, N);
typedef uint8_t array[N];
return *(array *)(val().data() + off);
}
template<size_t N>
const uint8_t (&pdata(size_t off) const)[N]
{
CHECK_INVALID_ARGUMENT(check_data(off, N));
typedef const uint8_t array[N];
return *(array *)(val().data() + off);
}
template<size_t N>
const uint8_t (&cpdata(size_t off) const)[N]
{
return pdata<N>(off);
}
static const size_t int7_size = 1;
uint8_t get_uint7(size_t off) const
{
auto p = pdata<int7_size>(off);
return p[0] >> 1;
}
int8_t get_int7(size_t off) const
{
return int8_t(get_uint7(off));
}
void set_uint7(size_t off, uint8_t val)
{
auto p = pdata<int7_size>(off);
p[0] = uint8_t((val << 1) | 1);
}
void set_int7(size_t off, int8_t val)
{
set_uint7(off, uint8_t(val));
}
static const size_t int28_size = 4;
uint32_t get_uint28(size_t off) const
{
auto p = pdata<int28_size>(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((p[3]&~1U)<<20);
}
int32_t get_int28(size_t off) const
{
return int32_t(get_uint28(off));
}
void set_uint28(size_t off, uint32_t val)
{
auto p = pdata<int28_size>(off);
p[0] = uint8_t((val<<1) | 1);
p[1] = uint8_t((val>>6) | 1);
p[2] = uint8_t((val>>13) | 1);
p[3] = uint8_t((val>>20) | 1);
}
void set_int28(size_t off, int32_t val)
{
set_uint28(off, val);
}
PersistentDataItem() : index(0), data(nullptr) {}
PersistentDataItem(size_t index, const std::shared_ptr<Persistence::LegacyData> &data)
: index(index), data(data) {}
};
namespace Persistence
{
class Internal
{
static void clear();
static void save();
static void load();
friend class ::DFHack::Core;
};
// Returns a new PersistentDataItem with the specified key.
// If there is no world loaded or the key is empty, returns an invalid item.
DFHACK_EXPORT PersistentDataItem addItem(const std::string &key);
// Returns an existing PersistentDataItem with the specified key.
// If "added" is not null and there is no such item, a new item is returned and
// the bool value is set to true. If "added" is not null and an item is found or
// no new item can be created, the bool value is set to false.
DFHACK_EXPORT PersistentDataItem getByKey(const std::string &key, bool *added = nullptr);
// Returns an existing PersistentDataItem with the specified index.
// If there is no world loaded or the index is empty, returns an invalid item.
DFHACK_EXPORT PersistentDataItem getByIndex(size_t index);
// If the item is invalid, returns false. Otherwise, deletes the item and returns
// true. All references to the item are invalid as soon as this function returns.
DFHACK_EXPORT bool deleteItem(const PersistentDataItem &item);
// Fills the vector with references to each persistent item.
DFHACK_EXPORT void getAll(std::vector<PersistentDataItem> &vec);
// Fills the vector with references to each persistent item with a key that is
// greater than or equal to "min" and less than "max".
DFHACK_EXPORT void getAllByKeyRange(std::vector<PersistentDataItem> &vec, const std::string &min, const std::string &max);
// Fills the vector with references to each persistent item with a key that is
// equal to the given key.
DFHACK_EXPORT void getAllByKey(std::vector<PersistentDataItem> &vec, const std::string &key);
#if defined(__GNUC__) && __GNUC__ < 5
// file stream move constructors are missing in libstdc++ before version 5.
template<typename T>
class DFHACK_EXPORT gcc_4_fstream_shim : public T
{
const std::string file;
public:
explicit gcc_4_fstream_shim() : T(), file() {}
explicit gcc_4_fstream_shim(const std::string &file) : T(file), file() {}
gcc_4_fstream_shim(gcc_4_fstream_shim<T> && s) : T(), file(s.file)
{
if (!file.empty())
{
T::open(file.c_str());
}
}
};
#define FSTREAM(x) gcc_4_fstream_shim<x>
#else
#define FSTREAM(x) x
#endif
// Returns an input stream that data can be read from. If no world is currently loaded,
// or no data has been saved with the specified key, the stream is invalid and acts
// as if the file is empty.
DFHACK_EXPORT FSTREAM(std::ifstream) readSaveData(const std::string &key);
// Returns an output stream that data can be saved to. If no world is currently loaded,
// an invalid stream is returned, and writing to it has no effect.
DFHACK_EXPORT FSTREAM(std::ofstream) writeSaveData(const std::string &key);
#undef FSTREAM
}
}
#endif

@ -33,6 +33,7 @@ distribution.
#include "Export.h" #include "Export.h"
#include "Module.h" #include "Module.h"
#include "modules/Persistence.h"
#include <ostream> #include <ostream>
#include "DataDefs.h" #include "DataDefs.h"
@ -60,81 +61,6 @@ namespace DFHack
}; };
class DFContextShared; class DFContextShared;
class DFHACK_EXPORT PersistentDataItem {
int id;
std::string key_value;
std::string *str_value;
int *int_values;
public:
static const int NumInts = 7;
bool isValid() const { return id != 0; }
int entry_id() const { return -id; }
int raw_id() const { return id; }
const std::string &key() const { return key_value; }
std::string &val() { return *str_value; }
const std::string &val() const { return *str_value; }
int &ival(int i) { return int_values[i]; }
int ival(int i) const { return int_values[i]; }
// Pack binary data into string field.
// Since DF serialization chokes on NUL bytes,
// use bit magic to ensure none of the bytes is 0.
// Choose the lowest bit for padding so that
// sign-extend can be used normally.
size_t data_size() const { return str_value->size(); }
bool check_data(size_t off, size_t sz = 1) {
return (str_value->size() >= off+sz);
}
void ensure_data(size_t off, size_t sz = 0) {
if (str_value->size() < off+sz) str_value->resize(off+sz, '\x01');
}
uint8_t *pdata(size_t off) { return (uint8_t*)&(*str_value)[off]; }
static const size_t int7_size = 1;
uint8_t get_uint7(size_t off) {
uint8_t *p = pdata(off);
return p[0]>>1;
}
int8_t get_int7(size_t off) {
uint8_t *p = pdata(off);
return int8_t(p[0])>>1;
}
void set_uint7(size_t off, uint8_t val) {
uint8_t *p = pdata(off);
p[0] = uint8_t((val<<1) | 1);
}
void set_int7(size_t off, int8_t val) { set_uint7(off, val); }
static const size_t int28_size = 4;
uint32_t get_uint28(size_t off) {
uint8_t *p = pdata(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((p[3]&~1U)<<20);
}
int32_t get_int28(size_t off) {
uint8_t *p = pdata(off);
return (p[0]>>1) | ((p[1]&~1U)<<6) | ((p[2]&~1U)<<13) | ((int8_t(p[3])&~1)<<20);
}
void set_uint28(size_t off, uint32_t val) {
uint8_t *p = pdata(off);
p[0] = uint8_t((val<<1) | 1);
p[1] = uint8_t((val>>6) | 1);
p[2] = uint8_t((val>>13) | 1);
p[3] = uint8_t((val>>20) | 1);
}
void set_int28(size_t off, int32_t val) { set_uint28(off, val); }
PersistentDataItem() : id(0), str_value(0), int_values(0) {}
PersistentDataItem(int id, const std::string &key, std::string *sv, int *iv)
: id(id), key_value(key), str_value(sv), int_values(iv) {}
};
/** /**
* The World module * The World module
* \ingroup grp_modules * \ingroup grp_modules
@ -179,8 +105,6 @@ namespace DFHack
// Deletes the item; returns true if success. // Deletes the item; returns true if success.
DFHACK_EXPORT bool DeletePersistentData(const PersistentDataItem &item); DFHACK_EXPORT bool DeletePersistentData(const PersistentDataItem &item);
DFHACK_EXPORT void ClearPersistentCache();
DFHACK_EXPORT df::tile_bitmask *getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create = false); DFHACK_EXPORT df::tile_bitmask *getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create = false);
DFHACK_EXPORT bool deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block); DFHACK_EXPORT bool deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block);
} }

@ -226,7 +226,7 @@ MOVEMENT_KEYS = {
CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 }, CURSOR_UP_Z_AUX = { 0, 0, 1 }, CURSOR_DOWN_Z_AUX = { 0, 0, -1 },
} }
local function get_movement_delta(key, delta, big_step) function get_movement_delta(key, delta, big_step)
local info = MOVEMENT_KEYS[key] local info = MOVEMENT_KEYS[key]
if info then if info then
if info[4] then if info[4] then
@ -243,7 +243,7 @@ for i,v in ipairs(df.global.ui.main.hotkeys) do
HOTKEY_KEYS['D_HOTKEY'..(i+1)] = v HOTKEY_KEYS['D_HOTKEY'..(i+1)] = v
end end
local function get_hotkey_target(key) function get_hotkey_target(key)
local hk = HOTKEY_KEYS[key] local hk = HOTKEY_KEYS[key]
if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then
return xyz2pos(hk.x, hk.y, hk.z) return xyz2pos(hk.x, hk.y, hk.z)

@ -46,6 +46,7 @@ using namespace std;
#include "modules/MapCache.h" #include "modules/MapCache.h"
#include "modules/Maps.h" #include "modules/Maps.h"
#include "df/biome_type.h"
#include "df/block_burrow.h" #include "df/block_burrow.h"
#include "df/block_burrow_link.h" #include "df/block_burrow_link.h"
#include "df/block_square_event_grassst.h" #include "df/block_square_event_grassst.h"
@ -711,3 +712,393 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2)
return false; return false;
} }
/* The code below is a heavily refactored version of code found at
https://github.com/ragundo/exportmaps/blob/master/cpp/df_utils/biome_type.cpp.
*/
/*
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.
*/
namespace {
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality(df::region_map_entry& region,
int y_pos
)
{
int flip_latitude = df::global::world->world_data->flip_latitude;
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
if (flip_latitude == -1) // NO POLES
{
// If there're no poles, tropical area is determined by temperature
is_possible_tropical_area_by_latitude = region.temperature >= 75;
is_tropical_area_by_latitude = region.temperature >= 85;
}
else
{
int v6 = 0;
df::world_data* wdata = df::global::world->world_data;
if (flip_latitude == 0) // NORTH POLE ONLY
{
v6 = y_pos;
}
else if (flip_latitude == 1) // SOUTH_POLE ONLY
{
v6 = df::global::world->world_data->world_height - y_pos - 1;
}
else if (flip_latitude == 2) // BOTH POLES
{
if (y_pos < wdata->world_height / 2)
v6 = 2 * y_pos;
else
{
v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1;
if (v6 < 0)
v6 = 0;
if (v6 >= wdata->world_height)
v6 = wdata->world_height - 1;
}
}
if (wdata->world_height == 17)
v6 *= 16;
else if (wdata->world_height == 33)
v6 *= 8;
else if (wdata->world_height == 65)
v6 *= 4;
else if (wdata->world_height == 129)
v6 *= 2;
is_possible_tropical_area_by_latitude = v6 > 170;
is_tropical_area_by_latitude = v6 >= 200;
}
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
// return some unknow parameter as a percentage
//----------------------------------------------------------------------------//
int get_region_parameter(int y,
int x
)
{
int world_height = df::global::world->world_data->world_height;
if (world_height > 65) // Medium and large worlds
{
// access to region 2D array
df::region_map_entry& region = df::global::world->world_data->region_map[x][y];
int flip_latitude = df::global::world->world_data->flip_latitude;
int rainfall = region.rainfall;
int result;
int y_pos = y;
int ypos = y_pos;
if (flip_latitude == -1) // NO POLES
return 100;
else if (flip_latitude == 1) // SOUTH POLE
ypos = world_height - y_pos - 1;
else if (flip_latitude == 2) // NORTH & SOUTH POLE
{
if (ypos < world_height / 2)
ypos *= 2;
else
{
ypos = world_height + 2 * (world_height / 2 - ypos) - 1;
if (ypos < 0)
ypos = 0;
if (ypos >= world_height)
ypos = world_height - 1;
}
}
int latitude; // 0 - 256 (size of a large world)
switch (world_height)
{
case 17: // Pocket world
latitude = 16 * ypos;
break;
case 33: // Smaller world
latitude = 8 * ypos;
break;
case 65: // Small world
latitude = 4 * ypos;
break;
case 129: // Medium world
latitude = 2 * ypos;
break;
default: // Large world
latitude = ypos;
break;
}
// latitude > 220
if ((latitude - 171) > 49)
return 100;
// Latitude between 191 and 200
if ((latitude > 190) && (latitude < 201))
return 0;
// Latitude between 201 and 220
if ((latitude > 190) && (latitude >= 201))
result = rainfall + 16 * (latitude - 207);
else
// Latitude between 0 and 190
result = (16 * (184 - latitude) - rainfall);
if (result < 0)
return 0;
if (result > 100)
return 100;
return result;
}
return 100;
}
}
/*****************************************************************************
Module main function.
Return the biome type, given a position coordinate expressed in world_tiles
The world ref coordinates are used for tropicality determination and may refer
to a tile neighboring the "official" one.
*****************************************************************************/
df::enums::biome_type::biome_type Maps::GetBiomeTypeWithRef(int world_coord_x,
int world_coord_y,
int world_ref_coord_y
)
{
// Biome is per region, so get the region where this biome exists
df::region_map_entry& region = df::global::world->world_data->region_map[world_coord_x][world_coord_y];
// Check if the y reference position coordinate belongs to a tropical area
std::pair<bool, bool> p = check_tropicality(region,
world_ref_coord_y
);
bool is_possible_tropical_area_by_latitude = p.first;
bool is_tropical_area_by_latitude = p.second;
int parameter = get_region_parameter(world_coord_y, world_coord_x);
// Begin the discrimination
if (region.flags.is_set(df::region_map_entry_flags::is_lake)) // is it a lake?
{
// salinity values tell us the lake type
// greater than 66 is a salt water lake
// between 33 and 65 is a brackish water lake
// less than 33 is a fresh water lake
if (region.salinity < 33)
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_FRESHWATER; // 39
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_FRESHWATER; // 36
else if (region.salinity < 66)
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_BRACKISHWATER; // 40
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_BRACKISHWATER; // 37
else // salinity >= 66
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_SALTWATER;// 41
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_SALTWATER; // 38
}
// Not a lake. Check elevation
// Elevation greater then 149 means a mountain biome
// Elevation below 100 means a ocean biome
// Elevation between 100 and 149 are land biomes
if (region.elevation >= 150) // is it a mountain?
return df::enums::biome_type::biome_type::MOUNTAIN; // 0
if (region.elevation < 100) // is it a ocean?
{
if (is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::OCEAN_TROPICAL; // 27
else if (region.temperature <= -5)
return df::enums::biome_type::biome_type::OCEAN_ARCTIC; // 29
else
return df::enums::biome_type::biome_type::OCEAN_TEMPERATE; // 28
}
// land biome. Elevation between 100 and 149
if (region.temperature <= -5)
{
if (region.drainage < 75)
return df::enums::biome_type::biome_type::TUNDRA; // 2
else
return df::enums::biome_type::biome_type::GLACIER; // 1
}
// Not a lake, mountain, ocean, glacier or tundra
// Vegetation determines the biome type
if (region.vegetation < 10)
{
if (region.drainage < 33)
return df::enums::biome_type::biome_type::DESERT_SAND; // 26
else if (region.drainage < 66)
return df::enums::biome_type::biome_type::DESERT_ROCK; // 25
else // drainage >= 66
return df::enums::biome_type::biome_type::DESERT_BADLAND; // 24
}
else if (region.vegetation < 20)
{
if ((is_possible_tropical_area_by_latitude && (parameter < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::GRASSLAND_TROPICAL; // 21
else
return df::enums::biome_type::biome_type::GRASSLAND_TEMPERATE; //18;
}
else if (region.vegetation < 33)
{
// vegetation between 20 and 32
if ((is_possible_tropical_area_by_latitude && (parameter <= 6)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::SAVANNA_TROPICAL; // 22
else
return df::enums::biome_type::biome_type::SAVANNA_TEMPERATE; //19;
}
else if (region.vegetation < 66)
{
if (region.drainage >= 33)
{
if (is_possible_tropical_area_by_latitude && (parameter < 66 || is_tropical_area_by_latitude))
return df::enums::biome_type::biome_type::SHRUBLAND_TROPICAL; // 23
else
return df::enums::biome_type::biome_type::SHRUBLAND_TEMPERATE; // 20
}
// drainage < 33
{
if (region.salinity < 66)
{
if ((is_possible_tropical_area_by_latitude && (parameter < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::MARSH_TROPICAL_FRESHWATER; // 10
else
return df::enums::biome_type::biome_type::MARSH_TEMPERATE_FRESHWATER; // 5
}
else // drainage < 33, salinity >= 66
{
if ((is_possible_tropical_area_by_latitude && (parameter < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::MARSH_TROPICAL_SALTWATER; // 11
else
return df::enums::biome_type::biome_type::MARSH_TEMPERATE_SALTWATER; // 6
}
}
}
// Not a lake, mountain, ocean, glacier, tundra, desert, grassland or savanna
// vegetation >= 66
else if (region.drainage >= 33)
{
// drainage >= 33, not tropical area
if (!is_possible_tropical_area_by_latitude)
{
if ((region.rainfall < 75) || (region.temperature < 65))
{
if (region.temperature >= 10)
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13
else
return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12
}
else
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14
}
else // drainage >= 33, tropical area
{
if (((parameter < 66) || is_tropical_area_by_latitude) && (region.rainfall < 75))
return df::enums::biome_type::biome_type::FOREST_TROPICAL_CONIFER; // 15
if (parameter < 66)
return df::enums::biome_type::biome_type::FOREST_TROPICAL_DRY_BROADLEAF; // 16
if (is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF; // 17
else
{
if ((region.rainfall < 75) || (region.temperature < 65))
{
if (region.temperature >= 10)
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13
else
return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12
}
else
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14
}
}
}
// Not a lake, mountain, ocean, glacier, tundra, desert, grassland, savanna or forest
// vegetation >= 66, drainage < 33
else if (is_possible_tropical_area_by_latitude)
{
if (region.salinity < 66)
{
if ((parameter < 66) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::SWAMP_TROPICAL_FRESHWATER; // 7
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER;// 3
}
else // elevation between 100 and 149, vegetation >= 66, drainage < 33, salinity >= 66
{
if ((parameter < 66) || is_tropical_area_by_latitude)
if (region.drainage < 10)
return df::enums::biome_type::biome_type::SWAMP_MANGROVE; //9
else // drainage >= 10
return df::enums::biome_type::biome_type::SWAMP_TROPICAL_SALTWATER; // 8
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4
}
}
else if (region.salinity >= 66)
// elevation between 100 and 149, vegetation >= 66, drainage < 33, not tropical area
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER; // 3
}
/*****************************************************************************
Module main function.
Return the biome type, given a position coordinate expressed in world_tiles
*****************************************************************************/
df::enums::biome_type::biome_type Maps::GetBiomeType(int world_coord_x, int world_coord_y)
{
return Maps::GetBiomeTypeWithRef(world_coord_x, world_coord_y, world_coord_y);
}

@ -0,0 +1,420 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 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 <array>
#include <map>
#include <json/json.h>
#include "Core.h"
#include "DataDefs.h"
#include "modules/Persistence.h"
#include "modules/World.h"
#include "df/historical_figure.h"
using namespace DFHack;
static std::vector<std::shared_ptr<Persistence::LegacyData>> legacy_data;
static std::multimap<std::string, size_t> index_cache;
struct Persistence::LegacyData
{
const std::string key;
std::string str_value;
std::array<int, PersistentDataItem::NumInts> int_values;
explicit LegacyData(const std::string &key) : key(key)
{
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
int_values.at(i) = -1;
}
}
explicit LegacyData(Json::Value &json) : key(json["k"].asString())
{
str_value = json["s"].asString();
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
int_values.at(i) = json["i"][i].asInt();
}
}
explicit LegacyData(const df::language_name &name) : key(name.first_name)
{
str_value = name.nickname;
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
int_values.at(i) = name.words[i];
}
}
Json::Value toJSON()
{
Json::Value json(Json::objectValue);
json["k"] = key;
json["s"] = str_value;
Json::Value ints(Json::arrayValue);
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
ints[i] = int_values.at(i);
}
json["i"] = std::move(ints);
return std::move(json);
}
};
const std::string &PersistentDataItem::key() const
{
CHECK_INVALID_ARGUMENT(isValid());
return data->key;
}
std::string &PersistentDataItem::val()
{
CHECK_INVALID_ARGUMENT(isValid());
return data->str_value;
}
const std::string &PersistentDataItem::val() const
{
CHECK_INVALID_ARGUMENT(isValid());
return data->str_value;
}
int &PersistentDataItem::ival(int i)
{
CHECK_INVALID_ARGUMENT(isValid());
CHECK_INVALID_ARGUMENT(i >= 0 && i < NumInts);
return data->int_values.at(i);
}
int PersistentDataItem::ival(int i) const
{
CHECK_INVALID_ARGUMENT(isValid());
CHECK_INVALID_ARGUMENT(i >= 0 && i < NumInts);
return data->int_values.at(i);
}
bool PersistentDataItem::isValid() const
{
if (data == nullptr)
return false;
CoreSuspender suspend;
if (legacy_data.size() <= index)
return false;
return legacy_data.at(index) == data;
}
void Persistence::Internal::clear()
{
CoreSuspender suspend;
legacy_data.clear();
index_cache.clear();
}
void Persistence::Internal::save()
{
CoreSuspender suspend;
Json::Value json(Json::arrayValue);
for (size_t i = 0; i < legacy_data.size(); i++)
{
if (legacy_data.at(i) != nullptr)
{
while (json.size() < i)
{
json[json.size()] = Json::Value();
}
json[int(i)] = legacy_data.at(i)->toJSON();
}
}
auto file = writeSaveData("legacy-data");
file << json;
}
static void convertHFigs()
{
auto &figs = df::historical_figure::get_vector();
auto src = figs.begin();
while (src != figs.end() && (*src)->id > -100)
{
++src;
}
if (src == figs.end())
{
return;
}
auto dst = src;
while (src != figs.end())
{
auto fig = *src;
if (fig->id > -100)
{
*dst = *src;
++dst;
}
else
{
if (fig->name.has_name && !fig->name.first_name.empty())
{
size_t index = size_t(-fig->id - 100);
if (legacy_data.size() <= index)
{
legacy_data.resize(index + 1);
}
legacy_data.at(index) = std::shared_ptr<Persistence::LegacyData>(new Persistence::LegacyData(fig->name));
}
delete fig;
}
++src;
}
figs.erase(dst, figs.end());
}
void Persistence::Internal::load()
{
CoreSuspender suspend;
clear();
auto file = readSaveData("legacy-data");
Json::Value json;
try
{
file >> json;
}
catch (std::exception &)
{
// empty file?
}
if (json.isArray())
{
legacy_data.resize(json.size());
for (size_t i = 0; i < legacy_data.size(); i++)
{
if (json[int(i)].isObject())
{
legacy_data.at(i) = std::shared_ptr<LegacyData>(new LegacyData(json[int(i)]));
}
}
}
convertHFigs();
for (size_t i = 0; i < legacy_data.size(); i++)
{
if (legacy_data.at(i) == nullptr)
{
continue;
}
index_cache.insert(std::make_pair(legacy_data.at(i)->key, i));
}
}
PersistentDataItem Persistence::addItem(const std::string &key)
{
if (key.empty() || !Core::getInstance().isWorldLoaded())
return PersistentDataItem();
CoreSuspender suspend;
size_t index = 0;
while (index < legacy_data.size() && legacy_data.at(index) != nullptr)
{
index++;
}
auto ptr = std::shared_ptr<LegacyData>(new LegacyData(key));
if (index == legacy_data.size())
{
legacy_data.push_back(ptr);
}
else
{
legacy_data.at(index) = ptr;
}
index_cache.insert(std::make_pair(key, index));
return PersistentDataItem(index, ptr);
}
PersistentDataItem Persistence::getByKey(const std::string &key, bool *added)
{
CoreSuspender suspend;
auto it = index_cache.find(key);
if (added)
{
*added = it == index_cache.end();
}
if (it != index_cache.end())
{
return PersistentDataItem(it->second, legacy_data.at(it->second));
}
if (!added)
{
return PersistentDataItem();
}
return addItem(key);
}
PersistentDataItem Persistence::getByIndex(size_t index)
{
CoreSuspender suspend;
if (index < legacy_data.size() && legacy_data.at(index) != nullptr)
{
return PersistentDataItem(index, legacy_data.at(index));
}
return PersistentDataItem();
}
bool Persistence::deleteItem(const PersistentDataItem &item)
{
CoreSuspender suspend;
if (!item.isValid())
{
return false;
}
size_t index = item.get_index();
auto range = index_cache.equal_range(item.key());
for (auto it = range.first; it != range.second; ++it)
{
if (it->second == index)
{
index_cache.erase(it);
break;
}
}
legacy_data.at(index) = nullptr;
return true;
}
void Persistence::getAll(std::vector<PersistentDataItem> &vec)
{
vec.clear();
CoreSuspender suspend;
for (size_t i = 0; i < legacy_data.size(); i++)
{
if (legacy_data.at(i) != nullptr)
{
vec.push_back(PersistentDataItem(i, legacy_data.at(i)));
}
}
}
void Persistence::getAllByKeyRange(std::vector<PersistentDataItem> &vec, const std::string &min, const std::string &max)
{
vec.clear();
CoreSuspender suspend;
auto begin = index_cache.lower_bound(min);
auto end = index_cache.lower_bound(max);
for (auto it = begin; it != end; ++it)
{
vec.push_back(PersistentDataItem(it->second, legacy_data.at(it->second)));
}
}
void Persistence::getAllByKey(std::vector<PersistentDataItem> &vec, const std::string &key)
{
vec.clear();
CoreSuspender suspend;
auto range = index_cache.equal_range(key);
for (auto it = range.first; it != range.second; ++it)
{
vec.push_back(PersistentDataItem(it->second, legacy_data.at(it->second)));
}
}
static std::string filterSaveFileName(std::string s)
{
for (auto &ch : s)
{
if (!isalnum(ch) && ch != '-' && ch != '_')
{
ch = '_';
}
}
return s;
}
static std::string getSaveFilePath(const std::string &world, const std::string &name)
{
return "data/save/" + world + "/dfhack-" + filterSaveFileName(name) + ".dat";
}
#if defined(__GNUC__) && __GNUC__ < 5
// file stream move constructors are missing in libstdc++ before version 5.
#define FSTREAM(x) Persistence::gcc_4_fstream_shim<x>
#else
#define FSTREAM(x) x
#endif
FSTREAM(std::ifstream) Persistence::readSaveData(const std::string &name)
{
if (!Core::getInstance().isWorldLoaded())
{
// No world loaded - return unopened stream.
return FSTREAM(std::ifstream)();
}
return FSTREAM(std::ifstream)(getSaveFilePath(World::ReadWorldFolder(), name));
}
FSTREAM(std::ofstream) Persistence::writeSaveData(const std::string &name)
{
if (!Core::getInstance().isWorldLoaded())
{
// No world loaded - return unopened stream.
return FSTREAM(std::ofstream)();
}
return FSTREAM(std::ofstream)(getSaveFilePath("current", name));
}
#undef FSTREAM

@ -24,6 +24,7 @@ distribution.
#include "Internal.h" #include "Internal.h"
#include <array>
#include <string> #include <string>
#include <vector> #include <vector>
#include <map> #include <map>
@ -56,10 +57,6 @@ using namespace df::enums;
using df::global::world; using df::global::world;
static int next_persistent_id = 0;
static std::multimap<std::string, int> persistent_index;
typedef std::pair<std::string, int> T_persistent_item;
bool World::ReadPauseState() bool World::ReadPauseState()
{ {
return DF_GLOBAL_VALUE(pause_state, false); return DF_GLOBAL_VALUE(pause_state, false);
@ -183,117 +180,14 @@ bool World::isLegends(df::game_type t)
return (t == game_type::VIEW_LEGENDS); return (t == game_type::VIEW_LEGENDS);
} }
static PersistentDataItem dataFromHFig(df::historical_figure *hfig)
{
return PersistentDataItem(hfig->id, hfig->name.first_name, &hfig->name.nickname, hfig->name.words);
}
// Hide fake histfigs from legends xml export
static bool in_export_xml = false;
struct hide_fake_histfigs_hook : df::viewscreen_legendsst {
typedef df::viewscreen_legendsst interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
if (input->count(interface_key::LEGENDS_EXPORT_XML))
{
auto &figs = df::historical_figure::get_vector();
auto it = figs.begin();
while (it != figs.end() && (*it)->id <= -100)
++it;
// Move our histfigs to a temporary vector
std::vector<df::historical_figure*> fakes(figs.begin(), it);
figs.erase(figs.begin(), it);
in_export_xml = true;
INTERPOSE_NEXT(feed)(input);
in_export_xml = false;
figs.insert(figs.begin(), fakes.begin(), fakes.end());
}
else
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE_PRIO(hide_fake_histfigs_hook, feed, -10000);
void World::ClearPersistentCache()
{
next_persistent_id = 0;
persistent_index.clear();
INTERPOSE_HOOK(hide_fake_histfigs_hook, feed).apply(Core::getInstance().isWorldLoaded());
}
static bool BuildPersistentCache()
{
if (in_export_xml)
return false;
if (next_persistent_id)
return true;
if (!Core::getInstance().isWorldLoaded())
return false;
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector();
// Determine the next entry id as min(-100, lowest_id-1)
next_persistent_id = -100;
if (hfvec.size() > 0 && hfvec[0]->id <= -100)
next_persistent_id = hfvec[0]->id-1;
// Add the entries to the lookup table
persistent_index.clear();
for (size_t i = 0; i < hfvec.size() && hfvec[i]->id <= -100; i++)
{
if (!hfvec[i]->name.has_name || hfvec[i]->name.first_name.empty())
continue;
persistent_index.insert(T_persistent_item(hfvec[i]->name.first_name, -hfvec[i]->id));
}
return true;
}
PersistentDataItem World::AddPersistentData(const std::string &key) PersistentDataItem World::AddPersistentData(const std::string &key)
{ {
if (!BuildPersistentCache() || key.empty()) return Persistence::addItem(key);
return PersistentDataItem();
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector();
df::historical_figure *hfig = new df::historical_figure();
hfig->id = next_persistent_id;
hfig->name.has_name = true;
hfig->name.first_name = key;
memset(hfig->name.words, 0xFF, sizeof(hfig->name.words));
if (!hfvec.empty())
hfig->id = std::min(hfig->id, hfvec[0]->id-1);
next_persistent_id = hfig->id-1;
hfvec.insert(hfvec.begin(), hfig);
persistent_index.insert(T_persistent_item(key, -hfig->id));
return dataFromHFig(hfig);
} }
PersistentDataItem World::GetPersistentData(const std::string &key) PersistentDataItem World::GetPersistentData(const std::string &key)
{ {
if (!BuildPersistentCache()) return Persistence::getByKey(key);
return PersistentDataItem();
auto it = persistent_index.find(key);
if (it != persistent_index.end())
return GetPersistentData(it->second);
return PersistentDataItem();
} }
PersistentDataItem World::GetPersistentData(int entry_id) PersistentDataItem World::GetPersistentData(int entry_id)
@ -301,96 +195,45 @@ PersistentDataItem World::GetPersistentData(int entry_id)
if (entry_id < 100) if (entry_id < 100)
return PersistentDataItem(); return PersistentDataItem();
auto hfig = df::historical_figure::find(-entry_id); return Persistence::getByIndex(size_t(entry_id - 100));
if (hfig && hfig->name.has_name)
return dataFromHFig(hfig);
return PersistentDataItem();
} }
PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) PersistentDataItem World::GetPersistentData(const std::string &key, bool *added)
{ {
if (added) *added = false; bool temp = false;
if (!added)
PersistentDataItem rv = GetPersistentData(key); added = &temp;
if (!rv.isValid())
{
if (added) *added = true;
rv = AddPersistentData(key);
}
return rv; return Persistence::getByKey(key, added);
} }
void World::GetPersistentData(std::vector<PersistentDataItem> *vec, const std::string &key, bool prefix) void World::GetPersistentData(std::vector<PersistentDataItem> *vec, const std::string &key, bool prefix)
{ {
vec->clear(); if (prefix && key.empty())
if (!BuildPersistentCache())
return;
auto eqrange = persistent_index.equal_range(key);
if (prefix)
{ {
if (key.empty()) Persistence::getAll(*vec);
}
else if (prefix)
{
std::string min = key;
if (min.back() != '/')
{ {
eqrange.first = persistent_index.begin(); min.push_back('/');
eqrange.second = persistent_index.end();
} }
else std::string max = min;
{ ++max.back();
std::string bound = key;
if (bound[bound.size()-1] != '/')
bound += "/";
eqrange.first = persistent_index.lower_bound(bound);
bound[bound.size()-1]++; Persistence::getAllByKeyRange(*vec, min, max);
eqrange.second = persistent_index.lower_bound(bound);
}
} }
else
for (auto it = eqrange.first; it != eqrange.second; ++it)
{ {
auto hfig = df::historical_figure::find(-it->second); Persistence::getAllByKey(*vec, key);
if (hfig && hfig->name.has_name)
vec->push_back(dataFromHFig(hfig));
} }
} }
bool World::DeletePersistentData(const PersistentDataItem &item) bool World::DeletePersistentData(const PersistentDataItem &item)
{ {
int id = item.raw_id(); return Persistence::deleteItem(item);
if (id > -100)
return false;
if (!BuildPersistentCache())
return false;
std::vector<df::historical_figure*> &hfvec = df::historical_figure::get_vector();
auto eqrange = persistent_index.equal_range(item.key());
for (auto it2 = eqrange.first; it2 != eqrange.second; )
{
auto it = it2; ++it2;
if (it->second != -id)
continue;
persistent_index.erase(it);
int idx = binsearch_index(hfvec, id);
if (idx >= 0) {
delete hfvec[idx];
hfvec.erase(hfvec.begin()+idx);
}
return true;
}
return false;
} }
df::tile_bitmask *World::getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create) df::tile_bitmask *World::getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create)

@ -1 +1 @@
Subproject commit 44215836d5b57c3722b126aaf481f652385f3a23 Subproject commit 4388fbfb8f51be41777406c6e7c518f738c195c7

@ -136,6 +136,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua)
DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread)
DFHACK_PLUGIN(manipulator manipulator.cpp) DFHACK_PLUGIN(manipulator manipulator.cpp)
DFHACK_PLUGIN(map-render map-render.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(misery misery.cpp)
DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mode mode.cpp)
DFHACK_PLUGIN(mousequery mousequery.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp)

@ -111,6 +111,7 @@ MACRO(DFHACK_PLUGIN)
SET_SOURCE_FILES_PROPERTIES( ${PLUGIN_PROTO_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE ) SET_SOURCE_FILES_PROPERTIES( ${PLUGIN_PROTO_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE )
LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_HDRS}) LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_HDRS})
LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_SRCS}) LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTO_SRCS})
LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTOS})
IF(UNIX) IF(UNIX)
SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} -include Export.h") SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} -include Export.h")

@ -1,3 +1,4 @@
#include <array>
#include <atomic> #include <atomic>
#include <vector> #include <vector>
#include <random> #include <random>
@ -20,6 +21,11 @@
#include "df/creature_raw.h" #include "df/creature_raw.h"
#include "df/world.h" #include "df/world.h"
// for MSVC alignas(64) issues
#ifdef WIN32
#define _DISABLE_EXTENDED_ALIGNED_STORAGE
#endif
using std::vector; using std::vector;
using std::string; using std::string;
using namespace DFHack; using namespace DFHack;
@ -220,7 +226,7 @@ struct ClearMem : public ConnectedBase {
}; };
struct Connected : public ClearMem<Connected> { struct Connected : public ClearMem<Connected> {
using Sig = Signal<void(int), signal_shared_tag>; using Sig = Signal<void(uint32_t), signal_shared_tag>;
std::array<Sig::Connection,4> con; std::array<Sig::Connection,4> con;
Sig signal; Sig signal;
weak other; weak other;
@ -245,7 +251,7 @@ struct Connected : public ClearMem<Connected> {
// Externally synchronized object destruction is only safe to this // Externally synchronized object destruction is only safe to this
// connect. // connect.
con[pos] = b->signal.connect( con[pos] = b->signal.connect(
[this](int) { [this](uint32_t) {
uint32_t old = callee.fetch_add(1); uint32_t old = callee.fetch_add(1);
assert(old != 0xDEDEDEDE); assert(old != 0xDEDEDEDE);
std::this_thread::sleep_for(delay); std::this_thread::sleep_for(delay);
@ -272,7 +278,7 @@ struct Connected : public ClearMem<Connected> {
if (!sig) if (!sig)
return; return;
con[pos] = sig->connect(b, con[pos] = sig->connect(b,
[this](int) { [this](uint32_t) {
uint32_t old = callee.fetch_add(1); uint32_t old = callee.fetch_add(1);
assert(old != 0xDEDEDEDE); assert(old != 0xDEDEDEDE);
std::this_thread::sleep_for(delay); std::this_thread::sleep_for(delay);
@ -284,7 +290,7 @@ struct Connected : public ClearMem<Connected> {
out = &o; out = &o;
count = c; count = c;
con[pos] = b->signal.connect(a, con[pos] = b->signal.connect(a,
[this](int) { [this](uint32_t) {
uint32_t old = callee.fetch_add(1); uint32_t old = callee.fetch_add(1);
assert(old != 0xDEDEDEDE); assert(old != 0xDEDEDEDE);
std::this_thread::sleep_for(delay); std::this_thread::sleep_for(delay);
@ -345,13 +351,13 @@ command_result sharedsignal (color_ostream &out, vector <string> & parameters)
TRACE(command, out) << "Thread " << c->id << " started." << std::endl; TRACE(command, out) << "Thread " << c->id << " started." << std::endl;
weak ref = c; weak ref = c;
for (;c->caller < c->count; ++c->caller) { for (;c->caller < c->count; ++c->caller) {
c->signal(c->caller); c->signal(std::move(c->caller));
} }
TRACE(command, out) << "Thread " << c->id << " resets shared." << std::endl; TRACE(command, out) << "Thread " << c->id << " resets shared." << std::endl;
c.reset(); c.reset();
while((c = ref.lock())) { while((c = ref.lock())) {
++c->caller; ++c->caller;
c->signal(c->caller); c->signal(std::move(c->caller));
c.reset(); c.reset();
std::this_thread::sleep_for(delay*25); std::this_thread::sleep_for(delay*25);
} }
@ -363,7 +369,7 @@ command_result sharedsignal (color_ostream &out, vector <string> & parameters)
} }
TRACE(command, out) << "running " << std::endl; TRACE(command, out) << "running " << std::endl;
for (;external->caller < external->count; ++external->caller) { for (;external->caller < external->count; ++external->caller) {
external->signal(external->caller); external->signal(std::move(external->caller));
external->reconnect(1); external->reconnect(1);
} }
TRACE(command, out) << "join " << std::endl; TRACE(command, out) << "join " << std::endl;

@ -1,7 +1,6 @@
PROJECT (embark-assistant) PROJECT (embark-assistant)
# A list of source files # A list of source files
SET(PROJECT_SRCS SET(PROJECT_SRCS
biome_type.cpp
embark-assistant.cpp embark-assistant.cpp
finder_ui.cpp finder_ui.cpp
help_ui.cpp help_ui.cpp
@ -12,7 +11,6 @@ SET(PROJECT_SRCS
) )
# A list of headers # A list of headers
SET(PROJECT_HDRS SET(PROJECT_HDRS
biome_type.h
defs.h defs.h
embark-assistant.h embark-assistant.h
finder_ui.h finder_ui.h

@ -1,754 +0,0 @@
/* The code is copied from Ragundo's repo referenced below.
The changes are:
- The addition of a .h file reference.
- The simplification of the code using ofsub to remove the use of (and
.h reference to) that function (analysis of the code showed the
simplified code is the result, as the ofsub expressions will never be
true given the range of the values it can be passed in these functions).
- The change of the main function to take a separate y coordinate for
use in the tropicality determination to allow proper determination of
the tropicality of mid level tiles ("region tiles") referencing a
neighboring world tile's biome.
*/
/*
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.
*/
// You can always find the latest version of this plugin in Github
// https://github.com/ragundo/exportmaps
#include <utility>
#include "DataDefs.h"
#include <df/region_map_entry.h>
#include <df/world.h>
#include <df/world_data.h>
#include <df/biome_type.h>
#include "biome_type.h"
/*****************************************************************************
Local functions forward declaration
*****************************************************************************/
std::pair<bool, bool> check_tropicality(df::region_map_entry& region,
int a1
);
int get_lake_biome(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude
);
int get_ocean_biome(df::region_map_entry& region,
bool is_tropical_area_by_latitude
);
int get_desert_biome(df::region_map_entry& region);
int get_biome_grassland(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_savanna(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_shrubland(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_marsh(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_forest(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_swamp(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_desert_or_grassland_or_savanna(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
int get_biome_shrubland_or_marsh(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
);
/*****************************************************************************
Module main function.
Return the biome type, given a position coordinate expressed in world_tiles
The world ref coordinates are used for tropicality determination and may refer
to a tile neighboring the "official" one.
*****************************************************************************/
int get_biome_type(int world_coord_x,
int world_coord_y,
int world_ref_coord_y
)
{
// Biome is per region, so get the region where this biome exists
df::region_map_entry& region = df::global::world->world_data->region_map[world_coord_x][world_coord_y];
// Check if the y reference position coordinate belongs to a tropical area
std::pair<bool, bool> p = check_tropicality(region,
world_ref_coord_y
);
bool is_possible_tropical_area_by_latitude = p.first;
bool is_tropical_area_by_latitude = p.second;
// Begin the discrimination
if (region.flags.is_set(df::region_map_entry_flags::is_lake)) // is it a lake?
return get_lake_biome(region,
is_possible_tropical_area_by_latitude
);
// Not a lake. Check elevation
// Elevation greater then 149 means a mountain biome
// Elevation below 100 means a ocean biome
// Elevation between 100 and 149 are land biomes
if (region.elevation >= 150) // is it a mountain?
return df::enums::biome_type::biome_type::MOUNTAIN; // 0
if (region.elevation < 100) // is it a ocean?
return get_ocean_biome(region,
is_possible_tropical_area_by_latitude
);
// land biome. Elevation between 100 and 149
if (region.temperature <= -5)
{
if (region.drainage < 75)
return df::enums::biome_type::biome_type::TUNDRA; // 2
else
return df::enums::biome_type::biome_type::GLACIER; // 1
}
// Not a lake, mountain, ocean, glacier or tundra
// Vegetation determines the biome type
if (region.vegetation < 66)
{
if (region.vegetation < 33)
return get_biome_desert_or_grassland_or_savanna(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
world_coord_y,
world_coord_x
);
else // vegetation between 33 and 65
return get_biome_shrubland_or_marsh(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
world_coord_y,
world_coord_x
);
}
// Not a lake, mountain, ocean, glacier, tundra, desert, grassland or savanna
// vegetation >= 66
if (region.drainage >= 33)
return get_biome_forest(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
world_coord_y,
world_coord_x
);
// Not a lake, mountain, ocean, glacier, tundra, desert, grassland, savanna or forest
// vegetation >= 66, drainage < 33
return get_biome_swamp(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
world_coord_y,
world_coord_x);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality_no_poles_world(df::region_map_entry& region,
int y_pos
)
{
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
// If there're no poles, tropical area is determined by temperature
if (region.temperature >= 75)
is_possible_tropical_area_by_latitude = true;
is_tropical_area_by_latitude = region.temperature >= 85;
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality_north_pole_only_world(df::region_map_entry& region,
int y_pos
)
{
int v6;
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
df::world_data* wdata = df::global::world->world_data;
// Scale the smaller worlds to the big one
if (wdata->world_height == 17)
v6 = 16 * y_pos;
else if (wdata->world_height == 33)
v6 = 8 * y_pos;
else if (wdata->world_height == 65)
v6 = 4 * y_pos;
else if (wdata->world_height == 129)
v6 = 2 * y_pos;
else
v6 = y_pos;
is_possible_tropical_area_by_latitude = v6 > 170;
is_tropical_area_by_latitude = v6 >= 200;
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality_south_pole_only_world(df::region_map_entry& region,
int y_pos
)
{
int v6 = df::global::world->world_data->world_height - y_pos - 1;
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
df::world_data* wdata = df::global::world->world_data;
if (wdata->world_height == 17)
v6 *= 16;
else if (wdata->world_height == 33)
v6 *= 8;
else if (wdata->world_height == 65)
v6 *= 4;
else if (wdata->world_height == 129)
v6 *= 2;
else
v6 *= 1;
is_possible_tropical_area_by_latitude = v6 > 170;
is_tropical_area_by_latitude = v6 >= 200;
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality_both_poles_world(df::region_map_entry& region,
int y_pos
)
{
int v6;
bool is_possible_tropical_area_by_latitude = false;
bool is_tropical_area_by_latitude = false;
df::world_data* wdata = df::global::world->world_data;
if (y_pos < wdata->world_height / 2)
v6 = 2 * y_pos;
else
{
v6 = wdata->world_height + 2 * (wdata->world_height / 2 - y_pos) - 1;
if (v6 < 0)
v6 = 0;
if (v6 >= wdata->world_height)
v6 = wdata->world_height - 1;
}
if (wdata->world_height == 17)
v6 *= 16;
else if (wdata->world_height == 33)
v6 *= 8;
else if (wdata->world_height == 65)
v6 *= 4;
else if (wdata->world_height == 129)
v6 *= 2;
else
v6 *= 1;
is_possible_tropical_area_by_latitude = v6 > 170;
is_tropical_area_by_latitude = v6 >= 200;
return std::pair<bool, bool>(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
std::pair<bool, bool> check_tropicality(df::region_map_entry& region,
int y_pos
)
{
int flip_latitude = df::global::world->world_data->flip_latitude;
if (flip_latitude == -1) // NO POLES
return check_tropicality_no_poles_world(region,
y_pos
);
else if (flip_latitude == 0) // NORTH POLE ONLY
return check_tropicality_north_pole_only_world(region,
y_pos
);
else if (flip_latitude == 1) // SOUTH_POLE ONLY
return check_tropicality_south_pole_only_world(region,
y_pos
);
else if (flip_latitude == 2) // BOTH POLES
return check_tropicality_both_poles_world(region,
y_pos
);
return std::pair<bool, bool>(false, false);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_parameter_percentage(int flip_latitude,
int y_pos,
int rainfall,
int world_height
)
{
int result;
int ypos = y_pos;
if (flip_latitude == -1) // NO POLES
return 100;
else if (flip_latitude == 1) // SOUTH POLE
ypos = world_height - y_pos - 1;
else if (flip_latitude == 2) // NORTH & SOUTH POLE
{
if (ypos < world_height / 2)
ypos *= 2;
else
{
ypos = world_height + 2 * (world_height / 2 - ypos) - 1;
if (ypos < 0)
ypos = 0;
if (ypos >= world_height)
ypos = world_height - 1;
}
}
int latitude; // 0 - 256 (size of a large world)
switch (world_height)
{
case 17: // Pocket world
latitude = 16 * ypos;
break;
case 33: // Smaller world
latitude = 8 * ypos;
break;
case 65: // Small world
latitude = 4 * ypos;
break;
case 129: // Medium world
latitude = 2 * ypos;
break;
default: // Large world
latitude = ypos;
break;
}
// latitude > 220
if ((latitude - 171) > 49)
return 100;
// Latitude between 191 and 200
if ((latitude > 190) && (latitude < 201))
return 0;
// Latitude between 201 and 220
if ((latitude > 190) && (latitude >= 201))
result = rainfall + 16 * (latitude - 207);
else
// Latitude between 0 and 190
result = (16 * (184 - latitude) - rainfall);
if (result < 0)
return 0;
if (result > 100)
return 100;
return result;
}
//----------------------------------------------------------------------------//
// Utility function
//
// return some unknow parameter as a percentage
//----------------------------------------------------------------------------//
int get_region_parameter(int y,
int x,
char a4
)
{
int result = 100;
if ((df::global::cur_season && *df::global::cur_season != 1) || !a4)
{
int world_height = df::global::world->world_data->world_height;
if (world_height > 65) // Medium and large worlds
{
// access to region 2D array
df::region_map_entry& region = df::global::world->world_data->region_map[x][y];
return get_parameter_percentage(df::global::world->world_data->flip_latitude,
y,
region.rainfall,
world_height
);
}
}
return result;
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_lake_biome(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude
)
{
// salinity values tell us the lake type
// greater than 66 is a salt water lake
// between 33 and 65 is a brackish water lake
// less than 33 is a fresh water lake
if (region.salinity < 66)
{
if (region.salinity < 33)
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_FRESHWATER; // 39
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_FRESHWATER; // 36
else // salinity >= 33
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_BRACKISHWATER; // 40
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_BRACKISHWATER; // 37
}
else // salinity >= 66
{
if (is_possible_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::LAKE_TROPICAL_SALTWATER;// 41
else
return df::enums::biome_type::biome_type::LAKE_TEMPERATE_SALTWATER; // 38
}
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_ocean_biome(df::region_map_entry& region,
bool is_tropical_area_by_latitude
)
{
if (is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::OCEAN_TROPICAL; // 27
else
if (region.temperature <= -5)
return df::enums::biome_type::biome_type::OCEAN_ARCTIC; // 29
else
return df::enums::biome_type::biome_type::OCEAN_TEMPERATE; // 28
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_desert_biome(df::region_map_entry& region)
{
if (region.drainage < 66)
{
if (region.drainage < 33)
return df::enums::biome_type::biome_type::DESERT_SAND; // 26
else // drainage between 33 and 65
return df::enums::biome_type::biome_type::DESERT_ROCK; // 25
}
// drainage >= 66
return df::enums::biome_type::biome_type::DESERT_BADLAND; // 24
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_grassland(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::GRASSLAND_TROPICAL; // 21
else
return df::enums::biome_type::biome_type::GRASSLAND_TEMPERATE; //18;
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_savanna(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) <= 6)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::SAVANNA_TROPICAL; // 22
else
return df::enums::biome_type::biome_type::SAVANNA_TEMPERATE; //19;
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_desert_or_grassland_or_savanna(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if (region.vegetation < 20)
{
if (region.vegetation < 10)
return get_desert_biome(region);
else // vegetation between 10 and 19
return get_biome_grassland(is_possible_tropical_area_by_latitude, is_tropical_area_by_latitude, y, x);
}
// vegetation between 20 and 32
return get_biome_savanna(is_possible_tropical_area_by_latitude, is_tropical_area_by_latitude, y, x);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_shrubland(bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if (is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66 || is_tropical_area_by_latitude))
return df::enums::biome_type::biome_type::SHRUBLAND_TROPICAL; // 23
else
return df::enums::biome_type::biome_type::SHRUBLAND_TEMPERATE; // 20
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_marsh(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if (region.salinity < 66)
{
if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::MARSH_TROPICAL_FRESHWATER; // 10
else
return df::enums::biome_type::biome_type::MARSH_TEMPERATE_FRESHWATER; // 5
}
else // drainage < 33, salinity >= 66
{
if ((is_possible_tropical_area_by_latitude && (get_region_parameter(y, x, 0) < 66)) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::MARSH_TROPICAL_SALTWATER; // 11
else
return df::enums::biome_type::biome_type::MARSH_TEMPERATE_SALTWATER; // 6
}
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_shrubland_or_marsh(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
if (region.drainage >= 33)
return get_biome_shrubland(is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
y,
x
);
// drainage < 33
return get_biome_marsh(region,
is_possible_tropical_area_by_latitude,
is_tropical_area_by_latitude,
y,
x
);
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_forest(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
int parameter = get_region_parameter(y, x, 0);
// drainage >= 33, not tropical area
if (!is_possible_tropical_area_by_latitude)
{
if ((region.rainfall < 75) || (region.temperature < 65))
{
if (region.temperature >= 10)
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13
else
return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12
}
else
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14
}
else // drainage >= 33, tropical area
{
if (((parameter < 66) || is_tropical_area_by_latitude) && (region.rainfall < 75))
return df::enums::biome_type::biome_type::FOREST_TROPICAL_CONIFER; // 15
if (parameter < 66)
return df::enums::biome_type::biome_type::FOREST_TROPICAL_DRY_BROADLEAF; // 16
if (is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::FOREST_TROPICAL_MOIST_BROADLEAF; // 17
else
{
if ((region.rainfall < 75) || (region.temperature < 65))
{
if (region.temperature >= 10)
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_CONIFER; // 13
else
return df::enums::biome_type::biome_type::FOREST_TAIGA; // 12
}
else
return df::enums::biome_type::biome_type::FOREST_TEMPERATE_BROADLEAF; // 14
}
}
}
//----------------------------------------------------------------------------//
// Utility function
//
//----------------------------------------------------------------------------//
int get_biome_swamp(df::region_map_entry& region,
bool is_possible_tropical_area_by_latitude,
bool is_tropical_area_by_latitude,
int y,
int x
)
{
int parameter = get_region_parameter(y, x, 0);
if (is_possible_tropical_area_by_latitude)
{
if (region.salinity < 66)
{
if ((parameter < 66) || is_tropical_area_by_latitude)
return df::enums::biome_type::biome_type::SWAMP_TROPICAL_FRESHWATER; // 7
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER;// 3
}
else // elevation between 100 and 149, vegetation >= 66, drainage < 33, salinity >= 66
{
if ((parameter < 66) || is_tropical_area_by_latitude)
{
if (region.drainage < 10)
return df::enums::biome_type::biome_type::SWAMP_MANGROVE; //9
else // drainage >= 10
return df::enums::biome_type::biome_type::SWAMP_TROPICAL_SALTWATER; // 8
}
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4
}
}
else // elevation between 100 and 149, vegetation >= 66, drainage < 33, not tropical area
{
if (region.salinity >= 66)
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_SALTWATER; // 4
else
return df::enums::biome_type::biome_type::SWAMP_TEMPERATE_FRESHWATER; // 3
}
}

@ -1,7 +0,0 @@
// world_coord_x/y is the location of the tile "owning" the biome, while world_ref_coord_y is the
// location of the tile the biome appears on. They differ when a mid level tile ("region tile")
// refers to a neighboring tile for the biome parameters. The difference can affect the tropicality
// determination. Since Tropicality is determined by latitude, the x coordinate of the reference is
// omitted.
//
int get_biome_type(int world_coord_x, int world_coord_y, int world_ref_coord_y);

@ -3,6 +3,7 @@
#include <array> #include <array>
#include <string> #include <string>
#include <vector> #include <vector>
#include "df/world_region_type.h"
using namespace std; using namespace std;
using std::array; using std::array;
@ -55,7 +56,7 @@ namespace embark_assist {
uint16_t coal_count = 0; uint16_t coal_count = 0;
uint8_t min_region_soil = 10; uint8_t min_region_soil = 10;
uint8_t max_region_soil = 0; uint8_t max_region_soil = 0;
bool waterfall = false; uint8_t max_waterfall = 0;
river_sizes river_size; river_sizes river_size;
int16_t biome_index[10]; // Indexed through biome_offset; -1 = null, Index of region, [0] not used int16_t biome_index[10]; // Indexed through biome_offset; -1 = null, Index of region, [0] not used
int16_t biome[10]; // Indexed through biome_offset; -1 = null, df::biome_type, [0] not used int16_t biome[10]; // Indexed through biome_offset; -1 = null, df::biome_type, [0] not used
@ -82,6 +83,16 @@ namespace embark_assist {
std::vector<bool> metals; std::vector<bool> metals;
std::vector<bool> economics; std::vector<bool> economics;
std::vector<bool> minerals; std::vector<bool> minerals;
mid_level_tile north_row[16];
mid_level_tile south_row[16];
mid_level_tile west_column[16];
mid_level_tile east_column[16];
uint8_t north_corner_selection[16]; // 0 - 3. For some reason DF stores everything needed for incursion
uint8_t west_corner_selection[16]; // detection in 17:th row/colum data in the region details except
// this info, so we have to go to neighboring world tiles to fetch it.
df::world_region_type region_type[16][16]; // Required for incursion override detection. We could store only the
// edges, but storing it for every tile allows for a unified fetching
// logic.
}; };
struct geo_datum { struct geo_datum {
@ -107,16 +118,22 @@ namespace embark_assist {
}; };
struct site_infos { struct site_infos {
bool incursions_processed;
bool aquifer; bool aquifer;
bool aquifer_full; bool aquifer_full;
uint8_t min_soil; uint8_t min_soil;
uint8_t max_soil; uint8_t max_soil;
bool flat; bool flat;
bool waterfall; uint8_t max_waterfall;
bool clay; bool clay;
bool sand; bool sand;
bool flux; bool flux;
bool coal; bool coal;
bool blood_rain;
bool permanent_syndrome_rain;
bool temporary_syndrome_rain;
bool reanimating;
bool thralling;
std::vector<uint16_t> metals; std::vector<uint16_t> metals;
std::vector<uint16_t> economics; std::vector<uint16_t> economics;
std::vector<uint16_t> minerals; std::vector<uint16_t> minerals;
@ -252,7 +269,7 @@ namespace embark_assist {
aquifer_ranges aquifer; aquifer_ranges aquifer;
river_ranges min_river; river_ranges min_river;
river_ranges max_river; river_ranges max_river;
yes_no_ranges waterfall; int8_t min_waterfall; // N/A(-1), Absent, 1-9
yes_no_ranges flat; yes_no_ranges flat;
present_absent_ranges clay; present_absent_ranges clay;
present_absent_ranges sand; present_absent_ranges sand;

@ -1,11 +1,13 @@
#include <time.h>
#include "Core.h" #include "Core.h"
#include <Console.h> #include "Console.h"
#include <Export.h> #include "Export.h"
#include <PluginManager.h> #include "PluginManager.h"
#include <time.h> #include "modules/Gui.h"
#include <modules/Gui.h> #include "modules/Screen.h"
#include <modules/Screen.h> #include "../uicommon.h"
#include "DataDefs.h" #include "DataDefs.h"
#include "df/coord2d.h" #include "df/coord2d.h"
@ -27,6 +29,7 @@
#include "survey.h" #include "survey.h"
DFHACK_PLUGIN("embark-assistant"); DFHACK_PLUGIN("embark-assistant");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
using namespace DFHack; using namespace DFHack;
using namespace df::enums; using namespace df::enums;
@ -62,7 +65,10 @@ namespace embark_assist {
&state->survey_results, &state->survey_results,
&mlt); &mlt);
embark_assist::survey::survey_embark(&mlt, &state->site_info, false); embark_assist::survey::survey_embark(&mlt,
&state->survey_results,
&state->site_info,
false);
embark_assist::overlay::set_embark(&state->site_info); embark_assist::overlay::set_embark(&state->site_info);
embark_assist::survey::survey_region_sites(&state->region_sites); embark_assist::survey::survey_region_sites(&state->region_sites);
@ -131,11 +137,43 @@ command_result embark_assistant (color_ostream &out, std::vector <std::string> &
//======================================================================================= //=======================================================================================
struct start_site_hook : df::viewscreen_choose_start_sitest {
typedef df::viewscreen_choose_start_sitest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (embark_assist::main::state)
return;
auto dims = Screen::getWindowSize();
int x = 60;
int y = dims.y - 2;
OutputString(COLOR_LIGHTRED, x, y, " " + Screen::getKeyDisplay(interface_key::CUSTOM_A));
OutputString(COLOR_WHITE, x, y, ": Embark ");
OutputString(COLOR_WHITE, x, y, dims.x > 82 ? "Assistant" : "Asst.");
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
if (!embark_assist::main::state && input->count(interface_key::CUSTOM_A))
{
Core::getInstance().setHotkeyCmd("embark-assistant");
return;
}
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(start_site_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(start_site_hook, feed);
//=======================================================================================
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
{ {
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"embark-assistant", "Embark site selection support.", "embark-assistant", "Embark site selection support.",
embark_assistant, true, /* true means that the command can't be used from non-interactive user interface */ embark_assistant, false, /* false means that the command can be used from non-interactive user interface */
// Extended help string. Used by CR_WRONG_USAGE and the help command: // Extended help string. Used by CR_WRONG_USAGE and the help command:
" This command starts the embark-assist plugin that provides embark site\n" " This command starts the embark-assist plugin that provides embark site\n"
" selection help. It has to be called while the pre-embark screen is\n" " selection help. It has to be called while the pre-embark screen is\n"
@ -157,6 +195,22 @@ DFhackCExport command_result plugin_shutdown (color_ostream &out)
//======================================================================================= //=======================================================================================
DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
{
if (is_enabled != enable)
{
if (!INTERPOSE_HOOK(start_site_hook, render).apply(enable) ||
!INTERPOSE_HOOK(start_site_hook, feed).apply(enable))
{
return CR_FAILURE;
}
is_enabled = enable;
}
return CR_OK;
}
//=======================================================================================
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{ {
switch (event) { switch (event) {
@ -224,7 +278,7 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
// Find the end of the normal inorganic definitions. // Find the end of the normal inorganic definitions.
embark_assist::main::state->max_inorganic = 0; embark_assist::main::state->max_inorganic = 0;
for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) { for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) {
if (world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) embark_assist::main::state->max_inorganic = i; if (!world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) embark_assist::main::state->max_inorganic = i;
} }
embark_assist::main::state->max_inorganic++; // To allow it to be used as size() replacement embark_assist::main::state->max_inorganic++; // To allow it to be used as size() replacement
@ -253,7 +307,7 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
embark_assist::main::state->survey_results[i][k].flux_count = 0; embark_assist::main::state->survey_results[i][k].flux_count = 0;
embark_assist::main::state->survey_results[i][k].min_region_soil = 10; embark_assist::main::state->survey_results[i][k].min_region_soil = 10;
embark_assist::main::state->survey_results[i][k].max_region_soil = 0; embark_assist::main::state->survey_results[i][k].max_region_soil = 0;
embark_assist::main::state->survey_results[i][k].waterfall = false; embark_assist::main::state->survey_results[i][k].max_waterfall = 0;
embark_assist::main::state->survey_results[i][k].river_size = embark_assist::defs::river_sizes::None; embark_assist::main::state->survey_results[i][k].river_size = embark_assist::defs::river_sizes::None;
for (uint8_t l = 1; l < 10; l++) { for (uint8_t l = 1; l < 10; l++) {
@ -291,7 +345,7 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
embark_assist::defs::mid_level_tiles mlt; embark_assist::defs::mid_level_tiles mlt;
embark_assist::survey::survey_mid_level_tile(&embark_assist::main::state->geo_summary, &embark_assist::main::state->survey_results, &mlt); embark_assist::survey::survey_mid_level_tile(&embark_assist::main::state->geo_summary, &embark_assist::main::state->survey_results, &mlt);
embark_assist::survey::survey_embark(&mlt, &embark_assist::main::state->site_info, false); embark_assist::survey::survey_embark(&mlt, &embark_assist::main::state->survey_results, &embark_assist::main::state->site_info, false);
embark_assist::overlay::set_embark(&embark_assist::main::state->site_info); embark_assist::overlay::set_embark(&embark_assist::main::state->site_info);
return CR_OK; return CR_OK;

@ -39,7 +39,7 @@ namespace embark_assist {
aquifer, aquifer,
min_river, min_river,
max_river, max_river,
waterfall, min_waterfall,
flat, flat,
clay, clay,
sand, sand,
@ -246,7 +246,7 @@ namespace embark_assist {
fclose(infile); fclose(infile);
// Checking done. No do the work. // Checking done. Now do the work.
infile = fopen(profile_file_name, "r"); infile = fopen(profile_file_name, "r");
i = first_fields; i = first_fields;
@ -452,9 +452,23 @@ namespace embark_assist {
break; break;
case fields::waterfall: case fields::min_waterfall:
case fields::flat: for (int16_t k = -1; k <= 9; k++) {
if (k == -1) {
element->list.push_back({ "N/A", k });
}
else if (k == 0) {
element->list.push_back({ "Absent", k });
}
else {
element->list.push_back({ std::to_string(k), k });
}
}
break;
case fields::blood_rain: case fields::blood_rain:
case fields::flat:
{ {
embark_assist::defs::yes_no_ranges k = embark_assist::defs::yes_no_ranges::NA; embark_assist::defs::yes_no_ranges k = embark_assist::defs::yes_no_ranges::NA;
while (true) { while (true) {
@ -955,8 +969,8 @@ namespace embark_assist {
state->finder_list.push_back({ "Max River", static_cast<int8_t>(i) }); state->finder_list.push_back({ "Max River", static_cast<int8_t>(i) });
break; break;
case fields::waterfall: case fields::min_waterfall:
state->finder_list.push_back({ "Waterfall", static_cast<int8_t>(i) }); state->finder_list.push_back({ "Min Waterfall Drop", static_cast<int8_t>(i) });
break; break;
case fields::flat: case fields::flat:
@ -1184,9 +1198,8 @@ namespace embark_assist {
static_cast<embark_assist::defs::river_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value); static_cast<embark_assist::defs::river_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break; break;
case fields::waterfall: case fields::min_waterfall:
finder.waterfall = finder.min_waterfall = state->ui[static_cast<uint8_t>(i)]->current_value;
static_cast<embark_assist::defs::yes_no_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break; break;
case fields::flat: case fields::flat:

@ -18,7 +18,8 @@ namespace embark_assist{
Intro, Intro,
General, General,
Finder, Finder,
Caveats Caveats_1,
Caveats_2
}; };
class ViewscreenHelpUi : public dfhack_viewscreen class ViewscreenHelpUi : public dfhack_viewscreen
@ -56,10 +57,14 @@ namespace embark_assist{
break; break;
case pages::Finder: case pages::Finder:
current_page = pages::Caveats; current_page = pages::Caveats_1;
break; break;
case pages::Caveats: case pages::Caveats_1:
current_page = pages::Caveats_2;
break;
case pages::Caveats_2:
current_page = pages::Intro; current_page = pages::Intro;
break; break;
} }
@ -67,7 +72,7 @@ namespace embark_assist{
else if (input->count(df::interface_key::SEC_CHANGETAB)) { else if (input->count(df::interface_key::SEC_CHANGETAB)) {
switch (current_page) { switch (current_page) {
case pages::Intro: case pages::Intro:
current_page = pages::Caveats; current_page = pages::Caveats_2;
break; break;
case pages::General: case pages::General:
@ -78,8 +83,12 @@ namespace embark_assist{
current_page = pages::General; current_page = pages::General;
break; break;
case pages::Caveats: case pages::Caveats_1:
current_page = pages::Intro; current_page = pages::Finder;
break;
case pages::Caveats_2:
current_page = pages::Caveats_1;
break; break;
} }
} }
@ -135,6 +144,8 @@ namespace embark_assist{
help_text.push_back(" embarking."); help_text.push_back(" embarking.");
help_text.push_back("Below this a Matching World Tiles count is displayed. It shows the number"); help_text.push_back("Below this a Matching World Tiles count is displayed. It shows the number");
help_text.push_back("of World Tiles that have at least one embark matching the Find criteria."); help_text.push_back("of World Tiles that have at least one embark matching the Find criteria.");
help_text.push_back("Note that World Tiles are the ones shown on the 'Region' map: the 'World'");
help_text.push_back("typically merges several World Tiles into each of its tiles.");
break; break;
@ -156,20 +167,25 @@ namespace embark_assist{
help_text.push_back("DF's display of resources in the region DF currently displays. Secondly, the"); help_text.push_back("DF's display of resources in the region DF currently displays. Secondly, the");
help_text.push_back("DF display doesn't take elevation based soil erosion or the magma sea depth"); help_text.push_back("DF display doesn't take elevation based soil erosion or the magma sea depth");
help_text.push_back("into consideration, so it can display resources that actually are cut away."); help_text.push_back("into consideration, so it can display resources that actually are cut away.");
help_text.push_back("(It can be noted that the DFHack Sand indicator does take these elements into"); help_text.push_back("Thirdly, it takes 'intrusions', i.e. small sections of neighboring tiles'");
help_text.push_back("account)."); help_text.push_back("biomes into consideration for many fields.");
help_text.push_back("(It can be noted that the DFHack Sand indicator does take the first two");
help_text.push_back("elements into account).");
help_text.push_back("The info the Embark Assistant displays is:"); help_text.push_back("The info the Embark Assistant displays is:");
help_text.push_back("Sand, if present"); help_text.push_back("Incompl. Survey if all intrusions couldn't be examined because that requires");
help_text.push_back("Clay, if present"); help_text.push_back("info from neighboring world tiles that haven't been surveyed.");
help_text.push_back("Min and Max soil depth in the embark rectangle."); help_text.push_back("Sand, if present, including through intrusions.");
help_text.push_back("Flat indicator if all the tiles in the embark have the same elevation."); help_text.push_back("Clay, if present, including thorugh intrusions.");
help_text.push_back("Aquifer indicator, color coded as blue if all tiles have an aquifer and light"); help_text.push_back("Min and Max soil depth in the embark rectangle, including intrusions.");
help_text.push_back("blue if some, but not all, tiles have one."); help_text.push_back("Flat indicator if all the tiles and intrusions have the same elevation.");
help_text.push_back("Waterfall, if the embark has river elevation differences."); help_text.push_back("Aquifer indicator: Part(ial) or Full, when present, including intrusions.");
help_text.push_back("Flux, if present"); help_text.push_back("Waterfall and largest Z level drop if the river has elevation differences");
help_text.push_back("A list of all metals present in the embark."); help_text.push_back("Evil weather, when present: BR = Blood Rain, TS = Temporary Syndrome");
help_text.push_back("PS = Permanent Syndrome, Re = Reanimating, and Th = Thralling. Intrusions.");
help_text.push_back("Flux, if present. NOT allowing for small intrusion bits.");
help_text.push_back("A list of all metals present in the embark. Not intrusions.");
help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux"); help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux");
help_text.push_back("stones are economic, so they show up here as well."); help_text.push_back("stones are economic, so they show up here as well. Not intrusions.");
help_text.push_back("In addition to the above, the Find functionality can also produce blinking"); help_text.push_back("In addition to the above, the Find functionality can also produce blinking");
help_text.push_back("overlays over the Local, Region, and World maps to indicate where"); help_text.push_back("overlays over the Local, Region, and World maps to indicate where");
help_text.push_back("matching embarks are found. The Local display marks the top left corner of"); help_text.push_back("matching embarks are found. The Local display marks the top left corner of");
@ -205,15 +221,17 @@ namespace embark_assist{
help_text.push_back("as long as at least one tile doesn't have one, but it doesn't have to have"); help_text.push_back("as long as at least one tile doesn't have one, but it doesn't have to have");
help_text.push_back("any at all."); help_text.push_back("any at all.");
help_text.push_back("Min/Max rivers should be self explanatory. The Yes and No values of"); help_text.push_back("Min/Max rivers should be self explanatory. The Yes and No values of");
help_text.push_back("Waterfall, Flat, etc. means one has to be Present and Absent respectivey."); help_text.push_back("Clay, etc. means one has to be Present and Absent respectivey.");
help_text.push_back("Min Waterfall Drop finds embarks with drops of at least that number");
help_text.push_back("of Z levels, but Absent = no waterfall at all.");
help_text.push_back("Min/Max soil uses the same terminology as DF for 1-4. The Min Soil"); help_text.push_back("Min/Max soil uses the same terminology as DF for 1-4. The Min Soil");
help_text.push_back("Everywhere toggles the Min Soil parameter between acting as All and"); help_text.push_back("Everywhere toggles the Min Soil parameter between acting as All and");
help_text.push_back("and Present."); help_text.push_back("Present.");
help_text.push_back("Freezing allows you to select embarks to select/avoid various freezing"); help_text.push_back("Freezing allows you to select embarks to select/avoid various freezing");
help_text.push_back("conditions. Note that the minimum temperature is held for only 10 ticks"); help_text.push_back("conditions. Note that the minimum temperature is held for only 10 ticks");
help_text.push_back("in many embarks."); help_text.push_back("in many embarks.");
help_text.push_back("Syndrome Rain allows you to search for Permanent and Temporary syndromes,"); help_text.push_back("Syndrome Rain allows you to search for Permanent and Temporary syndromes,");
help_text.push_back("where Permanent allows for Temporary ones as well, but not the reverse, as"); help_text.push_back("where Permanent allows for Temporary ones as well, but not the reverse, and");
help_text.push_back("Not Permanent matches everything except Permanent syndromes."); help_text.push_back("Not Permanent matches everything except Permanent syndromes.");
help_text.push_back("Reanimation packages thralling and reanimation into a single search"); help_text.push_back("Reanimation packages thralling and reanimation into a single search");
help_text.push_back("criterion. Not Tralling means nothing and just reanimation is matched."); help_text.push_back("criterion. Not Tralling means nothing and just reanimation is matched.");
@ -224,16 +242,28 @@ namespace embark_assist{
help_text.push_back("list. Note that Find is a fairly time consuming task (as it is in vanilla)."); help_text.push_back("list. Note that Find is a fairly time consuming task (as it is in vanilla).");
break; break;
case pages::Caveats: case pages::Caveats_1:
Screen::drawBorder(" Embark Assistant Help/Info Caveats Page "); Screen::drawBorder(" Embark Assistant Help/Info Caveats 1 Page ");
help_text.push_back("Find searching first does a sanity check (e.g. max < min) and then a rough"); help_text.push_back("The plugin surveys world tiles through two actions: using the 'f'ind");
help_text.push_back("function and through manual movement of the embark rectangle between world");
help_text.push_back("tiles. In both cases the whole world tile is surveyed, regardless of which");
help_text.push_back("tiles the embark rectangle covers.");
help_text.push_back("'Find' searching first does a sanity check (e.g. max < min) and then a rough");
help_text.push_back("world tile match to find tiles that may have matching embarks. This results"); help_text.push_back("world tile match to find tiles that may have matching embarks. This results");
help_text.push_back("in an overlay of inverted yellow X on top of the middle world map. Then"); help_text.push_back("in overlays of inverted yellow X on top of the Region and World maps. Then");
help_text.push_back("those tiles are scanned in detail, one feature shell (16*16 world tile"); help_text.push_back("those tiles are scanned in detail, one feature shell (16*16 world tile");
help_text.push_back("block) at a time, and the results are displayed as green inverted X on"); help_text.push_back("block) at a time, and the results are displayed as green inverted X on");
help_text.push_back("the same map (replacing or erasing the yellow ones). region map overlay"); help_text.push_back("the same map (replacing or erasing the yellow ones). Local map overlay");
help_text.push_back("data is generated as well."); help_text.push_back("data is generated as well.");
help_text.push_back("Since 'intrusion' processing requires that the neighboring tiles that may");
help_text.push_back("provide them are surveyed before the current tile and tiles have to be");
help_text.push_back("surveyed in some order, the find function can not perform a complete");
help_text.push_back("survey of prospective embarks that border world tiles yet to be surveyed");
help_text.push_back("so the very first 'find' will fail to mark such embarks that actually do");
help_text.push_back("match, while the second and following 'find' operations will locate them");
help_text.push_back("because critical information from the first scan is kept for subsequent");
help_text.push_back("ones.");
help_text.push_back(""); help_text.push_back("");
help_text.push_back("Caveats & technical stuff:"); help_text.push_back("Caveats & technical stuff:");
help_text.push_back("- The Find searching uses simulated cursor movement input to DF to get it"); help_text.push_back("- The Find searching uses simulated cursor movement input to DF to get it");
@ -242,8 +272,8 @@ namespace embark_assist{
help_text.push_back("- The search strategy causes detailed region data to update surveyed"); help_text.push_back("- The search strategy causes detailed region data to update surveyed");
help_text.push_back(" world info, and this can cause a subsequent search to generate a smaller"); help_text.push_back(" world info, and this can cause a subsequent search to generate a smaller");
help_text.push_back(" set of preliminary matches (yellow tiles) than a previous search."); help_text.push_back(" set of preliminary matches (yellow tiles) than a previous search.");
help_text.push_back(" However, this is a bug only if it causes the search to fail to find"); help_text.push_back(" Note that the first search can miss a fair number of matches for");
help_text.push_back(" actual existing matches."); help_text.push_back(" technical reasons discussed above and below.");
help_text.push_back("- The site info is deduced by the author, so there may be errors and"); help_text.push_back("- The site info is deduced by the author, so there may be errors and");
help_text.push_back(" there are probably site types that end up not being identified."); help_text.push_back(" there are probably site types that end up not being identified.");
help_text.push_back("- Aquifer indications are based on the author's belief that they occur"); help_text.push_back("- Aquifer indications are based on the author's belief that they occur");
@ -252,10 +282,10 @@ namespace embark_assist{
help_text.push_back("- The biome determination logic comes from code provided by Ragundo,"); help_text.push_back("- The biome determination logic comes from code provided by Ragundo,");
help_text.push_back(" with only marginal changes by the author. References can be found in"); help_text.push_back(" with only marginal changes by the author. References can be found in");
help_text.push_back(" the source file."); help_text.push_back(" the source file.");
help_text.push_back("- Thralling is determined by weather material interactions causing"); help_text.push_back("- Thralling is determined by whether material interactions causes");
help_text.push_back(" blinking, which the author believes is one of 4 thralling changes."); help_text.push_back(" blinking, which the author believes is one of 4 thralling changes.");
help_text.push_back("- The geo information is gathered by code which is essentially a"); help_text.push_back("- The geo information is gathered by code which is essentially a");
help_text.push_back(" copy of parts of prospector's code adapted for this plugin."); help_text.push_back(" copy of parts of Prospector's code adapted for this plugin.");
help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS."); help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS.");
help_text.push_back("- Flux determination is made by finding the reaction PIG_IRON_MAKING."); help_text.push_back("- Flux determination is made by finding the reaction PIG_IRON_MAKING.");
help_text.push_back("- Coal is detected by finding COAL producing reactions on minerals."); help_text.push_back("- Coal is detected by finding COAL producing reactions on minerals.");
@ -263,7 +293,39 @@ namespace embark_assist{
help_text.push_back(" reaching caverns that have been removed at world gen to fail to be"); help_text.push_back(" reaching caverns that have been removed at world gen to fail to be");
help_text.push_back(" generated at all. It's likely this bug also affects magma pools."); help_text.push_back(" generated at all. It's likely this bug also affects magma pools.");
help_text.push_back(" This plugin does not address this but scripts can correct it."); help_text.push_back(" This plugin does not address this but scripts can correct it.");
help_text.push_back("Version 0.8 2018-12-04");
break;
case pages::Caveats_2:
Screen::drawBorder(" Embark Assistant Help/Info Caveats 2 Page ");
help_text.push_back("- The plugin detects 'incursions' of neighboring tiles into embarks, but");
help_text.push_back(" this functionality is incomplete when the incursion comes from a");
help_text.push_back(" neighboring tile that hasn't been surveyed yet. The embark info displays");
help_text.push_back(" what it can, while indicating if it is incomplete, while the first 'f'ind");
help_text.push_back(" will automatically fail to match any embarks that can not be analyzed");
help_text.push_back(" fully. Such failures only appear on embarks that touch an edge of the");
help_text.push_back(" world tile, and a second (and all subsequent) searches will be complete.");
help_text.push_back(" Since searches can take considerable time, it's left to the user to decide");
help_text.push_back(" whether to make a second, completing, search.");
help_text.push_back("- Incursions are taken into consideration when looking for Aquifers,");
help_text.push_back(" Clay, Sand, Min Soil when Everywhere, Biomes, Regions, Evil Weather,");
help_text.push_back(" Savagery, Evilness, Freezing and Flatness, but ignored for metals/");
help_text.push_back(" economics/minerals (including Flux and Coal) as any volumes are typically");
help_text.push_back(" too small to be of interest. Rivers, Waterfalls, Spires, and Magma Pools");
help_text.push_back(" are not incursion related features.");
help_text.push_back("- There are special rules for handing of intrusions from Lakes and Oceans,");
help_text.push_back(" as well as Mountains into everything that isn't a Lake or Ocean, and the");
help_text.push_back(" rules state that these intrusions should be reversed (i.e. 'normal' biomes");
help_text.push_back(" should push into Lakes, Oceans, and Mountains, even when the indicators");
help_text.push_back(" say otherwise). This rule is clear for edges, but not for corners, as it");
help_text.push_back(" does not specify which of the potentially multiple 'superior' biomes");
help_text.push_back(" should be used. The plugin uses the arbitrarily selected rule that the");
help_text.push_back(" touching corner to the NW should be selected if eligible, then the one to");
help_text.push_back(" the N, followed by the one to the W, and lastly the one acting as the");
help_text.push_back(" reference. This means there's a risk embarks with such 'trouble' corners");
help_text.push_back(" may get affected corner(s) evaluated incorrectly.");
help_text.push_back("Version 0.9 2019-07-12");
break; break;
} }
@ -311,7 +373,10 @@ namespace embark_assist{
embark_assist::screen::paintString(pen_lr, 3, 9, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_L).c_str()); embark_assist::screen::paintString(pen_lr, 3, 9, DFHack::Screen::getKeyDisplay(df::interface_key::CUSTOM_L).c_str());
break; break;
case pages::Caveats: case pages::Caveats_1:
break;
case pages::Caveats_2:
break; break;
} }
dfhack_viewscreen::render(); dfhack_viewscreen::render();

File diff suppressed because it is too large Load Diff

@ -331,6 +331,10 @@ void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs:
void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) { void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) {
state->embark_info.clear(); state->embark_info.clear();
if (!site_info->incursions_processed) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTRED), "Incomp. Survey" });
}
if (site_info->sand) { if (site_info->sand) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), "Sand" }); state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), "Sand" });
} }
@ -351,16 +355,65 @@ void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_in
if (site_info->aquifer) { if (site_info->aquifer) {
if (site_info->aquifer_full) { if (site_info->aquifer_full) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Aquifer" }); state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Full Aquifer" });
} }
else { else {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Aquifer" }); state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Part. Aquifer" });
} }
} }
if (site_info->waterfall) { if (site_info->max_waterfall > 0) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Waterfall" }); state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Waterfall " + std::to_string(site_info->max_waterfall) });
}
if (site_info->blood_rain ||
site_info->permanent_syndrome_rain ||
site_info->temporary_syndrome_rain ||
site_info->reanimating ||
site_info->thralling) {
std::string blood_rain;
std::string permanent_syndrome_rain;
std::string temporary_syndrome_rain;
std::string reanimating;
std::string thralling;
if (site_info->blood_rain) {
blood_rain = "BR ";
}
else {
blood_rain = " ";
}
if (site_info->permanent_syndrome_rain) {
permanent_syndrome_rain = "PS ";
}
else {
permanent_syndrome_rain = " ";
}
if (site_info->temporary_syndrome_rain) {
temporary_syndrome_rain = "TS ";
}
else {
permanent_syndrome_rain = " ";
}
if (site_info->reanimating) {
reanimating = "Re ";
}
else {
reanimating = " ";
}
if (site_info->thralling) {
thralling = "Th";
}
else {
thralling = " ";
}
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTRED), blood_rain + temporary_syndrome_rain + permanent_syndrome_rain + reanimating + thralling });
} }
if (site_info->flux) { if (site_info->flux) {

File diff suppressed because it is too large Load Diff

@ -27,9 +27,52 @@ namespace embark_assist {
df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset); df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset);
df::world_region_type region_type_of(embark_assist::defs::world_tile_data *survey_results,
int16_t x,
int16_t y,
int8_t i,
int8_t k);
// Returns the direction from which data should be read using DF's
// 0 - 8 range direction indication.
// "corner_location" uses the 0 - 8 notation to indicate the reader's
// relation to the data read. Only some values are legal, as the set of
// relations is limited by how the corners are defined.
// x, y, i, k are the world tile/mid level tile coordinates of the
// tile the results should be applied to.
// Deals with references outside of the world map by returning "yield"
// results, but requires all world tiles affected by the corner to have
// been surveyed.
//
uint8_t translate_corner(embark_assist::defs::world_tile_data *survey_results,
uint8_t corner_location,
uint16_t x,
uint16_t y,
uint8_t i,
uint8_t k);
// Same logic and restrictions as for translate_corner.
//
uint8_t translate_ns_edge(embark_assist::defs::world_tile_data *survey_results,
bool own_edge,
uint16_t x,
uint16_t y,
uint8_t i,
uint8_t k);
// Same logic and restrictions as for translate_corner.
//
uint8_t translate_ew_edge(embark_assist::defs::world_tile_data *survey_results,
bool own_edge,
uint16_t x,
uint16_t y,
uint8_t i,
uint8_t k);
void survey_region_sites(embark_assist::defs::site_lists *site_list); void survey_region_sites(embark_assist::defs::site_lists *site_list);
void survey_embark(embark_assist::defs::mid_level_tiles *mlt, void survey_embark(embark_assist::defs::mid_level_tiles *mlt,
embark_assist::defs::world_tile_data *survey_results,
embark_assist::defs::site_infos *site_info, embark_assist::defs::site_infos *site_info,
bool use_cache); bool use_cache);

@ -188,7 +188,7 @@ function num_sequence:new(a,b)
else else
error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b)) error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b))
end end
print("seqID:"..o.seqID) --print("seqID:"..o.seqID)
setmetatable(o,self) setmetatable(o,self)
return o return o
end end

@ -0,0 +1,11 @@
local _ENV = mkmodule('plugins.map-render')
--[[
Native functions:
* render_map_rect(x,y,z,w,h)
--]]
return _ENV

@ -0,0 +1,125 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "VersionInfo.h"
#include "VTableInterpose.h"
#include "LuaTools.h"
#include "DataDefs.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/init.h"
#include "df/renderer.h"
#include "df/graphic.h"
#include "df/enabler.h"
#include "df/map_renderer.h"
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("map-render");
REQUIRE_GLOBAL(window_x)
REQUIRE_GLOBAL(window_y)
REQUIRE_GLOBAL(window_z)
REQUIRE_GLOBAL_NO_USE(gps)
REQUIRE_GLOBAL_NO_USE(enabler)
REQUIRE_GLOBAL_NO_USE(twbt_render_map)
REQUIRE_GLOBAL(init)
#ifdef WIN32
// On Windows there's no parameter pointing to the map_renderer structure
typedef void(_stdcall *RENDER_MAP)(int);
RENDER_MAP _render_map;
void render_map(){ _render_map(0); }
#else
REQUIRE_GLOBAL(map_renderer)
typedef void(*RENDER_MAP)(void*, int);
RENDER_MAP _render_map;
void render_map(){ _render_map(map_renderer,0); }
#endif
static int render_map_rect(lua_State* L)
{
CoreSuspender suspender;
int x = luaL_checkint(L, 1);
int y = luaL_checkint(L, 2);
int z = luaL_checkint(L, 3);
int w = luaL_checkint(L, 4);
int h = luaL_checkint(L, 5);
uint8_t *s = df::global::gps->screen;
//backup state
//TODO: figure out if we can replace screen with other pointer. That way it could be a bit more tidy
int32_t win_h = df::global::gps->dimy;
int32_t was_x = *window_x;
int32_t was_y = *window_y;
int32_t was_z = *window_z;
int32_t gx = init->display.grid_x;
int32_t gy = init->display.grid_y;
init->display.grid_x = w+1;
init->display.grid_y = h+1;
*window_x = x;
*window_y = y;
*window_z = z;
//force full redraw
df::global::gps->force_full_display_count = 1;
//this modifies screen so it REALLY wants to redraw stuff
for (int ty = 0; ty < h; ty++)
for (int tx = 0; tx < w; tx++)
{
for (int i = 0; i < 4; i++)
{
int t = (tx + 1)*win_h + ty + 1;
s[t * 4 + i] = 0;
}
}
render_map();
//restore state
*window_x = was_x;
*window_y = was_y;
*window_z = was_z;
init->display.grid_x = gx;
init->display.grid_y = gy;
lua_createtable(L,w*h*4,0);
int counter = 0;
for (int ty = 0; ty < h; ty++)
for (int tx = 0; tx < w; tx++)
{
for (int i = 0; i < 4;i++)
{
int t = (tx + 1)*win_h + ty + 1;
lua_pushnumber(L, s[t*4+i]);
lua_rawseti(L, -2, counter);
counter++;
}
}
return 1;
}
DFHACK_PLUGIN_LUA_COMMANDS{
DFHACK_LUA_COMMAND(render_map_rect),
DFHACK_LUA_END
};
DFhackCExport command_result plugin_init(color_ostream &out, std::vector<PluginCommand> &commands)
{
auto addr =reinterpret_cast<RENDER_MAP>(Core::getInstance().vinfo->getAddress("twbt_render_map"));
if (addr == nullptr)
return CR_FAILURE;
_render_map = addr;
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &out)
{
return CR_OK;
}

@ -369,8 +369,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
// Can't check limits earlier as we must be sure we are in query or default mode // Can't check limits earlier as we must be sure we are in query or default mode
// (so we can clear the button down flag) // (so we can clear the button down flag)
auto dims = Gui::getDwarfmodeViewDims(); auto dims = Gui::getDwarfmodeViewDims();
int right_bound = (dims.menu_x1 > 0) ? dims.menu_x1 - 2 : gps->dimx - 2; if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2)
if (mx < 1 || mx > right_bound || my < 1 || my > gps->dimy - 2)
return false; return false;
if (ui->main.mode == df::ui_sidebar_mode::Zones || if (ui->main.mode == df::ui_sidebar_mode::Zones ||
@ -435,13 +434,13 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
if (mx < scroll_trigger_x) if (mx < scroll_trigger_x)
sendKey(interface_key::CURSOR_LEFT_FAST); sendKey(interface_key::CURSOR_LEFT_FAST);
if (mx > ((dims.menu_x1 > 0) ? dims.menu_x1 : gps->dimx) - scroll_trigger_x) if (mx > dims.map_x2 - scroll_trigger_x)
sendKey(interface_key::CURSOR_RIGHT_FAST); sendKey(interface_key::CURSOR_RIGHT_FAST);
if (my < scroll_trigger_y) if (my < scroll_trigger_y)
sendKey(interface_key::CURSOR_UP_FAST); sendKey(interface_key::CURSOR_UP_FAST);
if (my > gps->dimy - scroll_trigger_y) if (my > dims.map_y2 - scroll_trigger_y)
sendKey(interface_key::CURSOR_DOWN_FAST); sendKey(interface_key::CURSOR_DOWN_FAST);
} }
@ -572,12 +571,11 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
static decltype(enabler->clock) last_t = 0; static decltype(enabler->clock) last_t = 0;
auto dims = Gui::getDwarfmodeViewDims(); auto dims = Gui::getDwarfmodeViewDims();
auto right_margin = (dims.menu_x1 > 0) ? dims.menu_x1 : gps->dimx;
int32_t mx, my; int32_t mx, my;
auto mpos = get_mouse_pos(mx, my); auto mpos = get_mouse_pos(mx, my);
bool mpos_valid = mpos.x != -30000 && mpos.y != -30000 && mpos.z != -30000; bool mpos_valid = mpos.x != -30000 && mpos.y != -30000 && mpos.z != -30000;
if (mx < 1 || mx > right_margin - 2 || my < 1 || my > gps->dimy - 2) if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2)
mpos_valid = false; mpos_valid = false;
// Check if in lever binding mode // Check if in lever binding mode
@ -683,7 +681,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
return; return;
} }
if (mx > right_margin - scroll_buffer) if (mx > dims.map_x2 - scroll_buffer)
{ {
sendKey(interface_key::CURSOR_RIGHT); sendKey(interface_key::CURSOR_RIGHT);
return; return;
@ -695,7 +693,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
return; return;
} }
if (my > gps->dimy - scroll_buffer) if (my > dims.map_y2 - scroll_buffer)
{ {
sendKey(interface_key::CURSOR_DOWN); sendKey(interface_key::CURSOR_DOWN);
return; return;
@ -732,9 +730,9 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
if (shouldTrack()) if (shouldTrack())
{ {
if (delta_t <= scroll_delay && (mx < scroll_buffer || if (delta_t <= scroll_delay && (mx < scroll_buffer ||
mx > dims.menu_x1 - scroll_buffer || mx > dims.map_x2 - scroll_buffer ||
my < scroll_buffer || my < scroll_buffer ||
my > gps->dimy - scroll_buffer)) my > dims.map_y2 - scroll_buffer))
{ {
return; return;
} }
@ -836,26 +834,35 @@ static command_result mousequery_cmd(color_ostream &out, vector <string> & param
else if (cmd[0] == 'p') else if (cmd[0] == 'p')
{ {
plugin_enabled = (state == "enable"); plugin_enabled = (state == "enable");
out << "mousequery: plugin " << (plugin_enabled ? "enabled" : "disabled") << endl;
} }
else if (cmd[0] == 'r') else if (cmd[0] == 'r')
{ {
rbutton_enabled = (state == "enable"); rbutton_enabled = (state == "enable");
out << "mousequery: rbutton " << (rbutton_enabled ? "enabled" : "disabled") << endl;
} }
else if (cmd[0] == 't') else if (cmd[0] == 't')
{ {
tracking_enabled = (state == "enable"); tracking_enabled = (state == "enable");
if (!tracking_enabled) if (!tracking_enabled) {
out << "mousequery: edge scrolling disabled" << endl;
active_scrolling = false; active_scrolling = false;
}
out << "mousequery: tracking " << (tracking_enabled ? "enabled" : "disabled") << endl;
} }
else if (cmd[0] == 'e') else if (cmd[0] == 'e')
{ {
active_scrolling = (state == "enable"); active_scrolling = (state == "enable");
if (active_scrolling) if (active_scrolling) {
out << "mousequery: tracking enabled" << endl;
tracking_enabled = true; tracking_enabled = true;
}
out << "mousequery: edge scrolling " << (active_scrolling ? "enabled" : "disabled") << endl;
} }
else if (cmd[0] == 'l') else if (cmd[0] == 'l')
{ {
live_view = (state == "enable"); live_view = (state == "enable");
out << "mousequery: live view " << (live_view ? "enabled" : "disabled") << endl;
} }
else if (cmd == "drag") else if (cmd == "drag")
{ {

@ -0,0 +1,96 @@
syntax = "proto2";
package DwarfControl;
//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization
option optimize_for = LITE_RUNTIME;
// Plugin: RemoteFortressReader
import "ui_sidebar_mode.proto";
import "RemoteFortressReader.proto";
// RPC GetSideMenu : EmptyMessage -> SidebarState
// RPC SetSideMenu : SidebarCommand -> EmptyMessage
enum BuildCategory
{
NotCategory = 0;
SiegeEngines = 1;
Traps = 2;
Workshops = 3;
Furnaces = 4;
Constructions = 5;
MachineComponents = 6;
Track = 7;
}
enum MenuAction
{
MenuNone = 0;
MenuSelect = 1;
MenuCancel = 2;
MenuSelectAll = 3;
}
enum BuildSelectorStage
{
StageNoMat = 0;
StagePlace = 1;
StageItemSelect = 2;
}
message SidebarState
{
optional proto.enums.ui_sidebar_mode.ui_sidebar_mode mode = 1;
repeated MenuItem menu_items = 2;
optional BuildSelector build_selector = 3;
}
message MenuItem
{
optional RemoteFortressReader.BuildingType building_type = 1;
optional int32 existing_count = 2;
optional BuildCategory build_category = 3;
}
message SidebarCommand
{
optional proto.enums.ui_sidebar_mode.ui_sidebar_mode mode = 1;
optional int32 menu_index = 2;
optional MenuAction action = 3;
optional RemoteFortressReader.Coord selection_coord = 4;
}
message BuiildReqChoice
{
optional int32 distance = 1;
optional string name = 2;
optional int32 num_candidates = 3;
optional int32 used_count = 4;
}
message BuildItemReq
{
//Put filter here = 1
optional int32 count_required = 2;
optional int32 count_max = 3;
optional int32 count_provided = 4;
}
message BuildSelector
{
optional RemoteFortressReader.BuildingType building_type = 1;
optional BuildSelectorStage stage = 2;
repeated BuiildReqChoice choices = 3;
optional int32 sel_index = 4;
repeated BuildItemReq requirements = 5;
optional int32 req_index = 6;
repeated string errors = 7;
optional int32 radius_x_low = 8;
optional int32 radius_y_low = 9;
optional int32 radius_x_high = 10;
optional int32 radius_y_high = 11;
optional RemoteFortressReader.Coord cursor = 12;
repeated int32 tiles = 13;
}

@ -42,6 +42,7 @@ import "ItemdefInstrument.proto";
// RPC MovementSelectCommand : IntMessage -> EmptyMessage // RPC MovementSelectCommand : IntMessage -> EmptyMessage
// RPC MiscMoveCommand : MiscMoveParams -> EmptyMessage // RPC MiscMoveCommand : MiscMoveParams -> EmptyMessage
// RPC GetLanguage : EmptyMessage -> Language // RPC GetLanguage : EmptyMessage -> Language
// RPC GetGameValidity : EmptyMessage -> SingleBool
//We use shapes, etc, because the actual tiletypes may differ between DF versions. //We use shapes, etc, because the actual tiletypes may differ between DF versions.
enum TiletypeShape enum TiletypeShape
@ -201,6 +202,14 @@ enum InventoryMode
Strapped = 10; Strapped = 10;
} }
enum ArmorLayer
{
LAYER_UNDER = 0;
LAYER_OVER = 1;
LAYER_ARMOR = 2;
LAYER_COVER = 3;
}
message Coord message Coord
{ {
optional int32 x = 1; optional int32 x = 1;
@ -320,6 +329,31 @@ message Item
optional ArtImage image = 18; optional ArtImage image = 18;
} }
message PlantTile
{
optional bool trunk = 1;
optional bool connection_east = 2;
optional bool connection_south = 3;
optional bool connection_west = 4;
optional bool connection_north = 5;
optional bool branches = 6;
optional bool twigs = 7;
optional TiletypeSpecial tile_type = 8;
}
message TreeInfo
{
optional Coord size = 1;
repeated PlantTile tiles = 2;
}
message PlantInstance
{
optional int32 plant_type = 1;
optional Coord pos = 2;
optional TreeInfo tree_info = 3;
}
message MapBlock message MapBlock
{ {
required int32 map_x = 1; required int32 map_x = 1;
@ -371,6 +405,9 @@ message MaterialDefinition{
optional string name = 3; optional string name = 3;
optional ColorDefinition state_color = 4; //Simplifying colors to assume room temperature. optional ColorDefinition state_color = 4; //Simplifying colors to assume room temperature.
optional ItemdefInstrument.InstrumentDef instrument = 5; optional ItemdefInstrument.InstrumentDef instrument = 5;
optional int32 up_step = 6;
optional int32 down_step = 7;
optional ArmorLayer layer = 8;
} }
message BuildingType message BuildingType
@ -426,6 +463,20 @@ message InventoryItem
{ {
optional InventoryMode mode = 1; optional InventoryMode mode = 1;
optional Item item = 2; optional Item item = 2;
optional int32 body_part_id = 3;
}
message WoundPart
{
optional int32 global_layer_idx = 1;
optional int32 body_part_id = 2;
optional int32 layer_idx = 3;
}
message UnitWound
{
repeated WoundPart parts = 1;
optional bool severed_part = 2;
} }
message UnitDefinition message UnitDefinition
@ -453,6 +504,9 @@ message UnitDefinition
optional float subpos_x = 21; optional float subpos_x = 21;
optional float subpos_y = 22; optional float subpos_y = 22;
optional float subpos_z = 23; optional float subpos_z = 23;
optional Coord facing = 24;
optional int32 age = 25;
repeated UnitWound wounds = 26;
} }
message UnitList message UnitList
@ -787,6 +841,7 @@ message CreatureRaw
optional int32 adultsize = 9; optional int32 adultsize = 9;
repeated CasteRaw caste = 10; repeated CasteRaw caste = 10;
repeated TissueRaw tissues = 11; repeated TissueRaw tissues = 11;
repeated bool flags = 12;
} }
message CreatureRawList message CreatureRawList

@ -0,0 +1,63 @@
package proto.enums.ui_sidebar_mode;
//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization
option optimize_for = LITE_RUNTIME;
enum ui_sidebar_mode
{
Default = 0;
Squads = 1;
DesignateMine = 2;
DesignateRemoveRamps = 3;
DesignateUpStair = 4;
DesignateDownStair = 5;
DesignateUpDownStair = 6;
DesignateUpRamp = 7;
DesignateChannel = 8;
DesignateGatherPlants = 9;
DesignateRemoveDesignation = 10;
DesignateSmooth = 11;
DesignateCarveTrack = 12;
DesignateEngrave = 13;
DesignateCarveFortification = 14;
Stockpiles = 15;
Build = 16;
QueryBuilding = 17;
Orders = 18;
OrdersForbid = 19;
OrdersRefuse = 20;
OrdersWorkshop = 21;
OrdersZone = 22;
BuildingItems = 23;
ViewUnits = 24;
LookAround = 25;
DesignateItemsClaim = 26;
DesignateItemsForbid = 27;
DesignateItemsMelt = 28;
DesignateItemsUnmelt = 29;
DesignateItemsDump = 30;
DesignateItemsUndump = 31;
DesignateItemsHide = 32;
DesignateItemsUnhide = 33;
DesignateChopTrees = 34;
DesignateToggleEngravings = 35;
DesignateToggleMarker = 36;
Hotkeys = 37;
DesignateTrafficHigh = 38;
DesignateTrafficNormal = 39;
DesignateTrafficLow = 40;
DesignateTrafficRestricted = 41;
Zones = 42;
ZonesPenInfo = 43;
ZonesPitInfo = 44;
ZonesHospitalInfo = 45;
ZonesGatherInfo = 46;
DesignateRemoveConstruction = 47;
DepotAccess = 48;
NotesPoints = 49;
NotesRoutes = 50;
Burrows = 51;
Hauling = 52;
ArenaWeather = 53;
ArenaTrees = 54;
}

@ -4,27 +4,39 @@ SET(PROJECT_SRCS
remotefortressreader.cpp remotefortressreader.cpp
adventure_control.cpp adventure_control.cpp
building_reader.cpp building_reader.cpp
dwarf_control.cpp
item_reader.cpp item_reader.cpp
) )
# A list of headers # A list of headers
SET(PROJECT_HDRS SET(PROJECT_HDRS
adventure_control.h adventure_control.h
building_reader.h building_reader.h
dwarf_control.h
item_reader.h item_reader.h
df_version_int.h df_version_int.h
) )
#proto files to include. #proto files to include.
SET(PROJECT_PROTO SET(PROJECT_PROTO
${CMAKE_CURRENT_SOURCE_DIR}/../proto/RemoteFortressReader.pb.cc RemoteFortressReader
${CMAKE_CURRENT_SOURCE_DIR}/../proto/AdventureControl.pb.cc AdventureControl
${CMAKE_CURRENT_SOURCE_DIR}/../proto/ItemdefInstrument.pb.cc ItemdefInstrument
DwarfControl
ui_sidebar_mode
) )
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) SET(PLUGIN_PROTOS)
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO} PROPERTIES GENERATED TRUE) FOREACH(pbuf ${PROJECT_PROTO})
LIST(APPEND PLUGIN_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/../proto/${pbuf}.proto)
ENDFOREACH()
STRING(REPLACE ".proto" ".pb.cc" PLUGIN_PROTO_SRCS "${PLUGIN_PROTOS}")
STRING(REPLACE ".proto" ".pb.h" PLUGIN_PROTO_HDRS "${PLUGIN_PROTOS}")
SET_SOURCE_FILES_PROPERTIES(${PLUGIN_PROTO_SRCS} ${PLUGIN_PROTO_HDRS} PROPERTIES GENERATED TRUE)
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} ${PLUGIN_PROTO_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
# mash them together (headers are marked as headers and nothing will try to compile them) # mash them together (headers are marked as headers and nothing will try to compile them)
LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS};${PROJECT_PROTO}) LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS} ${PLUGIN_PROTOS} ${PLUGIN_PROTO_SRCS} ${PLUGIN_PROTO_HDRS})
IF(UNIX AND NOT APPLE) IF(UNIX AND NOT APPLE)
SET(PROJECT_LIBS ${PROJECT_LIBS} SDL) SET(PROJECT_LIBS ${PROJECT_LIBS} SDL)

@ -78,7 +78,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D
bld->mutable_building_type()->set_building_subtype(-1); bld->mutable_building_type()->set_building_subtype(-1);
bld->mutable_building_type()->set_building_custom(-1); bld->mutable_building_type()->set_building_custom(-1);
bld->set_id(ENUM_KEY_STR(building_type, bt)); bld->set_id(ENUM_KEY_STR(building_type, bt));
bld->set_name(ENUM_ATTR_STR(building_type, name, bt));
switch (bt) switch (bt)
{ {
case df::enums::building_type::NONE: case df::enums::building_type::NONE:
@ -101,6 +101,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D
bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_subtype(st);
bld->mutable_building_type()->set_building_custom(-1); bld->mutable_building_type()->set_building_custom(-1);
bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(furnace_type, st)); bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(furnace_type, st));
bld->set_name(ENUM_ATTR_STR(furnace_type, name, st));
if (st == furnace_type::Custom) if (st == furnace_type::Custom)
{ {
@ -128,7 +129,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D
bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_subtype(st);
bld->mutable_building_type()->set_building_custom(-1); bld->mutable_building_type()->set_building_custom(-1);
bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(shop_type, st)); bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(shop_type, st));
bld->set_name(ENUM_KEY_STR(shop_type, st));
} }
break; break;
case df::enums::building_type::Door: case df::enums::building_type::Door:
@ -149,6 +150,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D
bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_subtype(st);
bld->mutable_building_type()->set_building_custom(-1); bld->mutable_building_type()->set_building_custom(-1);
bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(workshop_type, st)); bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(workshop_type, st));
bld->set_name(ENUM_ATTR_STR(workshop_type, name, st));
if (st == workshop_type::Custom) if (st == workshop_type::Custom)
{ {
@ -190,7 +192,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D
bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_subtype(st);
bld->mutable_building_type()->set_building_custom(-1); bld->mutable_building_type()->set_building_custom(-1);
bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(siegeengine_type, st)); bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(siegeengine_type, st));
bld->set_name(ENUM_KEY_STR(siegeengine_type, st));
} }
break; break;
case df::enums::building_type::Trap: case df::enums::building_type::Trap:
@ -201,7 +203,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D
bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_subtype(st);
bld->mutable_building_type()->set_building_custom(-1); bld->mutable_building_type()->set_building_custom(-1);
bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(trap_type, st)); bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(trap_type, st));
bld->set_name(ENUM_KEY_STR(trap_type, st));
} }
break; break;
case df::enums::building_type::AnimalTrap: case df::enums::building_type::AnimalTrap:
@ -224,7 +226,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D
bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_subtype(st);
bld->mutable_building_type()->set_building_custom(-1); bld->mutable_building_type()->set_building_custom(-1);
bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(civzone_type, st)); bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(civzone_type, st));
bld->set_name(ENUM_KEY_STR(civzone_type, st));
} }
break; break;
case df::enums::building_type::Weapon: case df::enums::building_type::Weapon:
@ -241,7 +243,7 @@ DFHack::command_result GetBuildingDefList(DFHack::color_ostream &stream, const D
bld->mutable_building_type()->set_building_subtype(st); bld->mutable_building_type()->set_building_subtype(st);
bld->mutable_building_type()->set_building_custom(-1); bld->mutable_building_type()->set_building_custom(-1);
bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(construction_type, st)); bld->set_id(ENUM_KEY_STR(building_type, bt) + "/" + ENUM_KEY_STR(construction_type, st));
bld->set_name(ENUM_KEY_STR(construction_type, st));
} }
break; break;
case df::enums::building_type::Hatch: case df::enums::building_type::Hatch:

@ -1,2 +1,2 @@
#pragma once #pragma once
#define DF_VERSION_INT 44002 #define DF_VERSION_INT 44012

@ -0,0 +1,493 @@
#include "dwarf_control.h"
#include "DataDefs.h"
#include "df_version_int.h"
#include "df/build_req_choice_genst.h"
#include "df/build_req_choice_specst.h"
#include "df/build_req_choicest.h"
#include "df/building_def.h"
#include "df/building_def_furnacest.h"
#include "df/building_def_workshopst.h"
#include "df/job.h"
#include "df/job_list_link.h"
#include "df/interface_button_construction_building_selectorst.h"
#include "df/interface_button_construction_category_selectorst.h"
#include "df/ui.h"
#include "df/ui_build_selector.h"
#include "df/ui_sidebar_menus.h"
#include "df/viewscreen.h"
#include "df/world.h"
#include "modules/Buildings.h"
#include "modules/Gui.h"
#include "modules/Job.h"
#include "modules/MapCache.h"
#include "modules/Maps.h"
#include "modules/World.h"
#include "MiscUtils.h"
#include <queue>
using namespace DFHack;
using namespace RemoteFortressReader;
using namespace df::enums;
using namespace Gui;
using namespace df::global;
extern std::queue<interface_key::interface_key> keyQueue;
void GetBuildingSize(
int16_t type,
int16_t subtype,
int16_t custom,
int16_t &rad_x_low,
int16_t &rad_y_low,
int16_t &rad_x_high,
int16_t &rad_y_high
)
{
rad_x_low = 0;
rad_y_low = 0;
rad_x_high = 0;
rad_y_high = 0;
df::building_def* customBuilding = 0;
switch (type)
{
case building_type::FarmPlot:
case building_type::Bridge:
case building_type::RoadDirt:
case building_type::RoadPaved:
case building_type::Stockpile:
case building_type::Civzone:
case building_type::ScrewPump:
case building_type::Construction:
case building_type::AxleHorizontal:
case building_type::WaterWheel:
case building_type::Rollers:
{
bool widthOdd = world->building_width % 2;
rad_x_low = world->building_width / 2;
if(widthOdd)
rad_x_high = world->building_width / 2;
else
rad_x_high = (world->building_width / 2) - 1;
bool heightOdd = world->building_width % 2;
rad_y_low = world->building_height / 2;
if (widthOdd)
rad_y_high = world->building_height / 2;
else
rad_y_high = (world->building_height / 2) - 1;
}
return;
case building_type::Furnace:
if (subtype != furnace_type::Custom)
{
rad_x_low = rad_y_low = rad_x_high = rad_y_high = 1;
return;
}
customBuilding = world->raws.buildings.furnaces[custom];
break;
case building_type::TradeDepot:
case building_type::Shop:
rad_x_low = rad_y_low = rad_x_high = rad_y_high = 2;
return;
case building_type::Workshop:
switch (subtype)
{
case workshop_type::Carpenters:
case workshop_type::Farmers:
case workshop_type::Masons:
case workshop_type::Craftsdwarfs:
case workshop_type::Jewelers:
case workshop_type::MetalsmithsForge:
case workshop_type::MagmaForge:
case workshop_type::Bowyers:
case workshop_type::Mechanics:
case workshop_type::Butchers:
case workshop_type::Leatherworks:
case workshop_type::Tanners:
case workshop_type::Clothiers:
case workshop_type::Fishery:
case workshop_type::Still:
case workshop_type::Loom:
case workshop_type::Kitchen:
case workshop_type::Ashery:
case workshop_type::Dyers:
rad_x_low = rad_y_low = rad_x_high = rad_y_high = 1;
return;
case workshop_type::Siege:
case workshop_type::Kennels:
rad_x_low = rad_y_low = rad_x_high = rad_y_high = 2;
return;
case workshop_type::Custom:
customBuilding = world->raws.buildings.workshops[custom];
break;
default:
return;
}
break;
case building_type::SiegeEngine:
case building_type::Wagon:
case building_type::Windmill:
rad_x_low = rad_y_low = rad_x_high = rad_y_high = 1;
return;
default:
return;
}
if (customBuilding)
{
rad_x_low = customBuilding->workloc_x;
rad_y_low = customBuilding->workloc_y;
rad_x_high = customBuilding->dim_x - rad_x_low - 1;
rad_y_high = customBuilding->dim_y - rad_y_low - 1;
return;
}
}
command_result SendDigCommand(color_ostream &stream, const DigCommand *in)
{
MapExtras::MapCache mc;
for (int i = 0; i < in->locations_size(); i++)
{
auto pos = in->locations(i);
auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z()));
switch (in->designation())
{
case NO_DIG:
des.bits.dig = tile_dig_designation::No;
break;
case DEFAULT_DIG:
des.bits.dig = tile_dig_designation::Default;
break;
case UP_DOWN_STAIR_DIG:
des.bits.dig = tile_dig_designation::UpDownStair;
break;
case CHANNEL_DIG:
des.bits.dig = tile_dig_designation::Channel;
break;
case RAMP_DIG:
des.bits.dig = tile_dig_designation::Ramp;
break;
case DOWN_STAIR_DIG:
des.bits.dig = tile_dig_designation::DownStair;
break;
case UP_STAIR_DIG:
des.bits.dig = tile_dig_designation::UpStair;
break;
default:
break;
}
mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des);
#if DF_VERSION_INT >= 43005
//remove and job postings related.
for (df::job_list_link * listing = &(df::global::world->jobs.list); listing != NULL; listing = listing->next)
{
if (listing->item == NULL)
continue;
auto type = listing->item->job_type;
switch (type)
{
case job_type::CarveFortification:
case job_type::DetailWall:
case job_type::DetailFloor:
case job_type::Dig:
case job_type::CarveUpwardStaircase:
case job_type::CarveDownwardStaircase:
case job_type::CarveUpDownStaircase:
case job_type::CarveRamp:
case job_type::DigChannel:
case job_type::FellTree:
case job_type::GatherPlants:
case job_type::RemoveConstruction:
case job_type::CarveTrack:
{
if (listing->item->pos == DFCoord(pos.x(), pos.y(), pos.z()))
{
Job::removeJob(listing->item);
goto JOB_FOUND;
}
break;
}
default:
continue;
}
}
JOB_FOUND:
continue;
#endif
}
mc.WriteAll();
return CR_OK;
}
command_result SetPauseState(color_ostream &stream, const SingleBool *in)
{
DFHack::World::SetPauseState(in->value());
return CR_OK;
}
void CopyBuildMenu(DwarfControl::SidebarState * out)
{
auto menus = df::global::ui_sidebar_menus;
auto build_selector = df::global::ui_build_selector;
if (build_selector->building_type == -1)
for (size_t i = 0; i < menus->building.choices_visible.size(); i++)
{
auto menu_item = menus->building.choices_visible[i];
auto send_item = out->add_menu_items();
STRICT_VIRTUAL_CAST_VAR(building, df::interface_button_construction_building_selectorst, menu_item);
if (building)
{
auto send_bld = send_item->mutable_building_type();
send_bld->set_building_type(building->building_type);
send_bld->set_building_subtype(building->building_subtype);
send_bld->set_building_custom(building->custom_type);
send_item->set_existing_count(building->existing_count);
}
STRICT_VIRTUAL_CAST_VAR(sub_category, df::interface_button_construction_category_selectorst, menu_item);
if (sub_category)
{
send_item->set_build_category((DwarfControl::BuildCategory)sub_category->category_id);
}
}
else
{
auto send_selector = out->mutable_build_selector();
auto send_bld = send_selector->mutable_building_type();
send_bld->set_building_type(build_selector->building_type);
send_bld->set_building_subtype(build_selector->building_subtype);
send_bld->set_building_custom(build_selector->custom_type);
send_selector->set_stage((DwarfControl::BuildSelectorStage)build_selector->stage);
for (size_t i = 0; i < build_selector->errors.size(); i++)
{
if (build_selector->errors[i])
send_selector->add_errors(*build_selector->errors[i]);
}
for (size_t i = 0; i < build_selector->choices.size(); i++)
{
auto choice = build_selector->choices[i];
auto send_choice = send_selector->add_choices();
send_choice->set_distance(choice->distance);
std::string name;
choice->getName(&name);
send_choice->set_name(name);
send_choice->set_num_candidates(choice->getNumCandidates());
send_choice->set_used_count(choice->getUsedCount());
}
int16_t x_low, y_low, x_high, y_high;
GetBuildingSize(build_selector->building_type, build_selector->building_subtype, build_selector->custom_type, x_low, y_low, x_high, y_high);
send_selector->set_radius_x_low(x_low);
send_selector->set_radius_y_low(y_low);
send_selector->set_radius_x_high(x_high);
send_selector->set_radius_y_high(y_high);
if (build_selector->stage >= 1)
{
auto send_cursor = send_selector->mutable_cursor();
send_cursor->set_x(cursor->x);
send_cursor->set_y(cursor->y);
send_cursor->set_z(cursor->z);
}
for (int y = 0; y < (y_low + y_high + 1); y++)
for (int x = 0; x < (x_low + x_high + 1); x++)
{
send_selector->add_tiles(build_selector->tiles[x][y]);
}
}
}
command_result GetSideMenu(DFHack::color_ostream &stream, const dfproto::EmptyMessage *in, DwarfControl::SidebarState *out)
{
auto ui = df::global::ui;
out->set_mode((proto::enums::ui_sidebar_mode::ui_sidebar_mode)ui->main.mode);
auto mode = ui->main.mode;
switch (mode)
{
case ui_sidebar_mode::Default:
break;
case ui_sidebar_mode::Squads:
break;
case ui_sidebar_mode::DesignateMine:
break;
case ui_sidebar_mode::DesignateRemoveRamps:
break;
case ui_sidebar_mode::DesignateUpStair:
break;
case ui_sidebar_mode::DesignateDownStair:
break;
case ui_sidebar_mode::DesignateUpDownStair:
break;
case ui_sidebar_mode::DesignateUpRamp:
break;
case ui_sidebar_mode::DesignateChannel:
break;
case ui_sidebar_mode::DesignateGatherPlants:
break;
case ui_sidebar_mode::DesignateRemoveDesignation:
break;
case ui_sidebar_mode::DesignateSmooth:
break;
case ui_sidebar_mode::DesignateCarveTrack:
break;
case ui_sidebar_mode::DesignateEngrave:
break;
case ui_sidebar_mode::DesignateCarveFortification:
break;
case ui_sidebar_mode::Stockpiles:
break;
case ui_sidebar_mode::Build:
CopyBuildMenu(out);
break;
case ui_sidebar_mode::QueryBuilding:
break;
case ui_sidebar_mode::Orders:
break;
case ui_sidebar_mode::OrdersForbid:
break;
case ui_sidebar_mode::OrdersRefuse:
break;
case ui_sidebar_mode::OrdersWorkshop:
break;
case ui_sidebar_mode::OrdersZone:
break;
case ui_sidebar_mode::BuildingItems:
break;
case ui_sidebar_mode::ViewUnits:
break;
case ui_sidebar_mode::LookAround:
break;
case ui_sidebar_mode::DesignateItemsClaim:
break;
case ui_sidebar_mode::DesignateItemsForbid:
break;
case ui_sidebar_mode::DesignateItemsMelt:
break;
case ui_sidebar_mode::DesignateItemsUnmelt:
break;
case ui_sidebar_mode::DesignateItemsDump:
break;
case ui_sidebar_mode::DesignateItemsUndump:
break;
case ui_sidebar_mode::DesignateItemsHide:
break;
case ui_sidebar_mode::DesignateItemsUnhide:
break;
case ui_sidebar_mode::DesignateChopTrees:
break;
case ui_sidebar_mode::DesignateToggleEngravings:
break;
case ui_sidebar_mode::DesignateToggleMarker:
break;
case ui_sidebar_mode::Hotkeys:
break;
case ui_sidebar_mode::DesignateTrafficHigh:
break;
case ui_sidebar_mode::DesignateTrafficNormal:
break;
case ui_sidebar_mode::DesignateTrafficLow:
break;
case ui_sidebar_mode::DesignateTrafficRestricted:
break;
case ui_sidebar_mode::Zones:
break;
case ui_sidebar_mode::ZonesPenInfo:
break;
case ui_sidebar_mode::ZonesPitInfo:
break;
case ui_sidebar_mode::ZonesHospitalInfo:
break;
case ui_sidebar_mode::ZonesGatherInfo:
break;
case ui_sidebar_mode::DesignateRemoveConstruction:
break;
case ui_sidebar_mode::DepotAccess:
break;
case ui_sidebar_mode::NotesPoints:
break;
case ui_sidebar_mode::NotesRoutes:
break;
case ui_sidebar_mode::Burrows:
break;
case ui_sidebar_mode::Hauling:
break;
case ui_sidebar_mode::ArenaWeather:
break;
case ui_sidebar_mode::ArenaTrees:
break;
default:
break;
}
return CR_OK;
}
command_result SetSideMenu(DFHack::color_ostream &stream, const DwarfControl::SidebarCommand *in)
{
auto ui = df::global::ui;
if (in->has_mode())
{
ui_sidebar_mode::ui_sidebar_mode set_mode = (ui_sidebar_mode::ui_sidebar_mode)in->mode();
if (ui->main.mode != set_mode)
{
ui->main.mode = ui_sidebar_mode::Default;
switch (set_mode)
{
case ui_sidebar_mode::Build:
keyQueue.push(interface_key::D_BUILDING);
break;
default:
ui->main.mode = set_mode;
break;
}
}
}
switch (ui->main.mode)
{
case ui_sidebar_mode::Build:
if (in->has_action())
{
int index = 0;
if (in->has_menu_index())
index = in->menu_index();
if(ui_build_selector->building_type == -1)
df::global::ui_sidebar_menus->building.cursor = index;
if (ui_build_selector->stage == 2)
{
ui_build_selector->sel_index = index;
}
}
if (ui_build_selector->stage == 1)
{
if (in->has_selection_coord())
{
df::global::cursor->x = in->selection_coord().x();
df::global::cursor->y = in->selection_coord().y();
df::global::cursor->z = in->selection_coord().z();
getCurViewscreen()->feed_key(interface_key::CURSOR_LEFT);
getCurViewscreen()->feed_key(interface_key::CURSOR_RIGHT);
}
}
break;
default:
break;
}
auto viewScreen = getCurViewscreen();
if (in->has_action())
{
switch (in->action())
{
case DwarfControl::MenuSelect:
keyQueue.push(interface_key::SELECT);
break;
case DwarfControl::MenuCancel:
keyQueue.push(interface_key::LEAVESCREEN);
break;
default:
break;
}
}
return CR_OK;
}

@ -0,0 +1,15 @@
#ifndef DWARF_CONTROL_H
#define DWARF_CONTROL_H
#include "RemoteClient.h"
#include "RemoteFortressReader.pb.h"
#include "DwarfControl.pb.h"
DFHack::command_result SendDigCommand(DFHack::color_ostream &stream, const RemoteFortressReader::DigCommand *in);
DFHack::command_result SetPauseState(DFHack::color_ostream &stream, const RemoteFortressReader::SingleBool *in);
DFHack::command_result GetSideMenu(DFHack::color_ostream &stream, const dfproto::EmptyMessage *in, DwarfControl::SidebarState *out);
DFHack::command_result SetSideMenu(DFHack::color_ostream &stream, const DwarfControl::SidebarCommand *in);
#endif // !DWARF_CONTROL_H

@ -26,7 +26,13 @@
#include "df/item_statuest.h" #include "df/item_statuest.h"
#include "df/item_threadst.h" #include "df/item_threadst.h"
#include "df/item_toolst.h" #include "df/item_toolst.h"
#include "df/itemdef_armorst.h"
#include "df/itemdef_glovesst.h"
#include "df/itemdef_helmst.h"
#include "df/itemdef_instrumentst.h" #include "df/itemdef_instrumentst.h"
#include "df/itemdef_pantsst.h"
#include "df/itemdef_shieldst.h"
#include "df/itemdef_shoesst.h"
#include "df/itemdef_toolst.h" #include "df/itemdef_toolst.h"
#include "df/itemimprovement.h" #include "df/itemimprovement.h"
#include "df/itemimprovement_art_imagest.h" #include "df/itemimprovement_art_imagest.h"
@ -658,13 +664,70 @@ DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::
reg->set_pitch_range_max(instrument->registers[j]->pitch_range_max); reg->set_pitch_range_max(instrument->registers[j]->pitch_range_max);
} }
send_instrument->set_description(DF2UTF(instrument->description)); send_instrument->set_description(DF2UTF(instrument->description));
break;
} }
break;
case df::enums::item_type::TOOL: case df::enums::item_type::TOOL:
{ {
VIRTUAL_CAST_VAR(tool, df::itemdef_toolst, item); VIRTUAL_CAST_VAR(tool, df::itemdef_toolst, item);
mat_def->set_name(DF2UTF(tool->name)); mat_def->set_name(DF2UTF(tool->name));
} }
break;
case df::enums::item_type::ARMOR:
{
if (VIRTUAL_CAST_VAR(armor, df::itemdef_armorst, item))
{
mat_def->set_up_step(armor->ubstep);
mat_def->set_down_step(armor->lbstep);
mat_def->set_layer((ArmorLayer)armor->props.layer);
}
}
break;
case df::enums::item_type::SHOES:
{
if (VIRTUAL_CAST_VAR(armor, df::itemdef_shoesst, item))
{
mat_def->set_up_step(armor->upstep);
mat_def->set_down_step(10000);
mat_def->set_layer((ArmorLayer)armor->props.layer);
}
}
break;
case df::enums::item_type::SHIELD:
{
if (VIRTUAL_CAST_VAR(armor, df::itemdef_shieldst, item))
{
mat_def->set_up_step(armor->upstep);
mat_def->set_down_step(10000);
}
}
break;
case df::enums::item_type::HELM:
{
if (VIRTUAL_CAST_VAR(armor, df::itemdef_helmst, item))
{
mat_def->set_layer((ArmorLayer)armor->props.layer);
}
}
break;
case df::enums::item_type::GLOVES:
{
if (VIRTUAL_CAST_VAR(armor, df::itemdef_glovesst, item))
{
mat_def->set_up_step(armor->upstep);
mat_def->set_down_step(10000);
mat_def->set_layer((ArmorLayer)armor->props.layer);
}
}
break;
case df::enums::item_type::PANTS:
{
if (VIRTUAL_CAST_VAR(armor, df::itemdef_pantsst, item))
{
mat_def->set_down_step(armor->lbstep);
mat_def->set_layer((ArmorLayer)armor->props.layer);
}
}
break;
default: default:
break; break;
} }

@ -1,5 +1,5 @@
#include "df_version_int.h" #include "df_version_int.h"
#define RFR_VERSION "0.19.1" #define RFR_VERSION "0.20.2"
#include <cstdio> #include <cstdio>
#include <time.h> #include <time.h>
@ -63,7 +63,7 @@
#include "df/flow_guide_item_cloudst.h" #include "df/flow_guide_item_cloudst.h"
#include "df/graphic.h" #include "df/graphic.h"
#include "df/historical_figure.h" #include "df/historical_figure.h"
#include "df/identity.h"
#include "df/job.h" #include "df/job.h"
#include "df/job_type.h" #include "df/job_type.h"
#include "df/job_item.h" #include "df/job_item.h"
@ -92,7 +92,10 @@
#include "df/ui.h" #include "df/ui.h"
#include "df/unit.h" #include "df/unit.h"
#include "df/unit_inventory_item.h" #include "df/unit_inventory_item.h"
#include "df/unit_wound.h"
#include "df/viewscreen_choose_start_sitest.h" #include "df/viewscreen_choose_start_sitest.h"
#include "df/viewscreen_loadgamest.h"
#include "df/viewscreen_savegamest.h"
#include "df/vehicle.h" #include "df/vehicle.h"
#include "df/world.h" #include "df/world.h"
#include "df/world_data.h" #include "df/world_data.h"
@ -117,6 +120,7 @@
#include "adventure_control.h" #include "adventure_control.h"
#include "building_reader.h" #include "building_reader.h"
#include "dwarf_control.h"
#include "item_reader.h" #include "item_reader.h"
using namespace DFHack; using namespace DFHack;
@ -161,13 +165,11 @@ static command_result GetPlantRaws(color_ostream &stream, const EmptyMessage *in
static command_result GetPartialPlantRaws(color_ostream &stream, const ListRequest *in, PlantRawList *out); static command_result GetPartialPlantRaws(color_ostream &stream, const ListRequest *in, PlantRawList *out);
static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); static command_result CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out);
static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in); static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEvent *in);
static command_result SendDigCommand(color_ostream &stream, const DigCommand *in);
static command_result SetPauseState(color_ostream & stream, const SingleBool * in);
static command_result GetPauseState(color_ostream & stream, const EmptyMessage * in, SingleBool * out); static command_result GetPauseState(color_ostream & stream, const EmptyMessage * in, SingleBool * out);
static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out); static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::VersionInfo * out);
static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out); static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out);
static command_result GetLanguage(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Language * out); static command_result GetLanguage(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Language * out);
static command_result GetGameValidity(color_ostream &stream, const EmptyMessage * in, SingleBool *out);
void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos); void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos);
@ -208,54 +210,6 @@ command_result loadArtImageChunk(color_ostream &out, vector <string> & parameter
return CR_OK; return CR_OK;
} }
command_result dump_bp_mods(color_ostream &out, vector <string> & parameters)
{
remove("bp_appearance_mods.csv");
ofstream output;
output.open("bp_appearance_mods.csv");
output << "Race Index;Race;Caste;Bodypart Token;Bodypart Name;Tissue Layer;Modifier Type;Range\n";
for (size_t creatureIndex = 0; creatureIndex < world->raws.creatures.all.size(); creatureIndex++)
{
auto creatureRaw = world->raws.creatures.all[creatureIndex];
for (size_t casteIndex = 0; casteIndex < creatureRaw->caste.size(); casteIndex++)
{
df::caste_raw *casteRaw = creatureRaw->caste[casteIndex];
for (size_t partIndex = 0; partIndex < casteRaw->bp_appearance.part_idx.size(); partIndex++)
{
output << creatureIndex << ";";
output << creatureRaw->creature_id << ";";
output << casteRaw->caste_id << ";";
output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->token << ";";
output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->name_singular[0]->c_str() << ";";
int layer = casteRaw->bp_appearance.layer_idx[partIndex];
if (layer < 0)
output << "N/A;";
else
output << casteRaw->body_info.body_parts[casteRaw->bp_appearance.part_idx[partIndex]]->layers[layer]->layer_name << ";";
output << ENUM_KEY_STR(appearance_modifier_type, casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->type) << ";";
auto appMod = casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]];
#if DF_VERSION_INT > 34011
if (appMod->growth_rate > 0)
{
output << appMod->growth_min << " - " << appMod->growth_max << "\n";
}
else
#endif
{
output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[0] << " - ";
output << casteRaw->bp_appearance.modifiers[casteRaw->bp_appearance.modifier_idx[partIndex]]->ranges[6] << "\n";
}
}
}
}
output.close();
return CR_OK;
}
command_result RemoteFortressReader_version(color_ostream &out, vector<string> &parameters) command_result RemoteFortressReader_version(color_ostream &out, vector<string> &parameters)
{ {
out.print(RFR_VERSION); out.print(RFR_VERSION);
@ -267,16 +221,6 @@ DFHACK_PLUGIN_IS_ENABLED(enableUpdates);
// Mandatory init function. If you have some global state, create it here. // Mandatory init function. If you have some global state, create it here.
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands)
{ {
//// Fill the command list with your commands.
commands.push_back(PluginCommand(
"dump-bp-mods", "Dump bodypart mods for debugging",
dump_bp_mods, false, /* true means that the command can't be used from non-interactive user interface */
// Extended help string. Used by CR_WRONG_USAGE and the help command:
" This command does nothing at all.\n"
"Example:\n"
" isoworldremote\n"
" Does nothing.\n"
));
commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking.")); commands.push_back(PluginCommand("RemoteFortressReader_version", "List the loaded RemoteFortressReader version", RemoteFortressReader_version, false, "This is used for plugin version checking."));
commands.push_back(PluginCommand( commands.push_back(PluginCommand(
"load-art-image-chunk", "load-art-image-chunk",
@ -329,6 +273,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE); svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE);
svc->addFunction("MiscMoveCommand", MiscMoveCommand, SF_ALLOW_REMOTE); svc->addFunction("MiscMoveCommand", MiscMoveCommand, SF_ALLOW_REMOTE);
svc->addFunction("GetLanguage", GetLanguage, SF_ALLOW_REMOTE); svc->addFunction("GetLanguage", GetLanguage, SF_ALLOW_REMOTE);
svc->addFunction("GetSideMenu", GetSideMenu, SF_ALLOW_REMOTE);
svc->addFunction("SetSideMenu", SetSideMenu, SF_ALLOW_REMOTE);
svc->addFunction("GetGameValidity", GetGameValidity, SF_ALLOW_REMOTE);
return svc; return svc;
} }
@ -1506,7 +1453,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in
goto ItsAir; goto ItsAir;
} }
} }
ItsAir: ItsAir:
if (block->flows.size() > 0) if (block->flows.size() > 0)
nonAir = true; nonAir = true;
if (nonAir || firstBlock) if (nonAir || firstBlock)
@ -1556,9 +1503,9 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in
segment_passed = 0; segment_passed = 0;
// 'rotate' directions // 'rotate' directions
int buffer = di; int filename = di;
di = -dj; di = -dj;
dj = buffer; dj = filename;
// increase segment length if necessary // increase segment length if necessary
if (dj == 0) { if (dj == 0) {
@ -1702,6 +1649,24 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in,
return GetUnitListInside(stream, NULL, out); return GetUnitListInside(stream, NULL, out);
} }
float lerp(float a, float b, float f)
{
return a + f * (b - a);
}
void GetWounds(df::unit_wound * wound, UnitWound * send_wound)
{
for (size_t i = 0; i < wound->parts.size(); i++)
{
auto part = wound->parts[i];
auto send_part = send_wound->add_parts();
send_part->set_global_layer_idx(part->global_layer_idx);
send_part->set_body_part_id(part->body_part_id);
send_part->set_layer_idx(part->layer_idx);
}
send_wound->set_severed_part(wound->flags.bits.severed_part);
}
static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out) static command_result GetUnitListInside(color_ostream &stream, const BlockRequest *in, UnitList *out)
{ {
auto world = df::global::world; auto world = df::global::world;
@ -1724,6 +1689,25 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques
if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16) if (unit->pos.y < in->min_y() * 16 || unit->pos.y >= in->max_y() * 16)
continue; continue;
} }
using df::global::cur_year;
using df::global::cur_year_tick;
int year_ticks = 403200;
int birth_time = unit->birth_year * year_ticks + unit->birth_time;
int cur_time = *cur_year * year_ticks + *cur_year_tick;
if (unit->curse_year >= 0)
{
if (auto identity = Units::getIdentity(unit))
{
if (identity->histfig_id < 0)
birth_time = identity->birth_year * year_ticks + identity->birth_second;
}
}
send_unit->set_age(cur_time - birth_time);
ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color()); ConvertDfColor(Units::getProfessionColor(unit), send_unit->mutable_profession_color());
send_unit->set_flags1(unit->flags1.whole); send_unit->set_flags1(unit->flags1.whole);
send_unit->set_flags2(unit->flags2.whole); send_unit->set_flags2(unit->flags2.whole);
@ -1806,6 +1790,7 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques
auto inventory_item = unit->inventory[j]; auto inventory_item = unit->inventory[j];
auto sent_item = send_unit->add_inventory(); auto sent_item = send_unit->add_inventory();
sent_item->set_mode((InventoryMode)inventory_item->mode); sent_item->set_mode((InventoryMode)inventory_item->mode);
sent_item->set_body_part_id(inventory_item->body_part_id);
CopyItem(sent_item->mutable_item(), inventory_item->item); CopyItem(sent_item->mutable_item(), inventory_item->item);
} }
@ -1821,8 +1806,51 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques
send_unit->set_subpos_x(item->pos_x / 100000.0); send_unit->set_subpos_x(item->pos_x / 100000.0);
send_unit->set_subpos_y(item->pos_y / 100000.0); send_unit->set_subpos_y(item->pos_y / 100000.0);
send_unit->set_subpos_z(item->pos_z / 140000.0); send_unit->set_subpos_z(item->pos_z / 140000.0);
auto facing = send_unit->mutable_facing();
facing->set_x(item->speed_x);
facing->set_y(item->speed_x);
facing->set_z(item->speed_x);
break;
}
}
else
{
for (size_t i = 0; i < unit->actions.size(); i++)
{
auto action = unit->actions[i];
switch (action->type)
{
case unit_action_type::Move:
if (unit->path.path.x.size() > 0)
{
send_unit->set_subpos_x(lerp(0, unit->path.path.x[0] - unit->pos.x, (float)(action->data.move.timer_init - action->data.move.timer) / action->data.move.timer_init));
send_unit->set_subpos_y(lerp(0, unit->path.path.y[0] - unit->pos.y, (float)(action->data.move.timer_init - action->data.move.timer) / action->data.move.timer_init));
send_unit->set_subpos_z(lerp(0, unit->path.path.z[0] - unit->pos.z, (float)(action->data.move.timer_init - action->data.move.timer) / action->data.move.timer_init));
}
break;
case unit_action_type::Job:
{
auto facing = send_unit->mutable_facing();
facing->set_x(action->data.job.x - unit->pos.x);
facing->set_y(action->data.job.y - unit->pos.y);
facing->set_z(action->data.job.z - unit->pos.z);
}
default:
break;
}
}
if (unit->path.path.x.size() > 0)
{
auto facing = send_unit->mutable_facing();
facing->set_x(unit->path.path.x[0] - unit->pos.x);
facing->set_y(unit->path.path.y[0] - unit->pos.y);
facing->set_z(unit->path.path.z[0] - unit->pos.z);
} }
} }
for (size_t i = 0; i < unit->body.wounds.size(); i++)
{
GetWounds(unit->body.wounds[i], send_unit->add_wounds());
}
} }
return CR_OK; return CR_OK;
} }
@ -1847,6 +1875,14 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in,
} }
#endif #endif
auto dims = Gui::getDwarfmodeViewDims();
x += dims.map_x1;
y += dims.map_y1;
w = dims.map_x2 - dims.map_x1;
h = dims.map_y2 - dims.map_y1;
out->set_view_pos_x(x); out->set_view_pos_x(x);
out->set_view_pos_y(y); out->set_view_pos_y(y);
out->set_view_pos_z(z); out->set_view_pos_z(z);
@ -2793,6 +2829,10 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe
CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index); CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index);
} }
FOR_ENUM_ITEMS(creature_raw_flags, flag)
{
send_creature->add_flags(orig_creature->flags.is_set(flag));
}
} }
return CR_OK; return CR_OK;
@ -2893,94 +2933,26 @@ static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEve
return CR_OK; return CR_OK;
} }
static command_result SendDigCommand(color_ostream &stream, const DigCommand *in) static command_result GetPauseState(color_ostream &stream, const EmptyMessage *in, SingleBool *out)
{
MapExtras::MapCache mc;
for (int i = 0; i < in->locations_size(); i++)
{
auto pos = in->locations(i);
auto des = mc.designationAt(DFCoord(pos.x(), pos.y(), pos.z()));
switch (in->designation())
{
case NO_DIG:
des.bits.dig = tile_dig_designation::No;
break;
case DEFAULT_DIG:
des.bits.dig = tile_dig_designation::Default;
break;
case UP_DOWN_STAIR_DIG:
des.bits.dig = tile_dig_designation::UpDownStair;
break;
case CHANNEL_DIG:
des.bits.dig = tile_dig_designation::Channel;
break;
case RAMP_DIG:
des.bits.dig = tile_dig_designation::Ramp;
break;
case DOWN_STAIR_DIG:
des.bits.dig = tile_dig_designation::DownStair;
break;
case UP_STAIR_DIG:
des.bits.dig = tile_dig_designation::UpStair;
break;
default:
break;
}
mc.setDesignationAt(DFCoord(pos.x(), pos.y(), pos.z()), des);
#if DF_VERSION_INT >= 43005
//remove and job postings related.
for (df::job_list_link * listing = &(world->jobs.list); listing != NULL; listing = listing->next)
{
if (listing->item == NULL)
continue;
auto type = listing->item->job_type;
switch (type)
{
case df::enums::job_type::CarveFortification:
case df::enums::job_type::DetailWall:
case df::enums::job_type::DetailFloor:
case df::enums::job_type::Dig:
case df::enums::job_type::CarveUpwardStaircase:
case df::enums::job_type::CarveDownwardStaircase:
case df::enums::job_type::CarveUpDownStaircase:
case df::enums::job_type::CarveRamp:
case df::enums::job_type::DigChannel:
case df::enums::job_type::FellTree:
case df::enums::job_type::GatherPlants:
case df::enums::job_type::RemoveConstruction:
case df::enums::job_type::CarveTrack:
{
if (listing->item->pos == DFCoord(pos.x(), pos.y(), pos.z()))
{
Job::removeJob(listing->item);
goto JOB_FOUND;
}
break;
}
default:
continue;
}
}
JOB_FOUND:
continue;
#endif
}
mc.WriteAll();
return CR_OK;
}
static command_result SetPauseState(color_ostream &stream, const SingleBool *in)
{ {
DFHack::World::SetPauseState(in->value()); out->set_value(World::ReadPauseState());
return CR_OK; return CR_OK;
} }
static command_result GetPauseState(color_ostream &stream, const EmptyMessage *in, SingleBool *out) static command_result GetGameValidity(color_ostream &stream, const EmptyMessage * in, SingleBool *out)
{ {
out->set_value(World::ReadPauseState()); auto viewScreen = Gui::getCurViewscreen();
if (strict_virtual_cast<df::viewscreen_loadgamest>(viewScreen))
{
out->set_value(false);
return CR_OK;
}
else if (strict_virtual_cast<df::viewscreen_savegamest>(viewScreen))
{
out->set_value(false);
return CR_OK;
}
out->set_value(true);
return CR_OK; return CR_OK;
} }

@ -134,7 +134,7 @@ module DFHack
# does not include ghosts / wildlife # does not include ghosts / wildlife
def unit_ishostile(u) def unit_ishostile(u)
# return true if u.flags3.ghostly and not u.flags1.inactive # return true if u.flags3.ghostly and not u.flags1.inactive
return unless unit_category(u) == :Others return false unless unit_category(u) == :Others
case unit_other_category(u) case unit_other_category(u)
when :Berserk, :Undead, :Hostile, :Invader, :Underworld when :Berserk, :Undead, :Hostile, :Invader, :Underworld
@ -152,6 +152,8 @@ module DFHack
case unit_checkdiplomacy_hf_ent(histfig, group) case unit_checkdiplomacy_hf_ent(histfig, group)
when 4, 5 when 4, 5
true true
else
false
end end
elsif diplo = u.civ_tg.unknown1b.diplomacy.binsearch(df.ui.group_id, :group_id) elsif diplo = u.civ_tg.unknown1b.diplomacy.binsearch(df.ui.group_id, :group_id)

@ -6,6 +6,9 @@
#include <Export.h> #include <Export.h>
#include <PluginManager.h> #include <PluginManager.h>
// If you need to save data per-world:
//#include "modules/Persistence.h"
// DF data structure definition headers // DF data structure definition headers
#include "DataDefs.h" #include "DataDefs.h"
//#include "df/world.h" //#include "df/world.h"
@ -86,6 +89,25 @@ DFhackCExport command_result plugin_onupdate ( color_ostream &out )
} }
*/ */
// If you need to save or load world-specific data, define these functions.
// plugin_save_data is called when the game might be about to save the world,
// and plugin_load_data is called whenever a new world is loaded. If the plugin
// is loaded or unloaded while a world is active, plugin_save_data or
// plugin_load_data will be called immediately.
/*
DFhackCExport command_result plugin_save_data (color_ostream &out)
{
// Call functions in the Persistence module here.
return CR_OK;
}
DFhackCExport command_result plugin_load_data (color_ostream &out)
{
// Call functions in the Persistence module here.
return CR_OK;
}
*/
// A command! It sits around and looks pretty. And it's nice and friendly. // A command! It sits around and looks pretty. And it's nice and friendly.
command_result skeleton (color_ostream &out, std::vector <std::string> & parameters) command_result skeleton (color_ostream &out, std::vector <std::string> & parameters)
{ {

@ -1 +1 @@
Subproject commit 4a0f63e044d02532c948d67780dfd128fbd6d043 Subproject commit 03e96477ca84e42c87db93bd2d781c73687795a8

@ -1 +1 @@
Subproject commit a242a7cd81ae7046146f86c3103360449e1d9ca8 Subproject commit 8ef283377c9830fb932ea888d89b551873af36cf

@ -67,9 +67,9 @@ for plugin_name in actual:
io = methods[m] io = methods[m]
if m in expected[plugin_name]: if m in expected[plugin_name]:
if expected[plugin_name][m] != io: if expected[plugin_name][m] != io:
wrong.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) wrong.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1])
else: else:
missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) missing.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1])
if len(missing) > 0: if len(missing) > 0:
print('Incomplete documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Add the following lines:') print('Incomplete documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Add the following lines:')
@ -98,7 +98,7 @@ for plugin_name in expected:
for m in methods: for m in methods:
io = methods[m] io = methods[m]
if m not in actual[plugin_name]: if m not in actual[plugin_name]:
missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) missing.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1])
if len(missing) > 0: if len(missing) > 0:
print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Remove the following lines:') print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Remove the following lines:')

@ -10,31 +10,37 @@ cd "$(dirname "$0")"
echo "DF_VERSION: $DF_VERSION" echo "DF_VERSION: $DF_VERSION"
echo "DF_FOLDER: $DF_FOLDER" echo "DF_FOLDER: $DF_FOLDER"
mkdir -p "$DF_FOLDER" mkdir -p "$DF_FOLDER"
cd "$DF_FOLDER" # back out of df_linux
cd "$DF_FOLDER/.."
if [ -f receipt ]; then if [ -f receipt ]; then
if [ "$selfmd5" != "$(cat receipt)" ]; then if [ "$selfmd5" != "$(cat receipt)" ]; then
echo "download-df.sh changed; removing DF" echo "download-df.sh changed; removing DF"
rm receipt
else else
echo "Already downloaded $DF_VERSION" echo "Already downloaded $DF_VERSION"
exit 0
fi fi
fi fi
rm -rif "$tardest" df_linux if [ ! -f receipt ]; then
rm -f "$tardest"
minor=$(echo "$DF_VERSION" | cut -d. -f2)
patch=$(echo "$DF_VERSION" | cut -d. -f3)
url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2"
echo Downloading
wget "$url" -O "$tardest"
fi
minor=$(echo "$DF_VERSION" | cut -d. -f2) rm -rf df_linux
patch=$(echo "$DF_VERSION" | cut -d. -f3) mkdir df_linux
url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2"
echo Downloading
wget "$url" -O "$tardest"
echo Extracting echo Extracting
tar xf "$tardest" --strip-components=1 tar xf "$tardest" --strip-components=1 -C df_linux
echo Changing settings echo Changing settings
echo '' >> "$DF_FOLDER/data/init/init.txt" echo '' >> "df_linux/data/init/init.txt"
echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/data/init/init.txt" echo '[PRINT_MODE:TEXT]' >> "df_linux/data/init/init.txt"
echo '[SOUND:NO]' >> "$DF_FOLDER/data/init/init.txt" echo '[SOUND:NO]' >> "df_linux/data/init/init.txt"
echo Done echo Done
echo "$selfmd5" > receipt echo "$selfmd5" > receipt
ls

@ -1,4 +1,4 @@
#!/bin/sh #!/bin/sh
cd "$(dirname "$0")" cd "$(dirname "$0")"
cd .. cd ..
grep DF_VERSION CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/' grep -i 'set(DF_VERSION' CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/'

@ -14,6 +14,9 @@ os.chdir(sys.argv[1])
if os.path.exists(test_stage): if os.path.exists(test_stage):
os.remove(test_stage) os.remove(test_stage)
print(os.getcwd())
print(os.listdir('.'))
tries = 0 tries = 0
while True: while True:
tries += 1 tries += 1
@ -33,3 +36,5 @@ while True:
process = subprocess.Popen([dfhack], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) process = subprocess.Popen([dfhack], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
process.communicate() process.communicate()
if process.returncode != 0:
print('DF exited with ' + repr(process.returncode))