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 4d122bb51..2b78fd700 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -2848,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 ef2fc825d..47dcaa58b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -37,17 +37,74 @@ 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 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 wheather or not the game is currently saving or loading, for utilities to check if it's safe to read from DF. - - guestimate unit facing direction, and position within tiles. - - get unit age. - - get unit wounds. + - 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 -- Fixed some OpenGL build issues with `stonesense` +- `stonesense`: fixed some OpenGL build issues on Linux # 0.44.12-r2 diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 5df51003b..14be2b555 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -131,6 +131,7 @@ set(MODULE_HEADERS 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 @@ set(MODULE_SOURCES modules/Materials.cpp modules/Notes.cpp modules/Once.cpp + modules/Persistence.cpp modules/Random.cpp modules/Renderer.cpp modules/Screen.cpp diff --git a/library/Core.cpp b/library/Core.cpp index 15d4be72c..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 @@ -1969,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; @@ -1990,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); @@ -2008,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); } } @@ -2028,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; } @@ -2271,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 ) 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/include/Core.h b/library/include/Core.h index 85bfdbd3e..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 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/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/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 c192f9798..4388fbfb8 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit c192f9798b5d134e777b1f37c8ebc0df4bd53bda +Subproject commit 4388fbfb8f51be41777406c6e7c518f738c195c7 diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 76e5c1c56..68fd44191 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/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..1eed98593 100644 --- a/plugins/embark-assistant/embark-assistant.cpp +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -62,7 +62,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); @@ -224,7 +227,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 +256,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 +294,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 7b3d385cb..a3975a980 100644 --- a/plugins/embark-assistant/matcher.cpp +++ b/plugins/embark-assistant/matcher.cpp @@ -12,6 +12,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" @@ -24,33 +25,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; @@ -61,15 +51,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; @@ -78,18 +502,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) { @@ -123,17 +547,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; } @@ -143,42 +573,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 @@ -186,8 +627,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 @@ -195,24 +636,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 @@ -220,21 +661,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 @@ -242,50 +683,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 @@ -295,11 +1031,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); @@ -314,107 +1050,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; } @@ -557,19 +1293,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 @@ -1041,7 +1768,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 @@ -1529,7 +2256,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 68cd3597f..3574219bc 100644 --- a/plugins/embark-assistant/survey.cpp +++ b/plugins/embark-assistant/survey.cpp @@ -51,6 +51,7 @@ #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" @@ -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; @@ -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/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/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/scripts b/scripts index 189d0d8f6..8ef283377 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 189d0d8f63b5d34ac3f779254b6ab5ff4763459a +Subproject commit 8ef283377c9830fb932ea888d89b551873af36cf