diff --git a/.gitignore b/.gitignore index 6a447a40e..0a614b082 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,10 @@ build/install_manifest.txt build/_CPack_Packages build/dfhack-*.zip build/dfhack-*.bz2 +build/*ninja* +build/compile_commands.json +build/dfhack_setarch.txt +build/ImportExecutables.cmake # Python binding binaries *.pyc @@ -58,5 +62,8 @@ tags /build/win32/DF_PATH.txt /.vs +# CLion +.idea + # custom plugins /plugins/CMakeLists.custom.txt diff --git a/.travis.yml b/.travis.yml index c90276540..98534a11a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -28,7 +28,7 @@ matrix: - g++-4.8 before_install: - 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]" - sh travis/build-lua.sh - sh travis/download-df.sh @@ -56,9 +56,7 @@ script: - python travis/check-rpc.py "$DF_FOLDER/dfhack-rpc.txt" before_cache: - cat "$DF_FOLDER/stderr.log" -- rm -rf "$DF_FOLDER/hack" -- rm -rf "$DF_FOLDER/dfhack-config" -- rm -f "$DF_FOLDER"/*.log +- rm -rf "$DF_FOLDER" notifications: email: false irc: diff --git a/CMakeLists.txt b/CMakeLists.txt index 689618637..9a9a03caf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) 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) execute_process(COMMAND ${COMPILER_PATH} -dumpversion OUTPUT_VARIABLE GCC_VERSION_OUT) string(STRIP "${GCC_VERSION_OUT}" GCC_VERSION_OUT) @@ -290,6 +296,7 @@ if(WIN32) endif() endif() +option(EXTERNAL_LIBSTDCXX "macOS only: Avoid installing the DFHack-provided libstdc++." OFF) if(APPLE) # libstdc++ (GCC 4.8.5 for OS X 10.6) # fixes crash-on-unwind bug in DF's libstdc++ @@ -340,9 +347,10 @@ if(APPLE) endif() endif() - install(PROGRAMS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib - DESTINATION ./hack/) - + if(NOT EXTERNAL_LIBSTDCXX) + install(PROGRAMS ${LIBSTDCXX_DOWNLOAD_DIR}/libstdc++.6.dylib + DESTINATION ./hack/) + endif() endif() #### expose depends #### diff --git a/README.md b/README.md index 9ea1ccb04..fd95e1e5e 100644 --- a/README.md +++ b/README.md @@ -3,15 +3,13 @@ [![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) [![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 and plugins implementing a wide variety of useful functions and tools. The full documentation [is available online here](https://dfhack.readthedocs.org), from the README.html page in the DFHack distribution, or as raw text in the `./docs` folder. -If you're an end-user, modder, or interested in contributing to DFHack - +If you're an end-user, modder, or interested in contributing to DFHack - go read those docs. If that's unclear or you need more help, try diff --git a/depends/lua/CMakeLists.txt b/depends/lua/CMakeLists.txt index 7dcae8068..fd3a59c08 100644 --- a/depends/lua/CMakeLists.txt +++ b/depends/lua/CMakeLists.txt @@ -95,6 +95,12 @@ LIST(APPEND SRC_LIBLUA ${HDR_LIBLUA}) ADD_LIBRARY ( lua SHARED ${SRC_LIBLUA} ) 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 LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) diff --git a/depends/lua/include/dfhack_llimits.h b/depends/lua/include/dfhack_llimits.h new file mode 100644 index 000000000..f8ca51819 --- /dev/null +++ b/depends/lua/include/dfhack_llimits.h @@ -0,0 +1,61 @@ +/** +Copyright © 2018 Pauli + +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 +#else +#include +#endif + +#include + +/*! \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 diff --git a/dfhack.init-example b/dfhack.init-example index bdf035bd3..d8cf832c7 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -262,6 +262,9 @@ enable \ # enable mouse controls and sand indicator in embark screen embark-tools enable sticky sand mouse +# enable option to enter embark assistant +enable embark-assistant + ########### # Scripts # ########### diff --git a/docs/Authors.rst b/docs/Authors.rst index ba3a00b35..5c3aae2fd 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -51,6 +51,7 @@ gchristopher gchristopher grubsteak grubsteak Harlan Playford playfordh Hayati Ayguen hayguen +Herwig Hochleitner bendlas IndigoFenix James Logsdon jlogsdon Japa JapaMala @@ -68,6 +69,7 @@ Kris Parker kaypy Kromtec Kromtec Kurik Amudnil Lethosor lethosor +LordGolias LordGolias Mason11987 Mason11987 Matt Regul mattregul Matthew Cline @@ -86,6 +88,7 @@ Milo Christiansen milochristiansen MithrilTuxedo MithrilTuxedo mizipzor mizipzor moversti moversti +napagokc napagokc Neil Little nmlittle Nick Rart nickrart comestible Nikolay Amiantov abbradar diff --git a/docs/Compile.rst b/docs/Compile.rst index 30fab6154..f30775a03 100644 --- a/docs/Compile.rst +++ b/docs/Compile.rst @@ -68,6 +68,10 @@ and more, please see `contributing-code`. 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 --------- @@ -86,6 +90,12 @@ much slower than Ninja builds. generator cannot be changed after ``cmake`` has been run without creating a 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 `_ or through a package manager. See + the sections below for more platform-specific directions for installing CMake. + 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 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 `_ or +through your package manager, if possible. + 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 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 +* 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. diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 3eb053e73..a4130096b 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -3920,6 +3920,22 @@ A class with all the tcp functionality. 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 diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 5fc30fa65..2b78fd700 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -561,16 +561,19 @@ directly to the main dwarf mode screen. Professions ----------- -The manipulator plugin supports saving Professions: a named set of Labors labors that can be -quickly applied to one or multiple Dwarves. +The manipulator plugin supports saving professions: a named set of labors that can be +quickly applied to one or multiple dwarves. -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 -Name has been set. +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 +name has been set. -To apply a Profession either highlight a single Dwarf, or select multiple with :kbd:`x`, and press -:kbd:`p` to select the Profession to apply. All labors for the selected Dwarves will be reset to -the labors of the chosen Profession. +To apply a profession, either highlight a single dwarf or select multiple with +:kbd:`x`, and press :kbd:`p` to select the profession to apply. All labors for +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 .. _search-plugin: @@ -2845,4 +2848,5 @@ in the `lua-api` file under `lua-plugins`: * `eventful` * `building-hacks` * `luasocket` +* `map-render` * `cxxrandom` diff --git a/docs/changelog.txt b/docs/changelog.txt index 4db4072f0..47dcaa58b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,6 +37,75 @@ changelog.txt uses a syntax similar to RST, with a few special sequences: ================================================================================ # 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 ## New Plugins diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index a9255ae00..deec2f703 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -131,6 +131,7 @@ include/modules/Maps.h include/modules/Materials.h include/modules/Notes.h include/modules/Once.h +include/modules/Persistence.h include/modules/Random.h include/modules/Renderer.h include/modules/Screen.h @@ -157,6 +158,7 @@ modules/Maps.cpp modules/Materials.cpp modules/Notes.cpp modules/Once.cpp +modules/Persistence.cpp modules/Random.cpp modules/Renderer.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) 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( OUTPUT ${CODEGEN_OUT} + BYPRODUCTS ${GENERATED_HDRS} COMMAND ${PERL_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/xml/codegen.pl ${CMAKE_CURRENT_SOURCE_DIR}/xml ${CMAKE_CURRENT_SOURCE_DIR}/include/df diff --git a/library/Console-posix.cpp b/library/Console-posix.cpp index da786aaac..696672861 100644 --- a/library/Console-posix.cpp +++ b/library/Console-posix.cpp @@ -480,7 +480,7 @@ namespace DFHack { char seq[64]; int cols = get_columns(); - int plen = prompt.size(); + int plen = prompt.size() % cols; int len = raw_buffer.size(); int begin = 0; int cooked_cursor = raw_cursor; @@ -493,7 +493,15 @@ namespace DFHack } if (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 */ snprintf(seq,64,"\x1b[1G"); if (::write(STDIN_FILENO,seq,strlen(seq)) == -1) return; diff --git a/library/Core.cpp b/library/Core.cpp index c406fb6c0..380f37965 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -54,6 +54,7 @@ using namespace std; #include "modules/World.h" #include "modules/Graphic.h" #include "modules/Windows.h" +#include "modules/Persistence.h" #include "RemoteServer.h" #include "RemoteTools.h" #include "LuaTools.h" @@ -135,6 +136,9 @@ struct Core::Private { std::thread iothread; std::thread hotkeythread; + + bool last_autosave_request{false}; + bool was_load_save{false}; }; 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) { + CoreSuspender lock; if (!df::global::world || !df::global::ui || !df::global::gview) { out.printerr("Key globals are missing, skipping loading dfhack.init.\n"); @@ -1528,6 +1533,7 @@ Core::Core() : HotkeyMutex{}, HotkeyCond{}, alias_mutex{}, + started{false}, misc_data_mutex{}, CoreSuspendMutex{}, CoreWakeup{}, @@ -1537,7 +1543,7 @@ Core::Core() : // init the console. This must be always the first step! plug_mgr = 0; errorstate = false; - started = false; + vinfo = 0; memset(&(s_mods), 0, sizeof(s_mods)); // set up hotkey capture @@ -1547,7 +1553,6 @@ Core::Core() : last_pause_state = false; top_viewscreen = NULL; screen_window = NULL; - server = NULL; color_ostream::log_errors_to_stderr = true; @@ -1765,6 +1770,8 @@ bool Core::Init() // create plugin manager plug_mgr = new PluginManager(this); plug_mgr->init(); + cerr << "Starting the TCP listener.\n"; + auto listen = ServerMain::listen(RemoteClient::GetDefaultPort()); IODATA *temp = new IODATA; temp->core = this; temp->plug_mgr = plug_mgr; @@ -1789,9 +1796,7 @@ bool Core::Init() started = true; modstate = 0; - cerr << "Starting the TCP listener.\n"; - server = new ServerMain(); - if (!server->listen(RemoteClient::GetDefaultPort())) + if (!listen.get()) cerr << "TCP listen failed.\n"; if (df::global::ui_sidebar_menus) @@ -1968,6 +1973,13 @@ void Core::doUpdate(color_ostream &out, bool first_update) strict_virtual_cast(screen) || strict_virtual_cast(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(screen))) + { + doSaveData(out); + } + // detect if the game was loaded or unloaded in the meantime void *new_wdata = 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_local_map_ptr = new_mapdata; - World::ClearPersistentCache(); - // and if the world is going away, we report the map change first if(had_map) onStateChange(out, SC_MAP_UNLOADED); @@ -2007,7 +2017,6 @@ void Core::doUpdate(color_ostream &out, bool first_update) if (isMapLoaded() != had_map) { - World::ClearPersistentCache(); 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 onUpdate(out); + d->last_autosave_request = df::global::ui->main.autosave_request; + d->was_load_save = is_load_save; + out << std::flush; } @@ -2270,6 +2282,27 @@ void Core::onStateChange(color_ostream &out, state_change_event event) Lua::Core::onStateChange(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 ) @@ -2294,6 +2327,8 @@ int Core::Shutdown ( void ) HotkeyCond.notify_one(); } + ServerMain::block(); + d->hotkeythread.join(); d->iothread.join(); diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index b17f8e214..05db0521d 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -197,6 +197,9 @@ Plugin::Plugin(Core * core, const std::string & path, plugin_rpcconnect = 0; plugin_enable = 0; plugin_is_enabled = 0; + plugin_save_data = 0; + plugin_load_data = 0; + plugin_eval_ruby = 0; state = PS_UNLOADED; access = new RefLock(); } @@ -348,6 +351,8 @@ bool Plugin::load(color_ostream &con) plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect"); plugin_enable = (command_result (*)(color_ostream &,bool)) LookupPlugin(plug, "plugin_enable"); 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"); index_lua(plug); plugin_lib = plug; @@ -359,6 +364,8 @@ bool Plugin::load(color_ostream &con) parent->registerCommands(this); if ((plugin_onupdate || plugin_enable) && !plugin_is_enabled) 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); fflush(stderr); return true; @@ -403,6 +410,8 @@ bool Plugin::unload(color_ostream &con) // enter suspend CoreSuspender suspend; 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 command_result cr = CR_OK; if(plugin_shutdown) @@ -410,6 +419,8 @@ bool Plugin::unload(color_ostream &con) // cleanup... plugin_is_enabled = 0; plugin_onupdate = 0; + plugin_save_data = 0; + plugin_load_data = 0; reset_lua(); parent->unregisterCommands(this); commands.clear(); @@ -570,6 +581,32 @@ command_result Plugin::on_state_change(color_ostream &out, state_change_event ev 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 *rv = NULL; @@ -1014,6 +1051,28 @@ void PluginManager::unregisterCommands( Plugin * p ) 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) { MUTEX_GUARD(plugin_mutex); diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 86ef9f40c..523058d55 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -57,12 +57,11 @@ POSSIBILITY OF SUCH DAMAGE. #include #include +#include #include "json/json.h" -#include "tinythread.h" using namespace DFHack; -using namespace tthread; using dfproto::CoreTextNotification; using dfproto::CoreTextFragment; @@ -72,6 +71,30 @@ bool readFullBuffer(CSimpleSocket *socket, void *buf, int size); bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, 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 lock; + BlockGuard() : + lock{ServerMain::access_} + { + if (ServerMain::blocked_) + throw BlockedException{}; + } + }; +} RPCService::RPCService() { @@ -134,9 +157,6 @@ ServerConnection::ServerConnection(CActiveSocket *socket) core_service = new CoreService(); core_service->finalize(this, &functions); - - thread = new tthread::thread(threadFn, (void*)this); - thread->detach(); } ServerConnection::~ServerConnection() @@ -144,7 +164,6 @@ ServerConnection::~ServerConnection() in_error = true; socket->Close(); delete socket; - delete thread; for (auto it = plugin_services.begin(); it != plugin_services.end(); ++it) 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; - - me->threadFn(); - - delete me; + std::thread{[](CActiveSocket* socket) { + try { + ServerConnection(socket).threadFn(); + } catch (BlockedException e) { + } + }, socket}.detach(); } void ServerConnection::threadFn() @@ -292,6 +312,7 @@ void ServerConnection::threadFn() // Find and call the function int in_size = header.size; + BlockGuard lock; ServerFunctionBase *fn = vector_get(functions, header.id); MessageLite *reply = NULL; @@ -378,25 +399,21 @@ void ServerConnection::threadFn() std::cerr << "Shutting down client connection." << endl; } -ServerMain::ServerMain() -{ - socket = new CPassiveSocket(); - thread = NULL; -} +namespace { + + struct ServerMainImpl : public ServerMain { + CPassiveSocket socket; + static void threadFn(std::promise promise, int port); + ServerMainImpl(std::promise promise, int port); + ~ServerMainImpl(); + }; -ServerMain::~ServerMain() -{ - socket->Close(); - delete socket; - delete thread; } -bool ServerMain::listen(int port) +ServerMainImpl::ServerMainImpl(std::promise promise, int port) : + socket{} { - if (thread) - return true; - - socket->Initialize(); + socket.Initialize(); 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; - if (allow_remote) - { - if (!socket->Listen(NULL, port)) - return false; - } - else - { - if (!socket->Listen("127.0.0.1", port)) - return false; + const char* addr = allow_remote ? NULL : "127.0.0.1"; + if (!socket.Listen(addr, port)) { + promise.set_value(false); + return; } + promise.set_value(true); +} + +ServerMainImpl::~ServerMainImpl() +{ + socket.Close(); +} - thread = new tthread::thread(threadFn, this); - thread->detach(); - return true; +std::future ServerMain::listen(int port) +{ + std::promise 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 promise, int port) { - ServerMain *me = (ServerMain*)arg; - CActiveSocket *client; + ServerMainImpl server{std::move(promise), port}; - while ((client = me->socket->Accept()) != NULL) - { - new ServerConnection(client); + CActiveSocket *client = nullptr; + + 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 lock{access_}; + blocked_ = true; +} diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp index abe34f34d..af00e4c04 100644 --- a/library/dfhack-run.cpp +++ b/library/dfhack-run.cpp @@ -63,7 +63,20 @@ int main (int argc, char *argv[]) if (argc <= 1) { - fprintf(stderr, "Usage: dfhack-run [args...]\n"); + fprintf(stderr, "Usage: dfhack-run [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; } diff --git a/library/include/Core.h b/library/include/Core.h index 0fec2774d..1648ae113 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -222,6 +222,8 @@ namespace DFHack void onUpdate(color_ostream &out); void onStateChange(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 void operator=(Core const&); // Don't implement @@ -283,7 +285,7 @@ namespace DFHack df::viewscreen *top_viewscreen; bool last_pause_state; // Very important! - bool started; + std::atomic started; // Additional state change scripts std::vector state_change_scripts; @@ -308,7 +310,6 @@ namespace DFHack friend class CoreSuspenderBase; friend struct CoreSuspendClaimMain; friend struct CoreSuspendReleaseMain; - ServerMain *server; }; class CoreSuspenderBase : protected std::unique_lock { diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index e473d7acb..8f73c8ec2 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -148,6 +148,8 @@ namespace DFHack ~Plugin(); command_result on_update(color_ostream &out); 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); public: enum plugin_state @@ -238,6 +240,8 @@ namespace DFHack command_result (*plugin_enable)(color_ostream &, bool); RPCService* (*plugin_rpcconnect)(color_ostream &); 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 { @@ -251,6 +255,8 @@ namespace DFHack void OnStateChange(color_ostream &out, state_change_event event); void registerCommands( Plugin * p ); void unregisterCommands( Plugin * p ); + void doSaveData(color_ostream &out); + void doLoadData(color_ostream &out); // PUBLIC METHODS public: // list names of all plugins present in hack/plugins diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index 85ac463fb..a7e0ad8d5 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -28,6 +28,8 @@ distribution. #include "RemoteClient.h" #include "Core.h" +#include + class CPassiveSocket; class CActiveSocket; class CSimpleSocket; @@ -233,26 +235,25 @@ namespace DFHack CoreService *core_service; std::map plugin_services; - tthread::thread *thread; - static void threadFn(void *); void threadFn(); + ServerConnection(CActiveSocket* socket); + ~ServerConnection(); public: - ServerConnection(CActiveSocket *socket); - ~ServerConnection(); + + static void Accepted(CActiveSocket* socket); ServerFunctionBase *findFunction(color_ostream &out, const std::string &plugin, const std::string &name); }; class ServerMain { - CPassiveSocket *socket; + static std::mutex access_; + static bool blocked_; + friend struct BlockGuard; - tthread::thread *thread; - static void threadFn(void *); public: - ServerMain(); - ~ServerMain(); - bool listen(int port); + static std::future listen(int port); + static void block(); }; } diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index 0c41bf7f6..f33abf4e0 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -36,6 +36,7 @@ distribution. #include "BitArray.h" #include "modules/Materials.h" +#include "df/biome_type.h" #include "df/block_flags.h" #include "df/feature_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 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 diff --git a/library/include/modules/Persistence.h b/library/include/modules/Persistence.h new file mode 100644 index 000000000..11f959698 --- /dev/null +++ b/library/include/modules/Persistence.h @@ -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 +#include +#include +#include + +#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 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 + uint8_t (&pdata(size_t off))[N] + { + ensure_data(off, N); + typedef uint8_t array[N]; + return *(array *)(val().data() + off); + } + template + 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 + const uint8_t (&cpdata(size_t off) const)[N] + { + return pdata(off); + } + + static const size_t int7_size = 1; + uint8_t get_uint7(size_t off) const + { + auto p = pdata(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(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(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(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 &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 &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 &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 &vec, const std::string &key); + +#if defined(__GNUC__) && __GNUC__ < 5 + // file stream move constructors are missing in libstdc++ before version 5. + template + 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 && s) : T(), file(s.file) + { + if (!file.empty()) + { + T::open(file.c_str()); + } + } + }; +#define FSTREAM(x) gcc_4_fstream_shim +#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 diff --git a/library/include/modules/World.h b/library/include/modules/World.h index 67c4082df..d5f21ec08 100644 --- a/library/include/modules/World.h +++ b/library/include/modules/World.h @@ -33,6 +33,7 @@ distribution. #include "Export.h" #include "Module.h" +#include "modules/Persistence.h" #include #include "DataDefs.h" @@ -60,81 +61,6 @@ namespace DFHack }; 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 * \ingroup grp_modules @@ -179,8 +105,6 @@ namespace DFHack // Deletes the item; returns true if success. 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 bool deletePersistentTilemask(const PersistentDataItem &item, df::map_block *block); } diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index fa2d41410..886e0c4df 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -226,7 +226,7 @@ MOVEMENT_KEYS = { 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] if info 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 end -local function get_hotkey_target(key) +function get_hotkey_target(key) local hk = HOTKEY_KEYS[key] if hk and hk.cmd == df.ui_hotkey.T_cmd.Zoom then return xyz2pos(hk.x, hk.y, hk.z) diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 54f314db8..361ada0f1 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -46,6 +46,7 @@ using namespace std; #include "modules/MapCache.h" #include "modules/Maps.h" +#include "df/biome_type.h" #include "df/block_burrow.h" #include "df/block_burrow_link.h" #include "df/block_square_event_grassst.h" @@ -711,3 +712,393 @@ bool Maps::canStepBetween(df::coord pos1, df::coord pos2) 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 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(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 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); +} + diff --git a/library/modules/Persistence.cpp b/library/modules/Persistence.cpp new file mode 100644 index 000000000..fa866897f --- /dev/null +++ b/library/modules/Persistence.cpp @@ -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 +#include +#include + +#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> legacy_data; +static std::multimap index_cache; + +struct Persistence::LegacyData +{ + const std::string key; + std::string str_value; + std::array 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(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(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(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 &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 &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 &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 +#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 diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 3be400515..12b339e95 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -24,6 +24,7 @@ distribution. #include "Internal.h" +#include #include #include #include @@ -56,10 +57,6 @@ using namespace df::enums; using df::global::world; -static int next_persistent_id = 0; -static std::multimap persistent_index; -typedef std::pair T_persistent_item; - bool World::ReadPauseState() { return DF_GLOBAL_VALUE(pause_state, false); @@ -183,117 +180,14 @@ bool World::isLegends(df::game_type t) 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 *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 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 &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) { - if (!BuildPersistentCache() || key.empty()) - return PersistentDataItem(); - - std::vector &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); + return Persistence::addItem(key); } PersistentDataItem World::GetPersistentData(const std::string &key) { - if (!BuildPersistentCache()) - return PersistentDataItem(); - - auto it = persistent_index.find(key); - if (it != persistent_index.end()) - return GetPersistentData(it->second); - - return PersistentDataItem(); + return Persistence::getByKey(key); } PersistentDataItem World::GetPersistentData(int entry_id) @@ -301,96 +195,45 @@ PersistentDataItem World::GetPersistentData(int entry_id) if (entry_id < 100) return PersistentDataItem(); - auto hfig = df::historical_figure::find(-entry_id); - if (hfig && hfig->name.has_name) - return dataFromHFig(hfig); - - return PersistentDataItem(); + return Persistence::getByIndex(size_t(entry_id - 100)); } PersistentDataItem World::GetPersistentData(const std::string &key, bool *added) { - if (added) *added = false; - - PersistentDataItem rv = GetPersistentData(key); - - if (!rv.isValid()) - { - if (added) *added = true; - rv = AddPersistentData(key); - } + bool temp = false; + if (!added) + added = &temp; - return rv; + return Persistence::getByKey(key, added); } void World::GetPersistentData(std::vector *vec, const std::string &key, bool prefix) { - vec->clear(); - - if (!BuildPersistentCache()) - return; - - auto eqrange = persistent_index.equal_range(key); - - if (prefix) + if (prefix && key.empty()) { - if (key.empty()) + Persistence::getAll(*vec); + } + else if (prefix) + { + std::string min = key; + if (min.back() != '/') { - eqrange.first = persistent_index.begin(); - eqrange.second = persistent_index.end(); + min.push_back('/'); } - else - { - std::string bound = key; - if (bound[bound.size()-1] != '/') - bound += "/"; - eqrange.first = persistent_index.lower_bound(bound); + std::string max = min; + ++max.back(); - bound[bound.size()-1]++; - eqrange.second = persistent_index.lower_bound(bound); - } + Persistence::getAllByKeyRange(*vec, min, max); } - - for (auto it = eqrange.first; it != eqrange.second; ++it) + else { - auto hfig = df::historical_figure::find(-it->second); - if (hfig && hfig->name.has_name) - vec->push_back(dataFromHFig(hfig)); + Persistence::getAllByKey(*vec, key); } } bool World::DeletePersistentData(const PersistentDataItem &item) { - int id = item.raw_id(); - if (id > -100) - return false; - if (!BuildPersistentCache()) - return false; - - std::vector &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; + return Persistence::deleteItem(item); } df::tile_bitmask *World::getPersistentTilemask(const PersistentDataItem &item, df::map_block *block, bool create) diff --git a/library/xml b/library/xml index 44215836d..4388fbfb8 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 44215836d5b57c3722b126aaf481f652385f3a23 +Subproject commit 4388fbfb8f51be41777406c6e7c518f738c195c7 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 284922cc1..fbca6bca9 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -136,6 +136,7 @@ if (BUILD_SUPPORTED) DFHACK_PLUGIN(liquids liquids.cpp Brushes.h LINK_LIBRARIES lua) DFHACK_PLUGIN(luasocket luasocket.cpp LINK_LIBRARIES clsocket lua dfhack-tinythread) DFHACK_PLUGIN(manipulator manipulator.cpp) + DFHACK_PLUGIN(map-render map-render.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(misery misery.cpp) DFHACK_PLUGIN(mode mode.cpp) DFHACK_PLUGIN(mousequery mousequery.cpp) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 4468140f7..3aefde807 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -111,6 +111,7 @@ MACRO(DFHACK_PLUGIN) 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_SRCS}) + LIST(APPEND PLUGIN_SOURCES ${PLUGIN_PROTOS}) IF(UNIX) SET(PLUGIN_COMPILE_FLAGS "${PLUGIN_COMPILE_FLAGS} -include Export.h") diff --git a/plugins/devel/kittens.cpp b/plugins/devel/kittens.cpp index 26a2655e8..b978fab7f 100644 --- a/plugins/devel/kittens.cpp +++ b/plugins/devel/kittens.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -20,6 +21,11 @@ #include "df/creature_raw.h" #include "df/world.h" +// for MSVC alignas(64) issues +#ifdef WIN32 +#define _DISABLE_EXTENDED_ALIGNED_STORAGE +#endif + using std::vector; using std::string; using namespace DFHack; @@ -220,7 +226,7 @@ struct ClearMem : public ConnectedBase { }; struct Connected : public ClearMem { - using Sig = Signal; + using Sig = Signal; std::array con; Sig signal; weak other; @@ -245,7 +251,7 @@ struct Connected : public ClearMem { // Externally synchronized object destruction is only safe to this // connect. con[pos] = b->signal.connect( - [this](int) { + [this](uint32_t) { uint32_t old = callee.fetch_add(1); assert(old != 0xDEDEDEDE); std::this_thread::sleep_for(delay); @@ -272,7 +278,7 @@ struct Connected : public ClearMem { if (!sig) return; con[pos] = sig->connect(b, - [this](int) { + [this](uint32_t) { uint32_t old = callee.fetch_add(1); assert(old != 0xDEDEDEDE); std::this_thread::sleep_for(delay); @@ -284,7 +290,7 @@ struct Connected : public ClearMem { out = &o; count = c; con[pos] = b->signal.connect(a, - [this](int) { + [this](uint32_t) { uint32_t old = callee.fetch_add(1); assert(old != 0xDEDEDEDE); std::this_thread::sleep_for(delay); @@ -345,13 +351,13 @@ command_result sharedsignal (color_ostream &out, vector & parameters) TRACE(command, out) << "Thread " << c->id << " started." << std::endl; weak ref = c; 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; c.reset(); while((c = ref.lock())) { ++c->caller; - c->signal(c->caller); + c->signal(std::move(c->caller)); c.reset(); std::this_thread::sleep_for(delay*25); } @@ -363,7 +369,7 @@ command_result sharedsignal (color_ostream &out, vector & parameters) } TRACE(command, out) << "running " << std::endl; for (;external->caller < external->count; ++external->caller) { - external->signal(external->caller); + external->signal(std::move(external->caller)); external->reconnect(1); } TRACE(command, out) << "join " << std::endl; diff --git a/plugins/embark-assistant/CMakeLists.txt b/plugins/embark-assistant/CMakeLists.txt index 917d2ec5a..dfc1a294f 100644 --- a/plugins/embark-assistant/CMakeLists.txt +++ b/plugins/embark-assistant/CMakeLists.txt @@ -1,7 +1,6 @@ PROJECT (embark-assistant) # A list of source files SET(PROJECT_SRCS - biome_type.cpp embark-assistant.cpp finder_ui.cpp help_ui.cpp @@ -12,7 +11,6 @@ SET(PROJECT_SRCS ) # A list of headers SET(PROJECT_HDRS - biome_type.h defs.h embark-assistant.h finder_ui.h diff --git a/plugins/embark-assistant/biome_type.cpp b/plugins/embark-assistant/biome_type.cpp deleted file mode 100644 index 01bc0044a..000000000 --- a/plugins/embark-assistant/biome_type.cpp +++ /dev/null @@ -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 - -#include "DataDefs.h" -#include -#include -#include -#include - -#include "biome_type.h" - -/***************************************************************************** -Local functions forward declaration -*****************************************************************************/ -std::pair 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 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 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(is_possible_tropical_area_by_latitude, - is_tropical_area_by_latitude - ); -} - -//----------------------------------------------------------------------------// -// Utility function -// -//----------------------------------------------------------------------------// -std::pair 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(is_possible_tropical_area_by_latitude, - is_tropical_area_by_latitude - ); -} - -//----------------------------------------------------------------------------// -// Utility function -// -//----------------------------------------------------------------------------// -std::pair 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(is_possible_tropical_area_by_latitude, - is_tropical_area_by_latitude - ); -} - -//----------------------------------------------------------------------------// -// Utility function -// -//----------------------------------------------------------------------------// -std::pair 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(is_possible_tropical_area_by_latitude, - is_tropical_area_by_latitude - ); -} - -//----------------------------------------------------------------------------// -// Utility function -// -//----------------------------------------------------------------------------// -std::pair 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(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 - } -} \ No newline at end of file diff --git a/plugins/embark-assistant/biome_type.h b/plugins/embark-assistant/biome_type.h deleted file mode 100644 index 9ea562749..000000000 --- a/plugins/embark-assistant/biome_type.h +++ /dev/null @@ -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); diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h index f7a9ed680..9d1c8e888 100644 --- a/plugins/embark-assistant/defs.h +++ b/plugins/embark-assistant/defs.h @@ -3,6 +3,7 @@ #include #include #include +#include "df/world_region_type.h" using namespace std; using std::array; @@ -55,7 +56,7 @@ namespace embark_assist { uint16_t coal_count = 0; uint8_t min_region_soil = 10; uint8_t max_region_soil = 0; - bool waterfall = false; + uint8_t max_waterfall = 0; river_sizes river_size; 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 @@ -82,6 +83,16 @@ namespace embark_assist { std::vector metals; std::vector economics; std::vector 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 { @@ -107,16 +118,22 @@ namespace embark_assist { }; struct site_infos { + bool incursions_processed; bool aquifer; bool aquifer_full; uint8_t min_soil; uint8_t max_soil; bool flat; - bool waterfall; + uint8_t max_waterfall; bool clay; bool sand; bool flux; bool coal; + bool blood_rain; + bool permanent_syndrome_rain; + bool temporary_syndrome_rain; + bool reanimating; + bool thralling; std::vector metals; std::vector economics; std::vector minerals; @@ -252,7 +269,7 @@ namespace embark_assist { aquifer_ranges aquifer; river_ranges min_river; river_ranges max_river; - yes_no_ranges waterfall; + int8_t min_waterfall; // N/A(-1), Absent, 1-9 yes_no_ranges flat; present_absent_ranges clay; present_absent_ranges sand; diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp index cfd44a30b..465435a38 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -1,11 +1,13 @@ +#include + #include "Core.h" -#include -#include -#include +#include "Console.h" +#include "Export.h" +#include "PluginManager.h" -#include -#include -#include +#include "modules/Gui.h" +#include "modules/Screen.h" +#include "../uicommon.h" #include "DataDefs.h" #include "df/coord2d.h" @@ -27,6 +29,7 @@ #include "survey.h" DFHACK_PLUGIN("embark-assistant"); +DFHACK_PLUGIN_IS_ENABLED(is_enabled); using namespace DFHack; using namespace df::enums; @@ -62,7 +65,10 @@ namespace embark_assist { &state->survey_results, &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::survey::survey_region_sites(&state->region_sites); @@ -131,11 +137,43 @@ command_result embark_assistant (color_ostream &out, std::vector & //======================================================================================= +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 *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 &commands) { commands.push_back(PluginCommand( "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: " 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" @@ -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) { switch (event) { @@ -224,7 +278,7 @@ command_result embark_assistant(color_ostream &out, std::vector & // Find the end of the normal inorganic definitions. embark_assist::main::state->max_inorganic = 0; 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 @@ -253,7 +307,7 @@ command_result embark_assistant(color_ostream &out, std::vector & 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].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; for (uint8_t l = 1; l < 10; l++) { @@ -291,7 +345,7 @@ command_result embark_assistant(color_ostream &out, std::vector & 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_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); return CR_OK; diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp index 5c7cc9046..80c61dc6e 100644 --- a/plugins/embark-assistant/finder_ui.cpp +++ b/plugins/embark-assistant/finder_ui.cpp @@ -39,7 +39,7 @@ namespace embark_assist { aquifer, min_river, max_river, - waterfall, + min_waterfall, flat, clay, sand, @@ -246,7 +246,7 @@ namespace embark_assist { fclose(infile); - // Checking done. No do the work. + // Checking done. Now do the work. infile = fopen(profile_file_name, "r"); i = first_fields; @@ -452,9 +452,23 @@ namespace embark_assist { break; - case fields::waterfall: - case fields::flat: + case fields::min_waterfall: + 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::flat: { embark_assist::defs::yes_no_ranges k = embark_assist::defs::yes_no_ranges::NA; while (true) { @@ -955,8 +969,8 @@ namespace embark_assist { state->finder_list.push_back({ "Max River", static_cast(i) }); break; - case fields::waterfall: - state->finder_list.push_back({ "Waterfall", static_cast(i) }); + case fields::min_waterfall: + state->finder_list.push_back({ "Min Waterfall Drop", static_cast(i) }); break; case fields::flat: @@ -1184,9 +1198,8 @@ namespace embark_assist { static_cast(state->ui[static_cast(i)]->current_value); break; - case fields::waterfall: - finder.waterfall = - static_cast(state->ui[static_cast(i)]->current_value); + case fields::min_waterfall: + finder.min_waterfall = state->ui[static_cast(i)]->current_value; break; case fields::flat: diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp index e92ae67c0..ef1bc2b39 100644 --- a/plugins/embark-assistant/help_ui.cpp +++ b/plugins/embark-assistant/help_ui.cpp @@ -18,7 +18,8 @@ namespace embark_assist{ Intro, General, Finder, - Caveats + Caveats_1, + Caveats_2 }; class ViewscreenHelpUi : public dfhack_viewscreen @@ -56,10 +57,14 @@ namespace embark_assist{ break; case pages::Finder: - current_page = pages::Caveats; + current_page = pages::Caveats_1; break; - case pages::Caveats: + case pages::Caveats_1: + current_page = pages::Caveats_2; + break; + + case pages::Caveats_2: current_page = pages::Intro; break; } @@ -67,7 +72,7 @@ namespace embark_assist{ else if (input->count(df::interface_key::SEC_CHANGETAB)) { switch (current_page) { case pages::Intro: - current_page = pages::Caveats; + current_page = pages::Caveats_2; break; case pages::General: @@ -78,8 +83,12 @@ namespace embark_assist{ current_page = pages::General; break; - case pages::Caveats: - current_page = pages::Intro; + case pages::Caveats_1: + current_page = pages::Finder; + break; + + case pages::Caveats_2: + current_page = pages::Caveats_1; break; } } @@ -135,6 +144,8 @@ namespace embark_assist{ 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("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; @@ -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 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("(It can be noted that the DFHack Sand indicator does take these elements into"); - help_text.push_back("account)."); + help_text.push_back("Thirdly, it takes 'intrusions', i.e. small sections of neighboring tiles'"); + 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("Sand, if present"); - help_text.push_back("Clay, if present"); - help_text.push_back("Min and Max soil depth in the embark rectangle."); - help_text.push_back("Flat indicator if all the tiles in the embark have the same elevation."); - help_text.push_back("Aquifer indicator, color coded as blue if all tiles have an aquifer and light"); - help_text.push_back("blue if some, but not all, tiles have one."); - help_text.push_back("Waterfall, if the embark has river elevation differences."); - help_text.push_back("Flux, if present"); - help_text.push_back("A list of all metals present in the embark."); + help_text.push_back("Incompl. Survey if all intrusions couldn't be examined because that requires"); + help_text.push_back("info from neighboring world tiles that haven't been surveyed."); + help_text.push_back("Sand, if present, including through intrusions."); + help_text.push_back("Clay, if present, including thorugh intrusions."); + help_text.push_back("Min and Max soil depth in the embark rectangle, including intrusions."); + help_text.push_back("Flat indicator if all the tiles and intrusions have the same elevation."); + help_text.push_back("Aquifer indicator: Part(ial) or Full, when present, including intrusions."); + help_text.push_back("Waterfall and largest Z level drop if the river has elevation differences"); + 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("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("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"); @@ -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("any at all."); 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("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("conditions. Note that the minimum temperature is held for only 10 ticks"); 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("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("Reanimation packages thralling and reanimation into a single search"); 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)."); break; - case pages::Caveats: - Screen::drawBorder(" Embark Assistant Help/Info Caveats Page "); + case pages::Caveats_1: + 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("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("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("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("Caveats & technical stuff:"); 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(" 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(" However, this is a bug only if it causes the search to fail to find"); - help_text.push_back(" actual existing matches."); + help_text.push_back(" Note that the first search can miss a fair number of matches for"); + 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(" 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"); @@ -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(" with only marginal changes by the author. References can be found in"); 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("- 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("- 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."); @@ -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(" 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("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; } @@ -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()); break; - case pages::Caveats: + case pages::Caveats_1: + break; + + case pages::Caveats_2: break; } dfhack_viewscreen::render(); diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp index e724e565f..71841f84e 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -13,6 +13,7 @@ #include "df/world_data.h" #include "df/world_raws.h" #include "df/world_region.h" +#include "df/world_region_details.h" #include "df/world_region_type.h" #include "matcher.h" @@ -25,33 +26,22 @@ namespace embark_assist { //======================================================================================= - //======================================================================================= - - bool embark_match(embark_assist::defs::world_tile_data *survey_results, - embark_assist::defs::mid_level_tiles *mlt, - uint16_t x, - uint16_t y, - uint16_t start_x, - uint16_t start_y, - embark_assist::defs::finders *finder) { - -// color_ostream_proxy out(Core::getInstance().getConsole()); - df::world_data *world_data = world->world_data; + struct matcher_info { bool savagery_found[3] = { false, false, false }; bool evilness_found[3] = { false, false, false }; - uint16_t aquifer_count = 0; + bool aquifer_absence_found = false; + bool aquifer_presence_found = false; bool river_found = false; - bool waterfall_found = false; - uint16_t river_elevation = 0xffff; - uint16_t elevation = mlt->at(start_x).at(start_y).elevation; + uint8_t max_waterfall = 0; + uint16_t elevation; bool clay_found = false; bool sand_found = false; bool flux_found = false; bool coal_found = false; uint8_t max_soil = 0; bool uneven = false; - int16_t min_temperature = survey_results->at(x).at(y).min_temperature[mlt->at(start_x).at(start_y).biome_offset]; - int16_t max_temperature = survey_results->at(x).at(y).max_temperature[mlt->at(start_x).at(start_y).biome_offset]; + int16_t min_temperature; + int16_t max_temperature; bool blood_rain_found = false; bool permanent_syndrome_rain_found = false; bool temporary_syndrome_rain_found = false; @@ -62,15 +52,449 @@ namespace embark_assist { bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; bool region_types[ENUM_LAST_ITEM(world_region_type) + 1]; uint8_t biome_count; - bool metal_1 = finder->metal_1 == -1; - bool metal_2 = finder->metal_2 == -1; - bool metal_3 = finder->metal_3 == -1; - bool economic_1 = finder->economic_1 == -1; - bool economic_2 = finder->economic_2 == -1; - bool economic_3 = finder->economic_3 == -1; - bool mineral_1 = finder->mineral_1 == -1; - bool mineral_2 = finder->mineral_2 == -1; - bool mineral_3 = finder->mineral_3 == -1; + bool metal_1; + bool metal_2; + bool metal_3; + bool economic_1; + bool economic_2; + bool economic_3; + bool mineral_1; + bool mineral_2; + bool mineral_3; + }; + + //======================================================================================= + + void process_embark_incursion(matcher_info *result, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tile *mlt, // Note this is a single tile, as opposed to most usages of this variable name. + embark_assist::defs::finders *finder, + int16_t elevation, + uint16_t x, + uint16_t y, + bool *failed_match) { + + df::world_data *world_data = world->world_data; + + // Savagery & Evilness + { + result->savagery_found[mlt->savagery_level] = true; + result->evilness_found[mlt->evilness_level] = true; + + embark_assist::defs::evil_savagery_ranges l = embark_assist::defs::evil_savagery_ranges::Low; + while (true) { + if (mlt->savagery_level == static_cast(l)) { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Absent) { + *failed_match = true; + return; + } + } + else { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::All) { + *failed_match = true; + return; + } + } + + if (mlt->evilness_level == static_cast(l)) { + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Absent) { + *failed_match = true; + return; + } + } + else { + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::All) { + *failed_match = true; + return; + } + } + + if (l == embark_assist::defs::evil_savagery_ranges::High) break; + l = static_cast (static_cast(l) + 1); + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; + + case embark_assist::defs::aquifer_ranges::All: + if (!mlt->aquifer) { + *failed_match = true; + return; + } + break; + + case embark_assist::defs::aquifer_ranges::Present: + case embark_assist::defs::aquifer_ranges::Partial: + case embark_assist::defs::aquifer_ranges::Not_All: + if (mlt->aquifer) { + result->aquifer_presence_found = true; + } + else { + result->aquifer_absence_found = true; + } + + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (mlt->aquifer) { + *failed_match = true; + return; + } + break; + } + + // River & Waterfall. N/A for incursions. + + // Flat + if (finder->flat == embark_assist::defs::yes_no_ranges::Yes && + result->elevation != mlt->elevation) { + *failed_match = true; + return; + } + + // Clay + if (mlt->clay) { + if (finder->clay == embark_assist::defs::present_absent_ranges::Absent) { + *failed_match = true; + return; + } + result->clay_found = true; + } + + // Sand + if (mlt->sand) { + if (finder->sand == embark_assist::defs::present_absent_ranges::Absent) { + *failed_match = true; + return; + } + result->sand_found = true; + } + + // Flux. N/A for intrusions. + // Coal. N/A for intrusions + + // Min Soil + if (finder->soil_min != embark_assist::defs::soil_ranges::NA && + mlt->soil_depth < static_cast(finder->soil_min) && + finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::All) { + *failed_match = true; + return; + } + + if (result->max_soil < mlt->soil_depth) { + result->max_soil = mlt->soil_depth; + } + + // Max Soil + if (finder->soil_max != embark_assist::defs::soil_ranges::NA && + mlt->soil_depth > static_cast(finder->soil_max)) { + *failed_match = true; + return; + } + + // Freezing + if (result->min_temperature > survey_results->at(x).at(y).min_temperature[mlt->biome_offset]) { + result->min_temperature = survey_results->at(x).at(y).min_temperature[mlt->biome_offset]; + } + + if (result->max_temperature < survey_results->at(x).at(y).max_temperature[mlt->biome_offset]) { + result->max_temperature = survey_results->at(x).at(y).max_temperature[mlt->biome_offset]; + } + + if (result->min_temperature <= 0 && + finder->freezing == embark_assist::defs::freezing_ranges::Never) { + *failed_match = true; + return; + } + + if (result->max_temperature > 0 && + finder->freezing == embark_assist::defs::freezing_ranges::Permanent) { + *failed_match = true; + return; + } + + // Blood Rain + if (survey_results->at(x).at(y).blood_rain[mlt->biome_offset]) { + if (finder->blood_rain == embark_assist::defs::yes_no_ranges::No) { + *failed_match = true; + return; + } + result->blood_rain_found = true; + } + + // Syndrome Rain, Permanent + if (survey_results->at(x).at(y).permanent_syndrome_rain[mlt->biome_offset]) { + if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Temporary || + finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Not_Permanent || + finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::None) { + *failed_match = true; + return; + } + result->permanent_syndrome_rain_found = true; + } + + // Syndrome Rain, Temporary + if (survey_results->at(x).at(y).temporary_syndrome_rain[mlt->biome_offset]) { + if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Permanent || + finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::None) { + *failed_match = true; + return; + } + result->temporary_syndrome_rain_found = true; + } + + // Reanmation + if (survey_results->at(x).at(y).reanimating[mlt->biome_offset]) { + if (finder->reanimation == embark_assist::defs::reanimation_ranges::Thralling || + finder->reanimation == embark_assist::defs::reanimation_ranges::None) { + *failed_match = true; + return; + } + result->reanimation_found = true; + } + + // Thralling + if (survey_results->at(x).at(y).thralling[mlt->biome_offset]) { + if (finder->reanimation == embark_assist::defs::reanimation_ranges::Reanimation || + finder->reanimation == embark_assist::defs::reanimation_ranges::Not_Thralling || + finder->reanimation == embark_assist::defs::reanimation_ranges::None) { + *failed_match = true; + return; + } + result->thralling_found = true; + } + + // Spires. N/A for intrusions + // Magma. N/A for intrusions + // Biomes + + result->biomes[survey_results->at(x).at(y).biome[mlt->biome_offset]] = true; + + // Region Type + result->region_types[world_data->regions[survey_results->at(x).at(y).biome_index[mlt->biome_offset]]->type] = true; + + // Metals. N/A for intrusions + // Economics. N/A for intrusions + } + + //======================================================================================= + + + void process_embark_incursion_mid_level_tile(uint8_t from_direction, + matcher_info *result, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::finders *finder, + uint16_t x, + uint16_t y, + uint8_t i, + uint8_t k, + bool *failed_match) { + int8_t fetch_i = i; + int8_t fetch_k = k; + int16_t fetch_x = x; + int16_t fetch_y = y; + df::world_data *world_data = world->world_data; + + // Logic can be implemented with modulo and division, but that's harder to read. + switch (from_direction) { + case 0: + fetch_i = i - 1; + fetch_k = k - 1; + break; + + case 1: + fetch_k = k - 1; + break; + + case 2: + fetch_i = i + 1; + fetch_k = k - 1; + break; + + case 3: + fetch_i = i - 1; + break; + + case 4: + return; // Own tile provides the data, so there's no incursion. + break; + + case 5: + fetch_i = i + 1; + break; + + case 6: + fetch_i = i - 1; + fetch_k = k + 1; + break; + + case 7: + fetch_k = k + 1; + break; + + case 8: + fetch_i = i + 1; + fetch_k = k + 1; + } + + if (fetch_i < 0) { + fetch_x = x - 1; + } + else if (fetch_i > 15) { + fetch_x = x + 1; + } + + if (fetch_k < 0) { + fetch_y = y - 1; + } + else if (fetch_k > 15) { + fetch_y = y + 1; + } + + if (fetch_x < 0 || + fetch_x == world_data->world_width || + fetch_y < 0 || + fetch_y == world_data->world_height) { + return; // We're at the world edge, so no incursions from the outside. + } + + if (!&survey_results->at(fetch_x).at(fetch_y).surveyed) { + *failed_match = true; + return; + } + + if (fetch_k < 0) { + if (fetch_i < 0) { + process_embark_incursion(result, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).south_row[15], + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + else if (fetch_i > 15) { + process_embark_incursion(result, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).south_row[0], + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + else { + process_embark_incursion(result, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).south_row[i], + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + } + else if (fetch_k > 15) { + if (fetch_i < 0) { + process_embark_incursion(result, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).north_row[15], + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + else if (fetch_i > 15) { + process_embark_incursion(result, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).north_row[0], + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + else { + process_embark_incursion(result, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).north_row[i], + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + } + else { + if (fetch_i < 0) { + process_embark_incursion(result, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).east_column[k], + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + else if (fetch_i > 15) { + process_embark_incursion(result, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).west_column[k], + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + else { + process_embark_incursion(result, + survey_results, + &mlt->at(fetch_i).at(fetch_k), + finder, + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y, + failed_match); + } + } + } + + //======================================================================================= + + bool embark_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + uint16_t x, + uint16_t y, + uint16_t start_x, + uint16_t start_y, + embark_assist::defs::finders *finder) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + matcher_info result; + result.elevation = mlt->at(start_x).at(start_y).elevation; + result.min_temperature = survey_results->at(x).at(y).min_temperature[mlt->at(start_x).at(start_y).biome_offset]; + result.max_temperature = survey_results->at(x).at(y).max_temperature[mlt->at(start_x).at(start_y).biome_offset]; + result.metal_1 = finder->metal_1 == -1; + result.metal_2 = finder->metal_2 == -1; + result.metal_3 = finder->metal_3 == -1; + result.economic_1 = finder->economic_1 == -1; + result.economic_2 = finder->economic_2 == -1; + result.economic_3 = finder->economic_3 == -1; + result.mineral_1 = finder->mineral_1 == -1; + result.mineral_2 = finder->mineral_2 == -1; + result.mineral_3 = finder->mineral_3 == -1; + bool failed_match = false; const uint16_t embark_size = finder->x_dim * finder->y_dim; @@ -79,18 +503,18 @@ namespace embark_assist { finder->biome_1 != -1 || finder->biome_2 != -1 || finder->biome_3 != -1) { - for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) biomes[i] = false; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) result.biomes[i] = false; } - for (uint8_t i = 0; i <= ENUM_LAST_ITEM(world_region_type); i++) region_types[i] = false; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(world_region_type); i++) result.region_types[i] = false; for (uint16_t i = start_x; i < start_x + finder->x_dim; i++) { for (uint16_t k = start_y; k < start_y + finder->y_dim; k++) { // Savagery & Evilness { - savagery_found[mlt->at(i).at(k).savagery_level] = true; - evilness_found[mlt->at(i).at(k).evilness_level] = true; + result.savagery_found[mlt->at(i).at(k).savagery_level] = true; + result.evilness_found[mlt->at(i).at(k).evilness_level] = true; embark_assist::defs::evil_savagery_ranges l = embark_assist::defs::evil_savagery_ranges::Low; while (true) { @@ -124,17 +548,23 @@ namespace embark_assist { case embark_assist::defs::aquifer_ranges::All: if (!mlt->at(i).at(k).aquifer) return false; - aquifer_count++; + result.aquifer_presence_found = true; break; case embark_assist::defs::aquifer_ranges::Present: case embark_assist::defs::aquifer_ranges::Partial: case embark_assist::defs::aquifer_ranges::Not_All: - if (mlt->at(i).at(k).aquifer) aquifer_count++; + if (mlt->at(i).at(k).aquifer) { + result.aquifer_presence_found = true; + } + else { + result.aquifer_absence_found = true; + } break; case embark_assist::defs::aquifer_ranges::Absent: if (mlt->at(i).at(k).aquifer) return false; + result.aquifer_presence_found = true; break; } @@ -144,42 +574,53 @@ namespace embark_assist { if (finder->max_river != embark_assist::defs::river_ranges::NA && finder->max_river < static_cast(survey_results->at(x).at(y).river_size)) return false; - if (river_found && river_elevation != mlt->at(i).at(k).river_elevation) { - if (finder->waterfall == embark_assist::defs::yes_no_ranges::No) return false; - waterfall_found = true; + if (i < start_x + finder->x_dim - 2 && + mlt->at(i + 1).at(k).river_present && + abs(mlt->at(i).at(k).river_elevation - mlt->at(i + 1).at(k).river_elevation) > result.max_waterfall) { + if (finder->min_waterfall == 0) return false; // 0 = Absent + result.max_waterfall = + abs(mlt->at(i).at(k).river_elevation - mlt->at(i + 1).at(k).river_elevation); } - river_found = true; - river_elevation = mlt->at(i).at(k).river_elevation; + + if (k < start_y + finder->y_dim - 2 && + mlt->at(i).at(k + 1).river_present && + abs(mlt->at(i).at(k).river_elevation - mlt->at(i).at(k + 1).river_elevation) > result.max_waterfall) { + if (finder->min_waterfall == 0) return false; // 0 = Absent + result.max_waterfall = + abs(mlt->at(i).at(k).river_elevation - mlt->at(i).at(k + 1).river_elevation); + } + + result.river_found = true; } // Flat if (finder->flat == embark_assist::defs::yes_no_ranges::Yes && - elevation != mlt->at(i).at(k).elevation) return false; + result.elevation != mlt->at(i).at(k).elevation) return false; - if (elevation != mlt->at(i).at(k).elevation) uneven = true; + if (result.elevation != mlt->at(i).at(k).elevation) result.uneven = true; // Clay if (mlt->at(i).at(k).clay) { if (finder->clay == embark_assist::defs::present_absent_ranges::Absent) return false; - clay_found = true; + result.clay_found = true; } // Sand if (mlt->at(i).at(k).sand) { if (finder->sand == embark_assist::defs::present_absent_ranges::Absent) return false; - sand_found = true; + result.sand_found = true; } // Flux if (mlt->at(i).at(k).flux) { if (finder->flux == embark_assist::defs::present_absent_ranges::Absent) return false; - flux_found = true; + result.flux_found = true; } // Coal if (mlt->at(i).at(k).coal) { if (finder->coal == embark_assist::defs::present_absent_ranges::Absent) return false; - coal_found = true; + result.coal_found = true; } // Min Soil @@ -187,8 +628,8 @@ namespace embark_assist { mlt->at(i).at(k).soil_depth < static_cast(finder->soil_min) && finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::All) return false; - if (max_soil < mlt->at(i).at(k).soil_depth) { - max_soil = mlt->at(i).at(k).soil_depth; + if (result.max_soil < mlt->at(i).at(k).soil_depth) { + result.max_soil = mlt->at(i).at(k).soil_depth; } // Max Soil @@ -196,24 +637,24 @@ namespace embark_assist { mlt->at(i).at(k).soil_depth > static_cast(finder->soil_max)) return false; // Freezing - if (min_temperature > survey_results->at(x).at(y).min_temperature[mlt->at(i).at(k).biome_offset]) { - min_temperature = survey_results->at(x).at(y).min_temperature[mlt->at(i).at(k).biome_offset]; + if (result.min_temperature > survey_results->at(x).at(y).min_temperature[mlt->at(i).at(k).biome_offset]) { + result.min_temperature = survey_results->at(x).at(y).min_temperature[mlt->at(i).at(k).biome_offset]; } - if (max_temperature < survey_results->at(x).at(y).max_temperature[mlt->at(i).at(k).biome_offset]) { - max_temperature = survey_results->at(x).at(y).max_temperature[mlt->at(i).at(k).biome_offset]; + if (result.max_temperature < survey_results->at(x).at(y).max_temperature[mlt->at(i).at(k).biome_offset]) { + result.max_temperature = survey_results->at(x).at(y).max_temperature[mlt->at(i).at(k).biome_offset]; } - if (min_temperature <= 0 && + if (result.min_temperature <= 0 && finder->freezing == embark_assist::defs::freezing_ranges::Never) return false; - if (max_temperature > 0 && + if (result.max_temperature > 0 && finder->freezing == embark_assist::defs::freezing_ranges::Permanent) return false; // Blood Rain if (survey_results->at(x).at(y).blood_rain[mlt->at(i).at(k).biome_offset]) { if (finder->blood_rain == embark_assist::defs::yes_no_ranges::No) return false; - blood_rain_found = true; + result.blood_rain_found = true; } // Syndrome Rain, Permanent @@ -221,21 +662,21 @@ namespace embark_assist { if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Temporary || finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Not_Permanent || finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::None) return false; - permanent_syndrome_rain_found = true; + result.permanent_syndrome_rain_found = true; } // Syndrome Rain, Temporary if (survey_results->at(x).at(y).temporary_syndrome_rain[mlt->at(i).at(k).biome_offset]) { if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Permanent || finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::None) return false; - temporary_syndrome_rain_found = true; + result.temporary_syndrome_rain_found = true; } // Reanmation if (survey_results->at(x).at(y).reanimating[mlt->at(i).at(k).biome_offset]) { if (finder->reanimation == embark_assist::defs::reanimation_ranges::Thralling || finder->reanimation == embark_assist::defs::reanimation_ranges::None) return false; - reanimation_found = true; + result.reanimation_found = true; } // Thralling @@ -243,50 +684,345 @@ namespace embark_assist { if (finder->reanimation == embark_assist::defs::reanimation_ranges::Reanimation || finder->reanimation == embark_assist::defs::reanimation_ranges::Not_Thralling || finder->reanimation == embark_assist::defs::reanimation_ranges::None) return false; - thralling_found = true; + result.thralling_found = true; } // Spires if (mlt->at(i).at(k).adamantine_level != -1) { - spire_count++; + result.spire_count++; if (finder->spire_count_max != -1 && - finder->spire_count_max < spire_count) return false; + finder->spire_count_max < result.spire_count) return false; } // Magma if (mlt->at(i).at(k).magma_level != -1) { - if (mlt->at(i).at(k).magma_level > magma_level) + if (mlt->at(i).at(k).magma_level > result.magma_level) { - magma_level = mlt->at(i).at(k).magma_level; + result.magma_level = mlt->at(i).at(k).magma_level; if (finder->magma_max != embark_assist::defs::magma_ranges::NA && - static_cast(finder->magma_max) < magma_level) return false; + static_cast(finder->magma_max) < result.magma_level) return false; } } // Biomes - biomes[survey_results->at(x).at(y).biome[mlt->at(i).at(k).biome_offset]] = true; + result.biomes[survey_results->at(x).at(y).biome[mlt->at(i).at(k).biome_offset]] = true; // Region Type - region_types[world_data->regions[survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset]]->type] = true; + result.region_types[world_data->regions[survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset]]->type] = true; // Metals - metal_1 = metal_1 || mlt->at(i).at(k).metals[finder->metal_1]; - metal_2 = metal_2 || mlt->at(i).at(k).metals[finder->metal_2]; - metal_3 = metal_3 || mlt->at(i).at(k).metals[finder->metal_3]; + result.metal_1 = result.metal_1 || mlt->at(i).at(k).metals[finder->metal_1]; + result.metal_2 = result.metal_2 || mlt->at(i).at(k).metals[finder->metal_2]; + result.metal_3 = result.metal_3 || mlt->at(i).at(k).metals[finder->metal_3]; // Economics - economic_1 = economic_1 || mlt->at(i).at(k).economics[finder->economic_1]; - economic_2 = economic_2 || mlt->at(i).at(k).economics[finder->economic_2]; - economic_3 = economic_3 || mlt->at(i).at(k).economics[finder->economic_3]; + result.economic_1 = result.economic_1 || mlt->at(i).at(k).economics[finder->economic_1]; + result.economic_2 = result.economic_2 || mlt->at(i).at(k).economics[finder->economic_2]; + result.economic_3 = result.economic_3 || mlt->at(i).at(k).economics[finder->economic_3]; // Minerals - mineral_1 = mineral_1 || mlt->at(i).at(k).minerals[finder->mineral_1]; - mineral_2 = mineral_2 || mlt->at(i).at(k).minerals[finder->mineral_2]; - mineral_3 = mineral_3 || mlt->at(i).at(k).minerals[finder->mineral_3]; + result.mineral_1 = result.mineral_1 || mlt->at(i).at(k).minerals[finder->mineral_1]; + result.mineral_2 = result.mineral_2 || mlt->at(i).at(k).minerals[finder->mineral_2]; + result.mineral_3 = result.mineral_3 || mlt->at(i).at(k).minerals[finder->mineral_3]; } } + // Take incursions into account. + + for (int8_t i = start_x; i < start_x + finder->x_dim; i++) { + + // NW corner, north row + if ((i == 0 && start_y == 0 && x - 1 >= 0 && y - 1 >= 0 && !survey_results->at(x - 1).at(y - 1).surveyed) || + (i == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed) || + (start_y == 0 && y - 1 >= 0 && !survey_results->at(x).at(y - 1).surveyed)) { + failed_match = true; + } + else { + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_corner(survey_results, + 4, + x, + y, + i, + start_y), + &result, + survey_results, + mlt, + finder, + x, + y, + i, + start_y, + &failed_match); + } + + // N edge, north row + if (start_y == 0 && y - 1 >= 0 && !survey_results->at(x).at(y - 1).surveyed) { + failed_match = true; + } + else { + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_ns_edge(survey_results, + true, + x, + y, + i, + start_y), + &result, + survey_results, + mlt, + finder, + x, + y, + i, + start_y, + &failed_match); + } + + // NE corner, north row + if ((i == 15 && start_y == 0 && x + 1 < world_data->world_width && y - 1 >= 0 && !survey_results->at(x + 1).at(y - 1).surveyed) || + (i == 15 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed) || + (start_y == 0 && y - 1 >= 0 && !survey_results->at(x).at(y - 1).surveyed)) { + failed_match = true; + } + else { + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_corner(survey_results, + 5, + x, + y, + i, + start_y), + &result, + survey_results, + mlt, + finder, + x, + y, + i, + start_y, + &failed_match); + } + + // SW corner, south row + if ((i == 0 && start_y + finder->y_dim == 16 && x - 1 >= 0 && y + 1 < world_data->world_height && !survey_results->at(x - 1).at(y + 1).surveyed) || + (i == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed) || + (start_y + finder->y_dim == 16 && y + 1 < world_data->world_height && !survey_results->at(x).at(y + 1).surveyed)) { + failed_match = true; + } + else { + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_corner(survey_results, + 7, + x, + y, + i, + start_y + finder->y_dim - 1), + &result, + survey_results, + mlt, + finder, + x, + y, + i, + start_y + finder->y_dim - 1, + &failed_match); + } + + // S edge, south row + if (start_y + finder->y_dim == 16 && y + 1 < world_data->world_height && !survey_results->at(x).at(y + 1).surveyed) { + failed_match = true; + } + else { + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_ns_edge(survey_results, + false, + x, + y, + i, + start_y + finder->y_dim - 1), + &result, + survey_results, + mlt, + finder, + x, + y, + i, + start_y + finder->y_dim - 1, + &failed_match); + } + + // SE corner south row + if ((i == 15 && start_y + finder->y_dim == 16 && x + 1 < world_data->world_width && y + 1 < world_data->world_height && !survey_results->at(x + 1).at(y + 1).surveyed) || + (i == 15 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed) || + (start_y + finder->y_dim == 16 && y + 1 < world_data->world_height && !survey_results->at(x).at(y + 1).surveyed)) { + failed_match = true; + } + else { + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_corner(survey_results, + 8, + x, + y, + i, + start_y + finder->y_dim - 1), + &result, + survey_results, + mlt, + finder, + x, + y, + i, + start_y + finder->y_dim - 1, + &failed_match); + } + + if (failed_match) return false; + } + + for (int8_t k = start_y; k < start_y + finder->y_dim; k++) { + // NW corner, west side + if ((start_x == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed)) { + failed_match = true; + } + else if (k > start_y) { // We've already covered the NW corner of the NW, with its complications. + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_corner(survey_results, + 4, + x, + y, + start_x, + k), + &result, + survey_results, + mlt, + finder, + x, + y, + start_x, + k, + &failed_match); + } + + // W edge, west side + if (start_x == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed) { + failed_match = true; + } + else { + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_ew_edge(survey_results, + true, + x, + y, + start_x, + k), + &result, + survey_results, + mlt, + finder, + x, + y, + start_x, + k, + &failed_match); + } + + // SW corner, west side + if (start_x == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed) { + failed_match = true; + } + else if (k < start_y + finder->y_dim - 1) { // We've already covered the SW corner of the SW tile, with its complicatinons. + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_corner(survey_results, + 7, + x, + y, + start_x, + k), + &result, + survey_results, + mlt, + finder, + x, + y, + start_x, + k, + &failed_match); + } + + // NE corner, east side + if ((start_x + finder->x_dim == 16 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed)) { + failed_match = true; + } + else if (k > start_y) { // We've already covered the NE tile's NE corner, with its complications. + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_corner(survey_results, + 5, + x, + y, + start_x + finder->x_dim - 1, + k), + &result, + survey_results, + mlt, + finder, + x, + y, + start_x + finder->x_dim - 1, + k, + &failed_match); + } + + // E edge, east side + if (start_x + finder->y_dim == 16 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed) { + failed_match = true; + } + else { + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_ew_edge(survey_results, + false, + x, + y, + start_x + finder->x_dim - 1, + k), + &result, + survey_results, + mlt, + finder, + x, + y, + start_x + finder->x_dim - 1, + k, + &failed_match); + } + + // SE corner, east side + if (start_x + finder->x_dim == 16 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed) { + failed_match = true; + } + else if (k < start_y + finder->y_dim - 1) { // We've already covered the SE tile's SE corner, with its complications. + process_embark_incursion_mid_level_tile + (embark_assist::survey::translate_corner(survey_results, + 8, + x, + y, + start_x + finder->x_dim - 1, + k), + &result, + survey_results, + mlt, + finder, + x, + y, + start_x + finder->x_dim - 1, + k, + &failed_match); + } + + if (failed_match) return false; + } + // Summary section, for all the stuff that require the complete picture // // Savagery & Evilness @@ -296,11 +1032,11 @@ namespace embark_assist { while (true) { if (finder->savagery[static_cast (l)] == embark_assist::defs::evil_savagery_values::Present && - !savagery_found[static_cast(l)]) return false; + !result.savagery_found[static_cast(l)]) return false; if (finder->evilness[static_cast (l)] == embark_assist::defs::evil_savagery_values::Present && - !evilness_found[static_cast(l)]) return false; + !result.evilness_found[static_cast(l)]) return false; if (l == embark_assist::defs::evil_savagery_ranges::High) break; l = static_cast (static_cast(l) + 1); @@ -315,107 +1051,107 @@ namespace embark_assist { break; case embark_assist::defs::aquifer_ranges::Present: - if (aquifer_count == 0) return false; + if (!result.aquifer_presence_found) return false; break; case embark_assist::defs::aquifer_ranges::Partial: - if (aquifer_count == 0 || aquifer_count == embark_size) return false; + if (!result.aquifer_absence_found || !result.aquifer_presence_found) return false; break; case embark_assist::defs::aquifer_ranges::Not_All: - if (aquifer_count == embark_size) return false; + if (!result.aquifer_absence_found) return false; break; } // River & Waterfall - if (!river_found && finder->min_river > embark_assist::defs::river_ranges::None) return false; - if (finder->waterfall == embark_assist::defs::yes_no_ranges::Yes && !waterfall_found) return false; + if (!result.river_found && finder->min_river > embark_assist::defs::river_ranges::None) return false; + if (result.max_waterfall < finder->min_waterfall) return false; // N/A = -1 is always smaller, so no additional check needed. // Flat - if (!uneven && finder->flat == embark_assist::defs::yes_no_ranges::No) return false; + if (!result.uneven && finder->flat == embark_assist::defs::yes_no_ranges::No) return false; // Clay - if (finder->clay == embark_assist::defs::present_absent_ranges::Present && !clay_found) return false; + if (finder->clay == embark_assist::defs::present_absent_ranges::Present && !result.clay_found) return false; // Sand - if (finder->sand == embark_assist::defs::present_absent_ranges::Present && !sand_found) return false; + if (finder->sand == embark_assist::defs::present_absent_ranges::Present && !result.sand_found) return false; // Flux - if (finder->flux == embark_assist::defs::present_absent_ranges::Present && !flux_found) return false; + if (finder->flux == embark_assist::defs::present_absent_ranges::Present && !result.flux_found) return false; // Coal - if (finder->coal == embark_assist::defs::present_absent_ranges::Present && !coal_found) return false; + if (finder->coal == embark_assist::defs::present_absent_ranges::Present && !result.coal_found) return false; // Min Soil if (finder->soil_min != embark_assist::defs::soil_ranges::NA && finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present && - max_soil < static_cast(finder->soil_min)) return false; + result.max_soil < static_cast(finder->soil_min)) return false; // Freezing if (finder->freezing == embark_assist::defs::freezing_ranges::At_Least_Partial && - min_temperature > 0) return false; + result.min_temperature > 0) return false; if (finder->freezing == embark_assist::defs::freezing_ranges::Partial && - (min_temperature > 0 || - max_temperature <= 0)) return false; + (result.min_temperature > 0 || + result.max_temperature <= 0)) return false; if (finder->freezing == embark_assist::defs::freezing_ranges::At_Most_Partial && - max_temperature <= 0) return false; + result.max_temperature <= 0) return false; // Blood Rain - if (finder->blood_rain == embark_assist::defs::yes_no_ranges::Yes && !blood_rain_found) return false; + if (finder->blood_rain == embark_assist::defs::yes_no_ranges::Yes && !result.blood_rain_found) return false; // Syndrome Rain - if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Any && !permanent_syndrome_rain_found && !temporary_syndrome_rain_found) return false; - if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Permanent && !permanent_syndrome_rain_found) return false; - if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Temporary && !temporary_syndrome_rain_found) return false; + if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Any && !result.permanent_syndrome_rain_found && !result.temporary_syndrome_rain_found) return false; + if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Permanent && !result.permanent_syndrome_rain_found) return false; + if (finder->syndrome_rain == embark_assist::defs::syndrome_rain_ranges::Temporary && !result.temporary_syndrome_rain_found) return false; // Reanimation - if (finder->reanimation == embark_assist::defs::reanimation_ranges::Both && !(reanimation_found && thralling_found)) return false; - if (finder->reanimation == embark_assist::defs::reanimation_ranges::Any && !reanimation_found && !thralling_found) return false; - if (finder->reanimation == embark_assist::defs::reanimation_ranges::Thralling && !thralling_found) return false; - if (finder->reanimation == embark_assist::defs::reanimation_ranges::Reanimation && !reanimation_found) return false; + if (finder->reanimation == embark_assist::defs::reanimation_ranges::Both && !(result.reanimation_found && result.thralling_found)) return false; + if (finder->reanimation == embark_assist::defs::reanimation_ranges::Any && !result.reanimation_found && !result.thralling_found) return false; + if (finder->reanimation == embark_assist::defs::reanimation_ranges::Thralling && !result.thralling_found) return false; + if (finder->reanimation == embark_assist::defs::reanimation_ranges::Reanimation && !result.reanimation_found) return false; // Spires - if (finder->spire_count_min != -1 && finder->spire_count_min > spire_count) return false; - if (finder->spire_count_max != -1 && finder->spire_count_max < spire_count) return false; + if (finder->spire_count_min != -1 && finder->spire_count_min > result.spire_count) return false; + if (finder->spire_count_max != -1 && finder->spire_count_max < result.spire_count) return false; // Magma if (// finder->magma_min != embark_assist::defs::magma_ranges::NA && // This check is redundant. - finder->magma_min > static_cast(magma_level)) return false; + finder->magma_min > static_cast(result.magma_level)) return false; // Biomes if (finder->biome_count_min != -1 || finder->biome_count_max != -1) { - biome_count = 0; + result.biome_count = 0; for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { - if (biomes[i]) biome_count++; + if (result.biomes[i]) result.biome_count++; } - if (biome_count < finder->biome_count_min || + if (result.biome_count < finder->biome_count_min || (finder->biome_count_max != -1 && - finder->biome_count_max < biome_count)) return false; + finder->biome_count_max < result.biome_count)) return false; } - if (finder->biome_1 != -1 && !biomes[finder->biome_1]) return false; - if (finder->biome_2 != -1 && !biomes[finder->biome_2]) return false; - if (finder->biome_3 != -1 && !biomes[finder->biome_3]) return false; + if (finder->biome_1 != -1 && !result.biomes[finder->biome_1]) return false; + if (finder->biome_2 != -1 && !result.biomes[finder->biome_2]) return false; + if (finder->biome_3 != -1 && !result.biomes[finder->biome_3]) return false; // Region Type - if (finder->region_type_1 != -1 && !region_types[finder->region_type_1]) return false; - if (finder->region_type_2 != -1 && !region_types[finder->region_type_2]) return false; - if (finder->region_type_3 != -1 && !region_types[finder->region_type_3]) return false; + if (finder->region_type_1 != -1 && !result.region_types[finder->region_type_1]) return false; + if (finder->region_type_2 != -1 && !result.region_types[finder->region_type_2]) return false; + if (finder->region_type_3 != -1 && !result.region_types[finder->region_type_3]) return false; // Metals, Economics, and Minerals - if (!metal_1 || - !metal_2 || - !metal_3 || - !economic_1 || - !economic_2 || - !economic_3 || - !mineral_1 || - !mineral_2 || - !mineral_3) return false; + if (!result.metal_1 || + !result.metal_2 || + !result.metal_3 || + !result.economic_1 || + !result.economic_2 || + !result.economic_3 || + !result.mineral_1 || + !result.mineral_2 || + !result.mineral_3) return false; return true; } @@ -558,19 +1294,10 @@ namespace embark_assist { } // Waterfall - switch (finder->waterfall) { - case embark_assist::defs::yes_no_ranges::NA: - break; // No restriction - - case embark_assist::defs::yes_no_ranges::Yes: - if (!tile->waterfall) return false; - break; - - case embark_assist::defs::yes_no_ranges::No: - if (tile->waterfall && - embark_size == 256) return false; - break; - } + if (finder->min_waterfall > tile->max_waterfall) return false; // N/A = -1 is always smaller + if (finder->min_waterfall == 0 && // Absent + embark_size == 256 && + tile->max_waterfall > 0) return false; // Flat. No world tile checks. Need to look at the details @@ -1042,7 +1769,7 @@ namespace embark_assist { } // Waterfall - if (finder->waterfall == embark_assist::defs::yes_no_ranges::Yes && + if (finder->min_waterfall > 0 && tile->river_size == embark_assist::defs::river_sizes::None) return false; // Flat. No world tile checks. Need to look at the details @@ -1530,7 +2257,7 @@ uint16_t embark_assist::matcher::find(embark_assist::defs::match_iterators *iter return 0; } - if (iterator->finder.waterfall == embark_assist::defs::yes_no_ranges::Yes && + if (iterator->finder.min_waterfall > 0 && iterator->finder.max_river == embark_assist::defs::river_ranges::None) { out.printerr("matcher::find: Will never find any waterfalls with None as max river\n"); return 0; diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp index c25859ccb..a0defd0d5 100644 --- a/plugins/embark-assistant/overlay.cpp +++ b/plugins/embark-assistant/overlay.cpp @@ -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) { state->embark_info.clear(); + if (!site_info->incursions_processed) { + state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTRED), "Incomp. Survey" }); + } + if (site_info->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_full) { - state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Aquifer" }); + state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Full Aquifer" }); } 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) { - state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Waterfall" }); + if (site_info->max_waterfall > 0) { + 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) { diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp index f19f3cd4a..3574219bc 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -8,6 +8,7 @@ #include #include "modules/Materials.h" +#include "modules/Maps.h" #include "DataDefs.h" #include "df/builtin_mats.h" @@ -50,12 +51,12 @@ #include "df/world_region.h" #include "df/world_region_details.h" #include "df/world_region_feature.h" +#include "df/world_region_type.h" #include "df/world_river.h" #include "df/world_site.h" #include "df/world_site_type.h" #include "df/world_underground_region.h" -#include "biome_type.h" #include "defs.h" #include "survey.h" @@ -112,7 +113,7 @@ namespace embark_assist { for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) { for (uint16_t k = 0; k < world->raws.inorganics[i]->economic_uses.size(); k++) { for (uint16_t l = 0; l < world->raws.reactions.reactions[world->raws.inorganics[i]->economic_uses[k]]->products.size(); l++) { - df::reaction_product_itemst *product = static_cast(world->raws.reactions.reactions[world->raws.inorganics[i]->economic_uses[k]]->products[l]); + df::reaction_product_itemst *product = static_cast(world->raws.reactions.reactions[world->raws.inorganics[i]->economic_uses[k]]->products[l]); if (product->mat_type == df::builtin_mats::COAL) { state->coals.push_back(i); @@ -172,7 +173,7 @@ namespace embark_assist { } } - for (uint16_t l = 0; l < state->coals.size(); l++) { + for (uint16_t l = 0; l < state->coals.size(); l++) { if (layer->mat_index == state->coals[l]) { geo_summary->at(i).coal_absent = false; break; @@ -203,7 +204,7 @@ namespace embark_assist { } for (uint16_t m = 0; m < state->coals.size(); m++) { - if (vein== state->coals[m]) { + if (vein == state->coals[m]) { geo_summary->at(i).coal_absent = false; break; } @@ -229,7 +230,7 @@ namespace embark_assist { //================================================================================= void survey_rivers(embark_assist::defs::world_tile_data *survey_results) { -// color_ostream_proxy out(Core::getInstance().getConsole()); + // color_ostream_proxy out(Core::getInstance().getConsole()); df::world_data *world_data = world->world_data; int16_t x; int16_t y; @@ -327,23 +328,23 @@ namespace embark_assist { thralling = true; } else if (ce_type == df::creature_interaction_effect_type::PAIN || - ce_type == df::creature_interaction_effect_type::SWELLING || - ce_type == df::creature_interaction_effect_type::OOZING || - ce_type == df::creature_interaction_effect_type::BRUISING || - ce_type == df::creature_interaction_effect_type::BLISTERS || - ce_type == df::creature_interaction_effect_type::NUMBNESS || - ce_type == df::creature_interaction_effect_type::PARALYSIS || - ce_type == df::creature_interaction_effect_type::FEVER || - ce_type == df::creature_interaction_effect_type::BLEEDING || - ce_type == df::creature_interaction_effect_type::COUGH_BLOOD || - ce_type == df::creature_interaction_effect_type::VOMIT_BLOOD || - ce_type == df::creature_interaction_effect_type::NAUSEA || - ce_type == df::creature_interaction_effect_type::UNCONSCIOUSNESS || - ce_type == df::creature_interaction_effect_type::NECROSIS || - ce_type == df::creature_interaction_effect_type::IMPAIR_FUNCTION || - ce_type == df::creature_interaction_effect_type::DROWSINESS || - ce_type == df::creature_interaction_effect_type::DIZZINESS || - ce_type == df::creature_interaction_effect_type::ERRATIC_BEHAVIOR) { // Doubtful if possible for region. + ce_type == df::creature_interaction_effect_type::SWELLING || + ce_type == df::creature_interaction_effect_type::OOZING || + ce_type == df::creature_interaction_effect_type::BRUISING || + ce_type == df::creature_interaction_effect_type::BLISTERS || + ce_type == df::creature_interaction_effect_type::NUMBNESS || + ce_type == df::creature_interaction_effect_type::PARALYSIS || + ce_type == df::creature_interaction_effect_type::FEVER || + ce_type == df::creature_interaction_effect_type::BLEEDING || + ce_type == df::creature_interaction_effect_type::COUGH_BLOOD || + ce_type == df::creature_interaction_effect_type::VOMIT_BLOOD || + ce_type == df::creature_interaction_effect_type::NAUSEA || + ce_type == df::creature_interaction_effect_type::UNCONSCIOUSNESS || + ce_type == df::creature_interaction_effect_type::NECROSIS || + ce_type == df::creature_interaction_effect_type::IMPAIR_FUNCTION || + ce_type == df::creature_interaction_effect_type::DROWSINESS || + ce_type == df::creature_interaction_effect_type::DIZZINESS || + ce_type == df::creature_interaction_effect_type::ERRATIC_BEHAVIOR) { // Doubtful if possible for region. if (ce->end == -1) { permanent_syndrome_rain = true; } @@ -446,7 +447,7 @@ namespace embark_assist { } else if (world->world_data->flip_latitude == df::world_data::T_flip_latitude::North || - world->world_data->flip_latitude == df::world_data::T_flip_latitude::South) { + world->world_data->flip_latitude == df::world_data::T_flip_latitude::South) { steps = world->world_data->world_height / 2; if (latitude > steps) { @@ -485,7 +486,7 @@ namespace embark_assist { divisor = (63 / steps * lat); } else if (world->world_data->world_height == 129 || - world->world_data->world_height == 257) { + world->world_data->world_height == 257) { divisor = (64 / steps * lat); } else { @@ -494,6 +495,222 @@ namespace embark_assist { return max_temperature - ceil(divisor * 3 / 4); } + + //================================================================================= + + void process_embark_incursion(embark_assist::defs::site_infos *site_info, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tile *mlt, // Note this is a single tile, as opposed to most usages of this variable name. + int16_t elevation, + uint16_t x, + uint16_t y) { + + if (mlt->aquifer) { + site_info->aquifer = true; + } + else { + site_info->aquifer_full = false; + } + + if (mlt->soil_depth < site_info->min_soil) { + site_info->min_soil = mlt->soil_depth; + } + + if (mlt->soil_depth > site_info->max_soil) { + site_info->max_soil = mlt->soil_depth; + } + + if (elevation != mlt->elevation) { + site_info->flat = false; + } + + if (mlt->clay) { + site_info->clay = true; + } + + if (mlt->sand) { + site_info->sand = true; + } + + if (survey_results->at(x).at(y).blood_rain [mlt->biome_offset]) { + site_info->blood_rain = true; + } + + if (survey_results->at(x).at(y).permanent_syndrome_rain[mlt->biome_offset]) { + site_info->permanent_syndrome_rain = true; + } + + if (survey_results->at(x).at(y).temporary_syndrome_rain[mlt->biome_offset]) { + site_info->temporary_syndrome_rain = true; + } + + if (survey_results->at(x).at(y).reanimating[mlt->biome_offset]) { + site_info->reanimating = true; + } + + if (survey_results->at(x).at(y).thralling[mlt->biome_offset]) { + site_info->thralling = true; + } + } + + //================================================================================= + + void process_embark_incursion_mid_level_tile(uint8_t from_direction, + embark_assist::defs::site_infos *site_info, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + uint8_t i, + uint8_t k) { + + int8_t fetch_i = i; + int8_t fetch_k = k; + int16_t fetch_x = state->x; + int16_t fetch_y = state->y; + df::world_data *world_data = world->world_data; + + // Logic can be implemented with modulo and division, but that's harder to read. + switch (from_direction) { + case 0: + fetch_i = i - 1; + fetch_k = k - 1; + break; + + case 1: + fetch_k = k - 1; + break; + + case 2: + fetch_i = i + 1; + fetch_k = k - 1; + break; + + case 3: + fetch_i = i - 1; + break; + + case 4: + return; // Own tile provides the data, so there's no incursion. + break; + + case 5: + fetch_i = i + 1; + break; + + case 6: + fetch_i = i - 1; + fetch_k = k + 1; + break; + + case 7: + fetch_k = k + 1; + break; + + case 8: + fetch_i = i + 1; + fetch_k = k + 1; + break; + } + + if (fetch_i < 0) { + fetch_x = state->x - 1; + } + else if (fetch_i > 15) { + fetch_x = state->x + 1; + } + + if (fetch_k < 0) { + fetch_y = state->y - 1; + } + else if (fetch_k > 15) { + fetch_y = state->y + 1; + } + + if (fetch_x < 0 || + fetch_x == world_data->world_width || + fetch_y < 0 || + fetch_y == world_data->world_height) { + return; // We're at the world edge, so no incursions from the outside. + } + + if (fetch_k < 0) { + if (fetch_i < 0) { + process_embark_incursion(site_info, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).south_row[15], + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + else if (fetch_i > 15) { + process_embark_incursion(site_info, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).south_row[0], + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + else { + process_embark_incursion(site_info, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).south_row[i], + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + } + else if (fetch_k > 15) { + if (fetch_i < 0) { + process_embark_incursion(site_info, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).north_row[15], + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + else if (fetch_i > 15) { + process_embark_incursion(site_info, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).north_row[0], + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + else { + process_embark_incursion(site_info, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).north_row[i], + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + } + else { + if (fetch_i < 0) { + process_embark_incursion(site_info, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).east_column[k], + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + else if (fetch_i > 15) { + process_embark_incursion(site_info, + survey_results, + &survey_results->at(fetch_x).at(fetch_y).west_column[k], + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + else { + process_embark_incursion(site_info, + survey_results, + &mlt->at(fetch_i).at(fetch_k), + mlt->at(i).at(k).elevation, + fetch_x, + fetch_y); + } + } + } } } @@ -567,7 +784,7 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat results.coal_count = 0; results.min_region_soil = 10; results.max_region_soil = 0; - results.waterfall = false; + results.max_waterfall = 0; results.savagery_count[0] = 0; results.savagery_count[1] = 0; results.savagery_count[2] = 0; @@ -586,7 +803,7 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat offset_count++; results.biome_index[l] = world_data->region_map[adjusted.x][adjusted.y].region_id; - results.biome[l] = get_biome_type(adjusted.x, adjusted.y, k); + results.biome[l] = DFHack::Maps::GetBiomeTypeWithRef(adjusted.x, adjusted.y, k); temperature = world_data->region_map[adjusted.x][adjusted.y].temperature; negative = temperature < 0; @@ -667,7 +884,7 @@ void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_dat void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary, embark_assist::defs::world_tile_data *survey_results, embark_assist::defs::mid_level_tiles *mlt) { - // color_ostream_proxy out(Core::getInstance().getConsole()); +// color_ostream_proxy out(Core::getInstance().getConsole()); auto screen = Gui::getViewscreenByType(0); int16_t x = screen->location.region_pos.x; int16_t y = screen->location.region_pos.y; @@ -853,12 +1070,12 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data if (layer->type == df::geo_layer_type::SOIL || layer->type == df::geo_layer_type::SOIL_SAND) { int16_t size = layer->top_height - layer->bottom_height - 1; - // Comment copied from prospector.cpp(like the logic)... + // Comment copied from prospector.cpp (like the logic)... // This is to replicate the behavior of a probable bug in the - // map generation code : if a layer is partially eroded, the - // removed levels are in fact transferred to the layer below, + // map generation code : if a layer is partially eroded, the + // removed levels are in fact transferred to the layer below, // because unlike the case of removing the whole layer, the code - // does not execute a loop to shift the lower part of the stack up. + // does not execute a loop to shift the lower part of the stack up. if (size > soil_erosion) { cur_shift = cur_shift - soil_erosion; } @@ -880,6 +1097,8 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data } if (top_z >= bottom_z) { + last_bottom = bottom_z; + mlt->at(i).at(k).minerals[layer->mat_index] = true; end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size()); @@ -974,9 +1193,6 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data survey_results->at(x).at(y).evilness_count[1] = 0; survey_results->at(x).at(y).evilness_count[2] = 0; - bool river_elevation_found = false; - int16_t river_elevation = 0; - for (uint8_t i = 0; i < 16; i++) { for (uint8_t k = 0; k < 16; k++) { if (mlt->at(i).at(k).aquifer) { survey_results->at(x).at(y).aquifer_count++; } @@ -994,15 +1210,20 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data } if (mlt->at(i).at(k).river_present) { - if (river_elevation_found) { - if (mlt->at(i).at(k).river_elevation != river_elevation) - { - survey_results->at(x).at(y).waterfall = true; - } + if (i < 15 && + mlt->at(i + 1).at(k).river_present && + abs (mlt->at(i).at(k).river_elevation - mlt->at(i + 1).at(k).river_elevation) > + survey_results->at(x).at(y).max_waterfall) { + survey_results->at(x).at(y).max_waterfall = + abs(mlt->at(i).at(k).river_elevation - mlt->at(i + 1).at(k).river_elevation); } - else { - river_elevation_found = true; - river_elevation = mlt->at(i).at(k).river_elevation; + + if (k < 15 && + mlt->at(i).at(k + 1).river_present && + abs(mlt->at(i).at(k).river_elevation - mlt->at(i).at(k + 1).river_elevation) > + survey_results->at(x).at(y).max_waterfall) { + survey_results->at(x).at(y).max_waterfall = + abs(mlt->at(i).at(k).river_elevation - mlt->at(i).at(k + 1).river_elevation); } } @@ -1047,8 +1268,111 @@ void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data } tile->biome_count = count; + + for (uint8_t i = 0; i < 16; i++) { + tile->north_row[i].aquifer = mlt->at(i).at(0).aquifer; + tile->south_row[i].aquifer = mlt->at(i).at(15).aquifer; + tile->west_column[i].aquifer = mlt->at(0).at(i).aquifer; + tile->east_column[i].aquifer = mlt->at(15).at(i).aquifer; + + tile->north_row[i].clay= mlt->at(i).at(0).clay; + tile->south_row[i].clay = mlt->at(i).at(15).clay; + tile->west_column[i].clay = mlt->at(0).at(i).clay; + tile->east_column[i].clay = mlt->at(15).at(i).clay; + + tile->north_row[i].sand = mlt->at(i).at(0).sand; + tile->south_row[i].sand = mlt->at(i).at(15).sand; + tile->west_column[i].sand = mlt->at(0).at(i).sand; + tile->east_column[i].sand = mlt->at(15).at(i).sand; + + tile->north_row[i].flux = mlt->at(i).at(0).flux; // Not used + tile->south_row[i].flux = mlt->at(i).at(15).flux; + tile->west_column[i].flux = mlt->at(0).at(i).flux; + tile->east_column[i].flux = mlt->at(15).at(i).flux; + + tile->north_row[i].coal = mlt->at(i).at(0).coal; // Not used + tile->south_row[i].coal = mlt->at(i).at(15).coal; + tile->west_column[i].coal = mlt->at(0).at(i).coal; + tile->east_column[i].coal = mlt->at(15).at(i).coal; + + tile->north_row[i].soil_depth = mlt->at(i).at(0).soil_depth; + tile->south_row[i].soil_depth = mlt->at(i).at(15).soil_depth; + tile->west_column[i].soil_depth = mlt->at(0).at(i).soil_depth; + tile->east_column[i].soil_depth = mlt->at(15).at(i).soil_depth; + + tile->north_row[i].offset = mlt->at(i).at(0).offset; // Not used + tile->south_row[i].offset = mlt->at(i).at(15).offset; + tile->west_column[i].offset = mlt->at(0).at(i).offset; + tile->east_column[i].offset = mlt->at(15).at(i).offset; + + tile->north_row[i].elevation = mlt->at(i).at(0).elevation; + tile->south_row[i].elevation = mlt->at(i).at(15).elevation; + tile->west_column[i].elevation = mlt->at(0).at(i).elevation; + tile->east_column[i].elevation = mlt->at(15).at(i).elevation; + + tile->north_row[i].river_present = mlt->at(i).at(0).river_present; // Not used + tile->south_row[i].river_present = mlt->at(i).at(15).river_present; + tile->west_column[i].river_present = mlt->at(0).at(i).river_present; + tile->east_column[i].river_present = mlt->at(15).at(i).river_present; + + tile->north_row[i].river_elevation = mlt->at(i).at(0).river_elevation; // Not used + tile->south_row[i].river_elevation = mlt->at(i).at(15).river_elevation; + tile->west_column[i].river_elevation = mlt->at(0).at(i).river_elevation; + tile->east_column[i].river_elevation = mlt->at(15).at(i).river_elevation; + + tile->north_row[i].adamantine_level = mlt->at(i).at(0).adamantine_level; // Not used + tile->south_row[i].adamantine_level = mlt->at(i).at(15).adamantine_level; + tile->west_column[i].adamantine_level = mlt->at(0).at(i).adamantine_level; + tile->east_column[i].adamantine_level = mlt->at(15).at(i).adamantine_level; + + tile->north_row[i].magma_level = mlt->at(i).at(0).magma_level; // Not used + tile->south_row[i].magma_level = mlt->at(i).at(15).magma_level; + tile->west_column[i].magma_level = mlt->at(0).at(i).magma_level; + tile->east_column[i].magma_level = mlt->at(15).at(i).magma_level; + + tile->north_row[i].biome_offset = mlt->at(i).at(0).biome_offset; + tile->south_row[i].biome_offset = mlt->at(i).at(15).biome_offset; + tile->west_column[i].biome_offset = mlt->at(0).at(i).biome_offset; + tile->east_column[i].biome_offset = mlt->at(15).at(i).biome_offset; + + tile->north_row[i].savagery_level = mlt->at(i).at(0).savagery_level; + tile->south_row[i].savagery_level = mlt->at(i).at(15).savagery_level; + tile->west_column[i].savagery_level = mlt->at(0).at(i).savagery_level; + tile->east_column[i].savagery_level = mlt->at(15).at(i).savagery_level; + + tile->north_row[i].evilness_level = mlt->at(i).at(0).evilness_level; + tile->south_row[i].evilness_level = mlt->at(i).at(15).evilness_level; + tile->west_column[i].evilness_level = mlt->at(0).at(i).evilness_level; + tile->east_column[i].evilness_level = mlt->at(15).at(i).evilness_level; + + tile->north_row[i].metals.resize(0); // Not used + tile->south_row[i].metals.resize(0); + tile->west_column[i].metals.resize(0); + tile->east_column[i].metals.resize(0); + + tile->north_row[i].economics.resize(0); // Not used + tile->south_row[i].economics.resize(0); + tile->west_column[i].economics.resize(0); + tile->east_column[i].economics.resize(0); + + tile->north_row[i].minerals.resize(0); // Not used + tile->south_row[i].minerals.resize(0); + tile->west_column[i].minerals.resize(0); + tile->east_column[i].minerals.resize(0); + + tile->north_corner_selection[i] = world_data->region_details[0]->edges.biome_corner[i][0]; + tile->west_corner_selection[i] = world_data->region_details[0]->edges.biome_corner[0][i]; + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + tile->region_type[i][k] = world_data->regions[tile->biome[mlt->at(i).at(k).biome_offset]]->type; + } + } + tile->surveyed = true; } + //================================================================================= df::coord2d embark_assist::survey::apply_offset(uint16_t x, uint16_t y, int8_t offset) { @@ -1120,6 +1444,486 @@ df::coord2d embark_assist::survey::apply_offset(uint16_t x, uint16_t y, int8_t o //================================================================================= +void adjust_coordinates(int16_t *x, int16_t *y, int8_t *i, int8_t *k) { + if (*i < 0) { + *x = *x - 1; + *i = *i + 16; + } + else if (*i > 15) { + *x = *x + 1; + *i = *i - 16; + } + + if (*k < 0) { + *y = *y - 1; + *k = *k + 16; + } + else if (*k > 15) { + *y = *y + 1; + *k = *k - 16; + } +} + +//================================================================================= + +df::world_region_type embark_assist::survey::region_type_of(embark_assist::defs::world_tile_data *survey_results, + int16_t x, + int16_t y, + int8_t i, + int8_t k) { + + df::world_data *world_data = world->world_data; + int16_t effective_x = x; + int16_t effective_y = y; + int8_t effective_i = i; + int8_t effective_k = k; + adjust_coordinates(&effective_x, &effective_y, &effective_i, &effective_i); + + if (effective_x < 0 || + effective_x >= world_data->world_width || + effective_y < 0 || + effective_y >= world_data->world_height) { + return df::world_region_type::Lake; // Essentially dummy value, yielding to everything. It will + // be handled properly later anyway. + } + + return survey_results->at(effective_x).at(effective_y).region_type[effective_i][effective_k]; +} + +//================================================================================= + +uint8_t embark_assist::survey::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) { + + df::world_data *world_data = world->world_data; + df::world_region_type nw_region_type; + df::world_region_type n_region_type; + df::world_region_type w_region_type; + df::world_region_type home_region_type; + + int16_t effective_x = x; + int16_t effective_y = y; + int8_t effective_i = i; + int8_t effective_k = k; + + uint8_t effective_corner; + bool nw_region_type_active; + bool n_region_type_active; + bool w_region_type_active; + bool home_region_type_active; + uint8_t nw_region_type_level; + uint8_t n_region_type_level; + uint8_t w_region_type_level; + uint8_t home_region_type_level; + + if (corner_location == 4) { // We're the reference. No change. + } + else if (corner_location == 5) { // Tile to the east is the reference + effective_i = i + 1; + } + else if (corner_location == 7) { // Tile to the south is the reference + effective_k = k + 1; + } + else { // 8, tile to the southeast is the reference. + effective_i = i + 1; + effective_k = k + 1; + } + + adjust_coordinates(&effective_x, &effective_y, &effective_i, &effective_i); + + if (effective_x == world_data->world_width) { + if (effective_y == world_data->world_height) { // Only the SE corner of the SE most tile of the world can reference this. + return 4; + } + else { // East side corners of the east edge of the world + if (corner_location == 5) { + return 1; + } + else { // Can only be corner_location == 8 + return 4; + } + } + } + else if (effective_y == world_data->world_height) { + if (corner_location == 7) { + return 4; + } + else { // Can only be corner_location == 8 + return 3; + } + } + + if (effective_x == x && effective_y == y) { + effective_corner = world_data->region_details[0]->edges.biome_corner[effective_i][effective_k]; + } + else if (effective_y != y) { + effective_corner = survey_results->at(effective_x).at(effective_y).north_corner_selection[effective_i]; + } + else { + effective_corner = survey_results->at(effective_x).at(effective_y).west_corner_selection[effective_k]; + } + + nw_region_type = embark_assist::survey::region_type_of(survey_results, x, y, effective_i - 1, effective_k - 1); + n_region_type = embark_assist::survey::region_type_of(survey_results, x, y, effective_i, effective_k - 1); + w_region_type = embark_assist::survey::region_type_of(survey_results, x, y, effective_i - 1, effective_k); + home_region_type = embark_assist::survey::region_type_of(survey_results, x, y, effective_i, effective_k); + + if (nw_region_type == df::world_region_type::Lake || + nw_region_type == df::world_region_type::Ocean) { + nw_region_type_level = 0; + } + else if (nw_region_type == df::world_region_type::Mountains) { + nw_region_type_level = 1; + } + else { + nw_region_type_level = 2; + } + + if (n_region_type == df::world_region_type::Lake || + n_region_type == df::world_region_type::Ocean) { + n_region_type_level = 0; + } + else if (n_region_type == df::world_region_type::Mountains) { + n_region_type_level = 1; + } + else { + n_region_type_level = 2; + } + + if (w_region_type == df::world_region_type::Lake || + w_region_type == df::world_region_type::Ocean) { + w_region_type_level = 0; + } + else if (w_region_type == df::world_region_type::Mountains) { + w_region_type_level = 1; + } + else { + w_region_type_level = 2; + } + + if (home_region_type == df::world_region_type::Lake || + home_region_type == df::world_region_type::Ocean) { + home_region_type_level = 0; + } + else if (n_region_type == df::world_region_type::Mountains) { + home_region_type_level = 1; + } + else { + home_region_type_level = 2; + } + + if (effective_x == 0 && effective_i == 0) { // West edge of the world + if (effective_y == 0 && effective_k == 0) { + return 4; // Only a single reference to this info, the own tile. + } + else { + nw_region_type_level = -1; // Knock out the unreachable corners + w_region_type_level = -1; + } + } + + if (effective_y == 0 && effective_k == 0) { // North edge of the world + nw_region_type_level = -1; // Knock out the unreachable corners + n_region_type_level = -1; + + if (corner_location == 4 && effective_corner == 1) { // The logic below would select the wrong alternative. + effective_corner = 3; + } + } + + nw_region_type_active = nw_region_type_level >= n_region_type_level && + nw_region_type_level >= w_region_type_level && + nw_region_type_level >= home_region_type_level; + + n_region_type_active = n_region_type_level >= nw_region_type_level && + n_region_type_level >= w_region_type_level && + n_region_type_level >= home_region_type_level; + + w_region_type_active = w_region_type_level >= nw_region_type_level && + w_region_type_level >= n_region_type_level && + w_region_type_level >= home_region_type_level; + + home_region_type_active = home_region_type_level >= nw_region_type_level && + home_region_type_level >= n_region_type_level && + home_region_type_level >= w_region_type_level; + + if ((effective_corner == 0 && !nw_region_type_active) || + (effective_corner == 1 && !n_region_type_active) || + (effective_corner == 2 && !w_region_type_active) || + (effective_corner == 3 && !home_region_type_active)) { + // The designated corner is suppressed. The precedence list below seems + // to match what DF produces except in the case adjusted above. + if (nw_region_type_active) { + effective_corner = 0; + } + else if (n_region_type_active) { + effective_corner = 1; + } + else if (w_region_type_active) { + effective_corner = 2; + } + else { + effective_corner = 3; + } + } + + switch (effective_corner) { + case 0: + return corner_location - 4; + break; + + case 1: + return corner_location - 3; + break; + + case 2: + return corner_location - 1; + break; + + case 3: + return corner_location; + break; + } + + return -1; // Should never happen + + /* The logic that's reduced to the code above. + switch (corner_location) { + case 0: // N/A Not to the north or west + case 1: // N/A + case 2: // N/A + case 3: // N/A + case 6: // N/A + return -1; // Should never happen + break; + + case 4: // Self + switch (corner) { + case 0: + return 0; // Northwest + break; + + case 1: + return 1; // North + break; + + case 2: + return 3; // West + break; + + case 3: + return 4; // Self + } + + case 5: // East + switch (corner) { + case 0: + return 1; // North + break; + + case 1: + return 2; // Northeast + break; + + case 2: + return 4; // Self + break; + + case 3: + return 5; // East + } + case 7: // South + switch (corner) { + case 0: + return 3; // West + break; + + case 1: + return 4; // Self + break; + + case 2: + return 6; // Southwest + break; + + case 3: + return 7; // South + } + + case 8: // Southeast + switch (corner) { + case 0: + return 4; // Self + break; + + case 1: + return 5; // East + break; + + case 2: + return 7; // South + break; + + case 3: + return 8; // Southeast + } + } + */ +} + +//================================================================================= + +uint8_t embark_assist::survey::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) { + + df::world_data *world_data = world->world_data; + uint8_t effective_edge; + df::world_region_type north_region_type; + df::world_region_type south_region_type; + + if (own_edge) { + effective_edge = world_data->region_details[0]->edges.biome_x[i][k]; + south_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k); + north_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k - 1); + } + else { + effective_edge = world_data->region_details[0]->edges.biome_x[i][k + 1]; + north_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k); + south_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k + 1); + } + + // Apply rules for Ocean && Lake to yield to everything else, + // and Mountain to everything but those. + // + if ((north_region_type == df::world_region_type::Lake || + north_region_type == df::world_region_type::Ocean) && + south_region_type != df::world_region_type::Lake && + south_region_type != df::world_region_type::Ocean) { + effective_edge = 1; + } + + if ((south_region_type == df::world_region_type::Lake || + south_region_type == df::world_region_type::Ocean) && + north_region_type != df::world_region_type::Lake && + north_region_type != df::world_region_type::Ocean) { + effective_edge = 0; + } + + if (north_region_type == df::world_region_type::Mountains && + south_region_type != df::world_region_type::Lake && + south_region_type != df::world_region_type::Ocean && + south_region_type != df::world_region_type::Mountains) { + effective_edge = 1; + } + + if (south_region_type == df::world_region_type::Mountains && + north_region_type != df::world_region_type::Lake && + north_region_type != df::world_region_type::Ocean && + north_region_type != df::world_region_type::Mountains) { + effective_edge = 0; + } + + if (effective_edge == 0) { + if (own_edge) { + return 1; + } + else { + return 4; + } + } + else { + if (own_edge) { + return 4; + } + else { + return 7; + } + } +} + +//================================================================================= + +uint8_t embark_assist::survey::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) { + + df::world_data *world_data = world->world_data; + uint8_t effective_edge; + df::world_region_type west_region_type; + df::world_region_type east_region_type; + + if (own_edge) { + effective_edge = world_data->region_details[0]->edges.biome_x[i][k]; + east_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k); + west_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i - 1, k); + } + else { + effective_edge = world_data->region_details[0]->edges.biome_x[i + 1][k]; + west_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i, k); + east_region_type = embark_assist::survey::region_type_of(survey_results, x, y, i + 1, k); + } + + // Apply rules for Ocean && Lake to yield to everything else, + // and Mountain to everything but those. + // + if ((west_region_type == df::world_region_type::Lake || + west_region_type == df::world_region_type::Ocean) && + east_region_type != df::world_region_type::Lake && + east_region_type != df::world_region_type::Ocean) { + effective_edge = 1; + } + + if ((east_region_type == df::world_region_type::Lake || + east_region_type == df::world_region_type::Ocean) && + west_region_type != df::world_region_type::Lake && + west_region_type != df::world_region_type::Ocean) { + effective_edge = 0; + } + + if (west_region_type == df::world_region_type::Mountains && + west_region_type != df::world_region_type::Lake && + east_region_type != df::world_region_type::Ocean && + east_region_type != df::world_region_type::Mountains) { + effective_edge = 1; + } + + if (east_region_type == df::world_region_type::Mountains && + east_region_type != df::world_region_type::Lake && + west_region_type != df::world_region_type::Ocean && + west_region_type != df::world_region_type::Mountains) { + effective_edge = 0; + } + if (effective_edge == 0) { + if (own_edge) { + return 3; + } + else { + return 4; + } + } + else { + if (own_edge) { + return 4; + } + else { + return 5; + } + } +} + +//================================================================================= + void embark_assist::survey::survey_region_sites(embark_assist::defs::site_lists *site_list) { // color_ostream_proxy out(Core::getInstance().getConsole()); auto screen = Gui::getViewscreenByType(0); @@ -1192,19 +1996,20 @@ void embark_assist::survey::survey_region_sites(embark_assist::defs::site_lists //================================================================================= void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::world_tile_data *survey_results, embark_assist::defs::site_infos *site_info, bool use_cache) { - // color_ostream_proxy out(Core::getInstance().getConsole()); + //color_ostream_proxy out(Core::getInstance().getConsole()); auto screen = Gui::getViewscreenByType(0); int16_t elevation = 0; uint16_t x = screen->location.region_pos.x; uint16_t y = screen->location.region_pos.y; - bool river_found = false; - int16_t river_elevation = 0; std::vector metals(state->max_inorganic); std::vector economics(state->max_inorganic); std::vector minerals(state->max_inorganic); + bool incursion_processing_failed = false; + df::world_data *world_data = world->world_data; if (!use_cache) { // For some reason DF scrambles these values on world tile movements (at least in Lua...). state->local_min_x = screen->location.embark_pos_min.x; @@ -1216,16 +2021,22 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * state->x = x; state->y = y; + site_info->incursions_processed = true; site_info->aquifer = false; site_info->aquifer_full = true; site_info->min_soil = 10; site_info->max_soil = 0; site_info->flat = true; - site_info->waterfall = false; + site_info->max_waterfall = 0; site_info->clay = false; site_info->sand = false; site_info->flux = false; site_info->coal = false; + site_info->blood_rain = false; + site_info->permanent_syndrome_rain = false; + site_info->temporary_syndrome_rain = false; + site_info->reanimating = false; + site_info->thralling = false; site_info->metals.clear(); site_info->economics.clear(); site_info->metals.clear(); @@ -1255,14 +2066,20 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * } if (mlt->at(i).at(k).river_present) { - if (river_found) { - if (river_elevation != mlt->at(i).at(k).river_elevation) { - site_info->waterfall = true; - } + if (i < 15 && + mlt->at(i + 1).at(k).river_present && + abs(mlt->at(i).at(k).river_elevation - mlt->at(i + 1).at(k).river_elevation) > + site_info->max_waterfall) { + site_info->max_waterfall = + abs(mlt->at(i).at(k).river_elevation - mlt->at(i + 1).at(k).river_elevation); } - else { - river_elevation = mlt->at(i).at(k).river_elevation; - river_found = true; + + if (k < 15 && + mlt->at(i).at(k + 1).river_present && + abs(mlt->at(i).at(k).river_elevation - mlt->at(i).at(k + 1).river_elevation) > + site_info->max_waterfall) { + site_info->max_waterfall = + abs(mlt->at(i).at(k).river_elevation - mlt->at(i).at(k + 1).river_elevation); } } @@ -1282,6 +2099,26 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * site_info->coal = true; } + if (survey_results->at(x).at(y).blood_rain[mlt->at(i).at(k).biome_offset]) { + site_info->blood_rain = true; + } + + if (survey_results->at(x).at(y).permanent_syndrome_rain[mlt->at(i).at(k).biome_offset]) { + site_info->permanent_syndrome_rain = true; + } + + if (survey_results->at(x).at(y).temporary_syndrome_rain[mlt->at(i).at(k).biome_offset]) { + site_info->temporary_syndrome_rain = true; + } + + if (survey_results->at(x).at(y).reanimating[mlt->at(i).at(k).biome_offset]) { + site_info->reanimating = true; + } + + if (survey_results->at(x).at(y).thralling[mlt->at(i).at(k).biome_offset]) { + site_info->thralling = true; + } + for (uint16_t l = 0; l < state->max_inorganic; l++) { metals[l] = metals[l] || mlt->at(i).at(k).metals[l]; economics[l] = economics[l] || mlt->at(i).at(k).economics[l]; @@ -1289,6 +2126,7 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * } } } + for (uint16_t l = 0; l < state->max_inorganic; l++) { if (metals[l]) { site_info->metals.push_back(l); @@ -1302,6 +2140,250 @@ void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles * site_info->minerals.push_back(l); } } + + // Take incursions into account. + + for (int8_t i = state->local_min_x; i <= state->local_max_x; i++) { + // NW corner, north row + if ((i == 0 && state->local_min_y == 0 && x - 1 >= 0 && y - 1 >= 0 && !survey_results->at(x - 1).at (y - 1).surveyed) || + (i == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed) || + (state->local_min_y == 0 && y - 1 >= 0 && !survey_results->at(x).at(y - 1).surveyed)) { + incursion_processing_failed = true; + } + else { + process_embark_incursion_mid_level_tile + (translate_corner(survey_results, + 4, + x, + y, + i, + state->local_min_y), + site_info, + survey_results, + mlt, + i, + state->local_min_y); + } + + // N edge, north row + if (state->local_min_y == 0 && y - 1 >= 0 && !survey_results->at(x).at(y - 1).surveyed) { + incursion_processing_failed = true; + } + else { + process_embark_incursion_mid_level_tile + (translate_ns_edge(survey_results, + true, + x, + y, + i, + state->local_min_y), + site_info, + survey_results, + mlt, + i, + state->local_min_y); + } + + // NE corner, north row + if ((i == 15 && state->local_min_y == 0 && x + 1 < world_data->world_width && y - 1 >= 0 && !survey_results->at(x + 1).at(y - 1).surveyed) || + (i == 15 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed) || + (state->local_min_y == 0 && y - 1 >= 0 && !survey_results->at(x).at(y - 1).surveyed)) { + incursion_processing_failed = true; + } + else { + process_embark_incursion_mid_level_tile + (translate_corner(survey_results, + 5, + x, + y, + i, + state->local_min_y), + site_info, + survey_results, + mlt, + i, + state->local_min_y); + } + + // SW corner, south row + if ((i == 0 && state->local_max_y == 15 && x - 1 >= 0 && y + 1 < world_data->world_height && !survey_results->at(x - 1).at(y + 1).surveyed) || + (i == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed) || + (state->local_max_y == 15 && y + 1 < world_data->world_height && !survey_results->at(x).at(y + 1).surveyed)) { + incursion_processing_failed = true; + } + else { + process_embark_incursion_mid_level_tile + (translate_corner(survey_results, + 7, + x, + y, + i, + state->local_max_y), + site_info, + survey_results, + mlt, + i, + state->local_max_y); + } + + // S edge, south row + if (state->local_max_y == 15 && y + 1 < world_data->world_height && !survey_results->at(x).at(y + 1).surveyed) { + incursion_processing_failed = true; + } + else { + process_embark_incursion_mid_level_tile + (translate_ns_edge(survey_results, + false, + x, + y, + i, + state->local_max_y), + site_info, + survey_results, + mlt, + i, + state->local_max_y); + } + + // SE corner south row + if ((i == 15 && state->local_max_y == 15 && x + 1 < world_data->world_width && y + 1 < world_data->world_height && !survey_results->at(x + 1).at(y + 1).surveyed) || + (i == 15 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed) || + (state->local_max_y == 15 && y + 1 < world_data->world_height && !survey_results->at(x).at(y + 1).surveyed)) { + incursion_processing_failed = true; + } + else { + process_embark_incursion_mid_level_tile + (translate_corner(survey_results, + 8, + x, + y, + i, + state->local_max_y), + site_info, + survey_results, + mlt, + i, + state->local_max_y); + } + } + + for (int8_t k = state->local_min_y; k <= state->local_max_y; k++) { + // NW corner, west side + if ((state->local_min_x == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed)) { + incursion_processing_failed = true; + } + else if (k > state->local_min_y) { // We've already covered the NW corner of the NW, with its complications. + process_embark_incursion_mid_level_tile + (translate_corner(survey_results, + 4, + x, + y, + state->local_min_x, + k), + site_info, + survey_results, + mlt, + state->local_min_x, + k); + } + + // W edge, west side + if (state->local_min_x == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed) { + incursion_processing_failed = true; + } + else { + process_embark_incursion_mid_level_tile + (translate_ns_edge(survey_results, + true, + x, + y, + state->local_min_x, + k), + site_info, + survey_results, + mlt, + state->local_min_x, + k); + } + + // SW corner, west side + if (state->local_min_x == 0 && x - 1 >= 0 && !survey_results->at(x - 1).at(y).surveyed) { + incursion_processing_failed = true; + } + else if (k < state->local_max_y) { // We've already covered the SW corner of the SW tile, with its complicatinons. + process_embark_incursion_mid_level_tile + (translate_corner(survey_results, + 7, + x, + y, + state->local_min_x, + k), + site_info, + survey_results, + mlt, + state->local_min_x, + k); + } + + // NE corner, east side + if ((state->local_max_x == 15 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed)) { + incursion_processing_failed = true; + } + else if (k > state->local_min_y) { // We've already covered the NE tile's NE corner, with its complications. + process_embark_incursion_mid_level_tile + (translate_corner(survey_results, + 5, + x, + y, + state->local_max_x, + k), + site_info, + survey_results, + mlt, + state->local_max_x, + k); + } + + // E edge, east side + if (state->local_max_x == 15 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed) { + incursion_processing_failed = true; + } + else { + process_embark_incursion_mid_level_tile + (translate_ns_edge(survey_results, + false, + x, + y, + state->local_max_x, + k), + site_info, + survey_results, + mlt, + state->local_max_x, + k); + } + + // SE corner, east side + if (state->local_max_x == 15 && x + 1 < world_data->world_width && !survey_results->at(x + 1).at(y).surveyed) { + incursion_processing_failed = true; + } + else if (k < state->local_max_y) { // We've already covered the SE tile's SE corner, with its complications. + process_embark_incursion_mid_level_tile + (translate_corner(survey_results, + 8, + x, + y, + state->local_max_x, + k), + site_info, + survey_results, + mlt, + state->local_max_x, + k); + } + } + + if (incursion_processing_failed) site_info->incursions_processed = false; } //================================================================================= diff --git a/plugins/embark-assistant/survey.h b/plugins/embark-assistant/survey.h index 03d2d9a03..14cc8a468 100644 --- a/plugins/embark-assistant/survey.h +++ b/plugins/embark-assistant/survey.h @@ -27,9 +27,52 @@ namespace embark_assist { 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_embark(embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::world_tile_data *survey_results, embark_assist::defs::site_infos *site_info, bool use_cache); diff --git a/plugins/lua/cxxrandom.lua b/plugins/lua/cxxrandom.lua index 1521c4c9e..4d2d59da9 100644 --- a/plugins/lua/cxxrandom.lua +++ b/plugins/lua/cxxrandom.lua @@ -188,7 +188,7 @@ function num_sequence:new(a,b) else error("Invalid arguments - a: " .. tostring(a) .. " and b: " .. tostring(b)) end - print("seqID:"..o.seqID) + --print("seqID:"..o.seqID) setmetatable(o,self) return o end diff --git a/plugins/lua/map-render.lua b/plugins/lua/map-render.lua new file mode 100644 index 000000000..9785c365e --- /dev/null +++ b/plugins/lua/map-render.lua @@ -0,0 +1,11 @@ +local _ENV = mkmodule('plugins.map-render') + +--[[ + + Native functions: + + * render_map_rect(x,y,z,w,h) + +--]] + +return _ENV \ No newline at end of file diff --git a/plugins/map-render.cpp b/plugins/map-render.cpp new file mode 100644 index 000000000..f89d8ab4e --- /dev/null +++ b/plugins/map-render.cpp @@ -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 &commands) +{ + auto addr =reinterpret_cast(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; +} diff --git a/plugins/mousequery.cpp b/plugins/mousequery.cpp index d9bba78b6..618252ad3 100644 --- a/plugins/mousequery.cpp +++ b/plugins/mousequery.cpp @@ -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 // (so we can clear the button down flag) auto dims = Gui::getDwarfmodeViewDims(); - int right_bound = (dims.menu_x1 > 0) ? dims.menu_x1 - 2 : gps->dimx - 2; - if (mx < 1 || mx > right_bound || my < 1 || my > gps->dimy - 2) + if (mx < 1 || mx > dims.map_x2 || my < 1 || my > dims.map_y2) return false; 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) 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); if (my < scroll_trigger_y) 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); } @@ -572,12 +571,11 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest static decltype(enabler->clock) last_t = 0; auto dims = Gui::getDwarfmodeViewDims(); - auto right_margin = (dims.menu_x1 > 0) ? dims.menu_x1 : gps->dimx; int32_t mx, my; auto mpos = get_mouse_pos(mx, my); 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; // Check if in lever binding mode @@ -683,7 +681,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest return; } - if (mx > right_margin - scroll_buffer) + if (mx > dims.map_x2 - scroll_buffer) { sendKey(interface_key::CURSOR_RIGHT); return; @@ -695,7 +693,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest return; } - if (my > gps->dimy - scroll_buffer) + if (my > dims.map_y2 - scroll_buffer) { sendKey(interface_key::CURSOR_DOWN); return; @@ -732,9 +730,9 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest if (shouldTrack()) { if (delta_t <= scroll_delay && (mx < scroll_buffer || - mx > dims.menu_x1 - scroll_buffer || + mx > dims.map_x2 - scroll_buffer || my < scroll_buffer || - my > gps->dimy - scroll_buffer)) + my > dims.map_y2 - scroll_buffer)) { return; } @@ -836,26 +834,35 @@ static command_result mousequery_cmd(color_ostream &out, vector & param else if (cmd[0] == 'p') { plugin_enabled = (state == "enable"); + out << "mousequery: plugin " << (plugin_enabled ? "enabled" : "disabled") << endl; } else if (cmd[0] == 'r') { rbutton_enabled = (state == "enable"); + out << "mousequery: rbutton " << (rbutton_enabled ? "enabled" : "disabled") << endl; } else if (cmd[0] == 't') { tracking_enabled = (state == "enable"); - if (!tracking_enabled) + if (!tracking_enabled) { + out << "mousequery: edge scrolling disabled" << endl; active_scrolling = false; + } + out << "mousequery: tracking " << (tracking_enabled ? "enabled" : "disabled") << endl; } else if (cmd[0] == 'e') { active_scrolling = (state == "enable"); - if (active_scrolling) + if (active_scrolling) { + out << "mousequery: tracking enabled" << endl; tracking_enabled = true; + } + out << "mousequery: edge scrolling " << (active_scrolling ? "enabled" : "disabled") << endl; } else if (cmd[0] == 'l') { live_view = (state == "enable"); + out << "mousequery: live view " << (live_view ? "enabled" : "disabled") << endl; } else if (cmd == "drag") { diff --git a/plugins/proto/DwarfControl.proto b/plugins/proto/DwarfControl.proto new file mode 100644 index 000000000..9bd63b8d3 --- /dev/null +++ b/plugins/proto/DwarfControl.proto @@ -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; +} diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index af971d55d..ed7557a2f 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -42,6 +42,7 @@ import "ItemdefInstrument.proto"; // RPC MovementSelectCommand : IntMessage -> EmptyMessage // RPC MiscMoveCommand : MiscMoveParams -> EmptyMessage // RPC GetLanguage : EmptyMessage -> Language +// RPC GetGameValidity : EmptyMessage -> SingleBool //We use shapes, etc, because the actual tiletypes may differ between DF versions. enum TiletypeShape @@ -201,6 +202,14 @@ enum InventoryMode Strapped = 10; } +enum ArmorLayer +{ + LAYER_UNDER = 0; + LAYER_OVER = 1; + LAYER_ARMOR = 2; + LAYER_COVER = 3; +} + message Coord { optional int32 x = 1; @@ -320,6 +329,31 @@ message Item 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 { required int32 map_x = 1; @@ -371,6 +405,9 @@ message MaterialDefinition{ optional string name = 3; optional ColorDefinition state_color = 4; //Simplifying colors to assume room temperature. optional ItemdefInstrument.InstrumentDef instrument = 5; + optional int32 up_step = 6; + optional int32 down_step = 7; + optional ArmorLayer layer = 8; } message BuildingType @@ -426,6 +463,20 @@ message InventoryItem { optional InventoryMode mode = 1; 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 @@ -453,6 +504,9 @@ message UnitDefinition optional float subpos_x = 21; optional float subpos_y = 22; optional float subpos_z = 23; + optional Coord facing = 24; + optional int32 age = 25; + repeated UnitWound wounds = 26; } message UnitList @@ -787,6 +841,7 @@ message CreatureRaw optional int32 adultsize = 9; repeated CasteRaw caste = 10; repeated TissueRaw tissues = 11; + repeated bool flags = 12; } message CreatureRawList diff --git a/plugins/proto/ui_sidebar_mode.proto b/plugins/proto/ui_sidebar_mode.proto new file mode 100644 index 000000000..b07bb1baa --- /dev/null +++ b/plugins/proto/ui_sidebar_mode.proto @@ -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; +} \ No newline at end of file diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index 1ff1cac7f..3e998ffb5 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -4,27 +4,39 @@ SET(PROJECT_SRCS remotefortressreader.cpp adventure_control.cpp building_reader.cpp + dwarf_control.cpp item_reader.cpp ) # A list of headers SET(PROJECT_HDRS adventure_control.h building_reader.h + dwarf_control.h item_reader.h df_version_int.h ) #proto files to include. SET(PROJECT_PROTO - ${CMAKE_CURRENT_SOURCE_DIR}/../proto/RemoteFortressReader.pb.cc - ${CMAKE_CURRENT_SOURCE_DIR}/../proto/AdventureControl.pb.cc - ${CMAKE_CURRENT_SOURCE_DIR}/../proto/ItemdefInstrument.pb.cc + RemoteFortressReader + AdventureControl + ItemdefInstrument + DwarfControl + ui_sidebar_mode ) -SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE) -SET_SOURCE_FILES_PROPERTIES( ${PROJECT_PROTO} PROPERTIES GENERATED TRUE) +SET(PLUGIN_PROTOS) +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) -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) SET(PROJECT_LIBS ${PROJECT_LIBS} SDL) diff --git a/plugins/remotefortressreader/building_reader.cpp b/plugins/remotefortressreader/building_reader.cpp index 5a0dd5c96..2d396980a 100644 --- a/plugins/remotefortressreader/building_reader.cpp +++ b/plugins/remotefortressreader/building_reader.cpp @@ -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_custom(-1); bld->set_id(ENUM_KEY_STR(building_type, bt)); - + bld->set_name(ENUM_ATTR_STR(building_type, name, bt)); switch (bt) { 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_custom(-1); 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) { @@ -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_custom(-1); 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; 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_custom(-1); 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) { @@ -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_custom(-1); 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; 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_custom(-1); 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; 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_custom(-1); 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; 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_custom(-1); 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; case df::enums::building_type::Hatch: diff --git a/plugins/remotefortressreader/df_version_int.h b/plugins/remotefortressreader/df_version_int.h index 35c9ed851..d83fa7548 100644 --- a/plugins/remotefortressreader/df_version_int.h +++ b/plugins/remotefortressreader/df_version_int.h @@ -1,2 +1,2 @@ #pragma once -#define DF_VERSION_INT 44002 +#define DF_VERSION_INT 44012 diff --git a/plugins/remotefortressreader/dwarf_control.cpp b/plugins/remotefortressreader/dwarf_control.cpp new file mode 100644 index 000000000..98b51175f --- /dev/null +++ b/plugins/remotefortressreader/dwarf_control.cpp @@ -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 + +using namespace DFHack; +using namespace RemoteFortressReader; +using namespace df::enums; +using namespace Gui; +using namespace df::global; + +extern std::queue 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; +} diff --git a/plugins/remotefortressreader/dwarf_control.h b/plugins/remotefortressreader/dwarf_control.h new file mode 100644 index 000000000..b16a4365d --- /dev/null +++ b/plugins/remotefortressreader/dwarf_control.h @@ -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 + + diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp index 3884d269f..620e06310 100644 --- a/plugins/remotefortressreader/item_reader.cpp +++ b/plugins/remotefortressreader/item_reader.cpp @@ -26,7 +26,13 @@ #include "df/item_statuest.h" #include "df/item_threadst.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_pantsst.h" +#include "df/itemdef_shieldst.h" +#include "df/itemdef_shoesst.h" #include "df/itemdef_toolst.h" #include "df/itemimprovement.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); } send_instrument->set_description(DF2UTF(instrument->description)); - break; } + break; case df::enums::item_type::TOOL: { VIRTUAL_CAST_VAR(tool, df::itemdef_toolst, item); 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: break; } diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 7dced3b9f..cd9ebd87e 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,5 +1,5 @@ #include "df_version_int.h" -#define RFR_VERSION "0.19.1" +#define RFR_VERSION "0.20.2" #include #include @@ -63,7 +63,7 @@ #include "df/flow_guide_item_cloudst.h" #include "df/graphic.h" #include "df/historical_figure.h" - +#include "df/identity.h" #include "df/job.h" #include "df/job_type.h" #include "df/job_item.h" @@ -92,7 +92,10 @@ #include "df/ui.h" #include "df/unit.h" #include "df/unit_inventory_item.h" +#include "df/unit_wound.h" #include "df/viewscreen_choose_start_sitest.h" +#include "df/viewscreen_loadgamest.h" +#include "df/viewscreen_savegamest.h" #include "df/vehicle.h" #include "df/world.h" #include "df/world_data.h" @@ -117,6 +120,7 @@ #include "adventure_control.h" #include "building_reader.h" +#include "dwarf_control.h" #include "item_reader.h" 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 CopyScreen(color_ostream &stream, const EmptyMessage *in, ScreenCapture *out); 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 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 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); @@ -208,54 +210,6 @@ command_result loadArtImageChunk(color_ostream &out, vector & parameter return CR_OK; } -command_result dump_bp_mods(color_ostream &out, vector & 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 ¶meters) { 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. DFhackCExport command_result plugin_init(color_ostream &out, std::vector &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( "load-art-image-chunk", @@ -329,6 +273,9 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &) svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE); svc->addFunction("MiscMoveCommand", MiscMoveCommand, 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; } @@ -1506,7 +1453,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in goto ItsAir; } } - ItsAir: + ItsAir: if (block->flows.size() > 0) nonAir = true; if (nonAir || firstBlock) @@ -1556,9 +1503,9 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in segment_passed = 0; // 'rotate' directions - int buffer = di; + int filename = di; di = -dj; - dj = buffer; + dj = filename; // increase segment length if necessary if (dj == 0) { @@ -1702,6 +1649,24 @@ static command_result GetUnitList(color_ostream &stream, const EmptyMessage *in, 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) { 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) 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()); send_unit->set_flags1(unit->flags1.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 sent_item = send_unit->add_inventory(); 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); } @@ -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_y(item->pos_y / 100000.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; } @@ -1847,6 +1875,14 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, } #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_y(y); 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); } + FOR_ENUM_ITEMS(creature_raw_flags, flag) + { + send_creature->add_flags(orig_creature->flags.is_set(flag)); + } } return CR_OK; @@ -2893,94 +2933,26 @@ static command_result PassKeyboardEvent(color_ostream &stream, const KeyboardEve return CR_OK; } -static 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 = &(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) +static command_result GetPauseState(color_ostream &stream, const EmptyMessage *in, SingleBool *out) { - DFHack::World::SetPauseState(in->value()); + out->set_value(World::ReadPauseState()); 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(viewScreen)) + { + out->set_value(false); + return CR_OK; + } + else if (strict_virtual_cast(viewScreen)) + { + out->set_value(false); + return CR_OK; + } + out->set_value(true); return CR_OK; } diff --git a/plugins/ruby/unit.rb b/plugins/ruby/unit.rb index 848e0f471..b27f2f0c3 100644 --- a/plugins/ruby/unit.rb +++ b/plugins/ruby/unit.rb @@ -134,7 +134,7 @@ module DFHack # does not include ghosts / wildlife def unit_ishostile(u) # 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) when :Berserk, :Undead, :Hostile, :Invader, :Underworld @@ -152,6 +152,8 @@ module DFHack case unit_checkdiplomacy_hf_ent(histfig, group) when 4, 5 true + else + false end elsif diplo = u.civ_tg.unknown1b.diplomacy.binsearch(df.ui.group_id, :group_id) diff --git a/plugins/skeleton/skeleton.cpp b/plugins/skeleton/skeleton.cpp index ea2f4fb4a..c06ca26cb 100644 --- a/plugins/skeleton/skeleton.cpp +++ b/plugins/skeleton/skeleton.cpp @@ -6,6 +6,9 @@ #include #include +// If you need to save data per-world: +//#include "modules/Persistence.h" + // DF data structure definition headers #include "DataDefs.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. command_result skeleton (color_ostream &out, std::vector & parameters) { diff --git a/plugins/stonesense b/plugins/stonesense index 4a0f63e04..03e96477c 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 4a0f63e044d02532c948d67780dfd128fbd6d043 +Subproject commit 03e96477ca84e42c87db93bd2d781c73687795a8 diff --git a/scripts b/scripts index a242a7cd8..8ef283377 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit a242a7cd81ae7046146f86c3103360449e1d9ca8 +Subproject commit 8ef283377c9830fb932ea888d89b551873af36cf diff --git a/travis/check-rpc.py b/travis/check-rpc.py index 7b771ee67..ecddafa9f 100644 --- a/travis/check-rpc.py +++ b/travis/check-rpc.py @@ -67,9 +67,9 @@ for plugin_name in actual: io = methods[m] if m in expected[plugin_name]: if expected[plugin_name][m] != io: - wrong.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + wrong.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) else: - missing.add('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) + missing.append('// RPC ' + m + ' : ' + io[0] + ' -> ' + io[1]) if len(missing) > 0: 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: io = methods[m] 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: print('Incorrect documentation for ' + ('core' if plugin_name == '' else 'plugin "' + plugin_name + '"') + ' proto files. Remove the following lines:') diff --git a/travis/download-df.sh b/travis/download-df.sh index 20dc3dbd4..4eae6f9b9 100755 --- a/travis/download-df.sh +++ b/travis/download-df.sh @@ -10,31 +10,37 @@ cd "$(dirname "$0")" echo "DF_VERSION: $DF_VERSION" echo "DF_FOLDER: $DF_FOLDER" mkdir -p "$DF_FOLDER" -cd "$DF_FOLDER" +# back out of df_linux +cd "$DF_FOLDER/.." if [ -f receipt ]; then if [ "$selfmd5" != "$(cat receipt)" ]; then echo "download-df.sh changed; removing DF" + rm receipt else echo "Already downloaded $DF_VERSION" - exit 0 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) -patch=$(echo "$DF_VERSION" | cut -d. -f3) -url="http://www.bay12games.com/dwarves/df_${minor}_${patch}_linux.tar.bz2" +rm -rf df_linux +mkdir df_linux -echo Downloading -wget "$url" -O "$tardest" echo Extracting -tar xf "$tardest" --strip-components=1 +tar xf "$tardest" --strip-components=1 -C df_linux echo Changing settings -echo '' >> "$DF_FOLDER/data/init/init.txt" -echo '[PRINT_MODE:TEXT]' >> "$DF_FOLDER/data/init/init.txt" -echo '[SOUND:NO]' >> "$DF_FOLDER/data/init/init.txt" +echo '' >> "df_linux/data/init/init.txt" +echo '[PRINT_MODE:TEXT]' >> "df_linux/data/init/init.txt" +echo '[SOUND:NO]' >> "df_linux/data/init/init.txt" echo Done echo "$selfmd5" > receipt +ls diff --git a/travis/get-df-version.sh b/travis/get-df-version.sh index 13d317d2e..24386252d 100755 --- a/travis/get-df-version.sh +++ b/travis/get-df-version.sh @@ -1,4 +1,4 @@ #!/bin/sh cd "$(dirname "$0")" 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\.]+/' diff --git a/travis/run-tests.py b/travis/run-tests.py index ae48074b5..a66320f37 100644 --- a/travis/run-tests.py +++ b/travis/run-tests.py @@ -14,6 +14,9 @@ os.chdir(sys.argv[1]) if os.path.exists(test_stage): os.remove(test_stage) +print(os.getcwd()) +print(os.listdir('.')) + tries = 0 while True: tries += 1 @@ -33,3 +36,5 @@ while True: process = subprocess.Popen([dfhack], stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE) process.communicate() + if process.returncode != 0: + print('DF exited with ' + repr(process.returncode))