Merge branch 'develop' into remote_reader

develop
JapaMala 2019-09-08 15:30:14 -05:00
commit 099ae66be8
22 changed files with 3169 additions and 513 deletions

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

@ -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

@ -131,6 +131,7 @@ include/modules/Maps.h
include/modules/Materials.h
include/modules/Notes.h
include/modules/Once.h
include/modules/Persistence.h
include/modules/Random.h
include/modules/Renderer.h
include/modules/Screen.h
@ -157,6 +158,7 @@ modules/Maps.cpp
modules/Materials.cpp
modules/Notes.cpp
modules/Once.cpp
modules/Persistence.cpp
modules/Random.cpp
modules/Renderer.cpp
modules/Screen.cpp

@ -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<df::viewscreen_loadgamest>(screen) ||
strict_virtual_cast<df::viewscreen_savegamest>(screen);
// save data (do this before updating last_world_data_ptr and triggering unload events)
if ((df::global::ui->main.autosave_request && !d->last_autosave_request) ||
(is_load_save && !d->was_load_save && strict_virtual_cast<df::viewscreen_savegamest>(screen)))
{
doSaveData(out);
}
// detect if the game was loaded or unloaded in the meantime
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 )

@ -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);

@ -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

@ -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

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

@ -33,6 +33,7 @@ distribution.
#include "Export.h"
#include "Module.h"
#include "modules/Persistence.h"
#include <ostream>
#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);
}

@ -0,0 +1,420 @@
/*
https://github.com/peterix/dfhack
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product documentation
would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#include "Internal.h"
#include <array>
#include <map>
#include <json/json.h>
#include "Core.h"
#include "DataDefs.h"
#include "modules/Persistence.h"
#include "modules/World.h"
#include "df/historical_figure.h"
using namespace DFHack;
static std::vector<std::shared_ptr<Persistence::LegacyData>> legacy_data;
static std::multimap<std::string, size_t> index_cache;
struct Persistence::LegacyData
{
const std::string key;
std::string str_value;
std::array<int, PersistentDataItem::NumInts> int_values;
explicit LegacyData(const std::string &key) : key(key)
{
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
int_values.at(i) = -1;
}
}
explicit LegacyData(Json::Value &json) : key(json["k"].asString())
{
str_value = json["s"].asString();
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
int_values.at(i) = json["i"][i].asInt();
}
}
explicit LegacyData(const df::language_name &name) : key(name.first_name)
{
str_value = name.nickname;
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
int_values.at(i) = name.words[i];
}
}
Json::Value toJSON()
{
Json::Value json(Json::objectValue);
json["k"] = key;
json["s"] = str_value;
Json::Value ints(Json::arrayValue);
for (int i = 0; i < PersistentDataItem::NumInts; i++)
{
ints[i] = int_values.at(i);
}
json["i"] = std::move(ints);
return std::move(json);
}
};
const std::string &PersistentDataItem::key() const
{
CHECK_INVALID_ARGUMENT(isValid());
return data->key;
}
std::string &PersistentDataItem::val()
{
CHECK_INVALID_ARGUMENT(isValid());
return data->str_value;
}
const std::string &PersistentDataItem::val() const
{
CHECK_INVALID_ARGUMENT(isValid());
return data->str_value;
}
int &PersistentDataItem::ival(int i)
{
CHECK_INVALID_ARGUMENT(isValid());
CHECK_INVALID_ARGUMENT(i >= 0 && i < NumInts);
return data->int_values.at(i);
}
int PersistentDataItem::ival(int i) const
{
CHECK_INVALID_ARGUMENT(isValid());
CHECK_INVALID_ARGUMENT(i >= 0 && i < NumInts);
return data->int_values.at(i);
}
bool PersistentDataItem::isValid() const
{
if (data == nullptr)
return false;
CoreSuspender suspend;
if (legacy_data.size() <= index)
return false;
return legacy_data.at(index) == data;
}
void Persistence::Internal::clear()
{
CoreSuspender suspend;
legacy_data.clear();
index_cache.clear();
}
void Persistence::Internal::save()
{
CoreSuspender suspend;
Json::Value json(Json::arrayValue);
for (size_t i = 0; i < legacy_data.size(); i++)
{
if (legacy_data.at(i) != nullptr)
{
while (json.size() < i)
{
json[json.size()] = Json::Value();
}
json[int(i)] = legacy_data.at(i)->toJSON();
}
}
auto file = writeSaveData("legacy-data");
file << json;
}
static void convertHFigs()
{
auto &figs = df::historical_figure::get_vector();
auto src = figs.begin();
while (src != figs.end() && (*src)->id > -100)
{
++src;
}
if (src == figs.end())
{
return;
}
auto dst = src;
while (src != figs.end())
{
auto fig = *src;
if (fig->id > -100)
{
*dst = *src;
++dst;
}
else
{
if (fig->name.has_name && !fig->name.first_name.empty())
{
size_t index = size_t(-fig->id - 100);
if (legacy_data.size() <= index)
{
legacy_data.resize(index + 1);
}
legacy_data.at(index) = std::shared_ptr<Persistence::LegacyData>(new Persistence::LegacyData(fig->name));
}
delete fig;
}
++src;
}
figs.erase(dst, figs.end());
}
void Persistence::Internal::load()
{
CoreSuspender suspend;
clear();
auto file = readSaveData("legacy-data");
Json::Value json;
try
{
file >> json;
}
catch (std::exception &)
{
// empty file?
}
if (json.isArray())
{
legacy_data.resize(json.size());
for (size_t i = 0; i < legacy_data.size(); i++)
{
if (json[int(i)].isObject())
{
legacy_data.at(i) = std::shared_ptr<LegacyData>(new LegacyData(json[int(i)]));
}
}
}
convertHFigs();
for (size_t i = 0; i < legacy_data.size(); i++)
{
if (legacy_data.at(i) == nullptr)
{
continue;
}
index_cache.insert(std::make_pair(legacy_data.at(i)->key, i));
}
}
PersistentDataItem Persistence::addItem(const std::string &key)
{
if (key.empty() || !Core::getInstance().isWorldLoaded())
return PersistentDataItem();
CoreSuspender suspend;
size_t index = 0;
while (index < legacy_data.size() && legacy_data.at(index) != nullptr)
{
index++;
}
auto ptr = std::shared_ptr<LegacyData>(new LegacyData(key));
if (index == legacy_data.size())
{
legacy_data.push_back(ptr);
}
else
{
legacy_data.at(index) = ptr;
}
index_cache.insert(std::make_pair(key, index));
return PersistentDataItem(index, ptr);
}
PersistentDataItem Persistence::getByKey(const std::string &key, bool *added)
{
CoreSuspender suspend;
auto it = index_cache.find(key);
if (added)
{
*added = it == index_cache.end();
}
if (it != index_cache.end())
{
return PersistentDataItem(it->second, legacy_data.at(it->second));
}
if (!added)
{
return PersistentDataItem();
}
return addItem(key);
}
PersistentDataItem Persistence::getByIndex(size_t index)
{
CoreSuspender suspend;
if (index < legacy_data.size() && legacy_data.at(index) != nullptr)
{
return PersistentDataItem(index, legacy_data.at(index));
}
return PersistentDataItem();
}
bool Persistence::deleteItem(const PersistentDataItem &item)
{
CoreSuspender suspend;
if (!item.isValid())
{
return false;
}
size_t index = item.get_index();
auto range = index_cache.equal_range(item.key());
for (auto it = range.first; it != range.second; ++it)
{
if (it->second == index)
{
index_cache.erase(it);
break;
}
}
legacy_data.at(index) = nullptr;
return true;
}
void Persistence::getAll(std::vector<PersistentDataItem> &vec)
{
vec.clear();
CoreSuspender suspend;
for (size_t i = 0; i < legacy_data.size(); i++)
{
if (legacy_data.at(i) != nullptr)
{
vec.push_back(PersistentDataItem(i, legacy_data.at(i)));
}
}
}
void Persistence::getAllByKeyRange(std::vector<PersistentDataItem> &vec, const std::string &min, const std::string &max)
{
vec.clear();
CoreSuspender suspend;
auto begin = index_cache.lower_bound(min);
auto end = index_cache.lower_bound(max);
for (auto it = begin; it != end; ++it)
{
vec.push_back(PersistentDataItem(it->second, legacy_data.at(it->second)));
}
}
void Persistence::getAllByKey(std::vector<PersistentDataItem> &vec, const std::string &key)
{
vec.clear();
CoreSuspender suspend;
auto range = index_cache.equal_range(key);
for (auto it = range.first; it != range.second; ++it)
{
vec.push_back(PersistentDataItem(it->second, legacy_data.at(it->second)));
}
}
static std::string filterSaveFileName(std::string s)
{
for (auto &ch : s)
{
if (!isalnum(ch) && ch != '-' && ch != '_')
{
ch = '_';
}
}
return s;
}
static std::string getSaveFilePath(const std::string &world, const std::string &name)
{
return "data/save/" + world + "/dfhack-" + filterSaveFileName(name) + ".dat";
}
#if defined(__GNUC__) && __GNUC__ < 5
// file stream move constructors are missing in libstdc++ before version 5.
#define FSTREAM(x) Persistence::gcc_4_fstream_shim<x>
#else
#define FSTREAM(x) x
#endif
FSTREAM(std::ifstream) Persistence::readSaveData(const std::string &name)
{
if (!Core::getInstance().isWorldLoaded())
{
// No world loaded - return unopened stream.
return FSTREAM(std::ifstream)();
}
return FSTREAM(std::ifstream)(getSaveFilePath(World::ReadWorldFolder(), name));
}
FSTREAM(std::ofstream) Persistence::writeSaveData(const std::string &name)
{
if (!Core::getInstance().isWorldLoaded())
{
// No world loaded - return unopened stream.
return FSTREAM(std::ofstream)();
}
return FSTREAM(std::ofstream)(getSaveFilePath("current", name));
}
#undef FSTREAM

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

@ -1 +1 @@
Subproject commit c192f9798b5d134e777b1f37c8ebc0df4bd53bda
Subproject commit 4388fbfb8f51be41777406c6e7c518f738c195c7

@ -3,6 +3,7 @@
#include <array>
#include <string>
#include <vector>
#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<bool> metals;
std::vector<bool> economics;
std::vector<bool> minerals;
mid_level_tile north_row[16];
mid_level_tile south_row[16];
mid_level_tile west_column[16];
mid_level_tile east_column[16];
uint8_t north_corner_selection[16]; // 0 - 3. For some reason DF stores everything needed for incursion
uint8_t west_corner_selection[16]; // detection in 17:th row/colum data in the region details except
// this info, so we have to go to neighboring world tiles to fetch it.
df::world_region_type region_type[16][16]; // Required for incursion override detection. We could store only the
// edges, but storing it for every tile allows for a unified fetching
// logic.
};
struct geo_datum {
@ -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<uint16_t> metals;
std::vector<uint16_t> economics;
std::vector<uint16_t> 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;

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

@ -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<int8_t>(i) });
break;
case fields::waterfall:
state->finder_list.push_back({ "Waterfall", static_cast<int8_t>(i) });
case fields::min_waterfall:
state->finder_list.push_back({ "Min Waterfall Drop", static_cast<int8_t>(i) });
break;
case fields::flat:
@ -1184,9 +1198,8 @@ namespace embark_assist {
static_cast<embark_assist::defs::river_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
break;
case fields::waterfall:
finder.waterfall =
static_cast<embark_assist::defs::yes_no_ranges>(state->ui[static_cast<uint8_t>(i)]->current_value);
case fields::min_waterfall:
finder.min_waterfall = state->ui[static_cast<uint8_t>(i)]->current_value;
break;
case fields::flat:

@ -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();

File diff suppressed because it is too large Load Diff

@ -331,6 +331,10 @@ void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs:
void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) {
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) {

File diff suppressed because it is too large Load Diff

@ -27,9 +27,52 @@ namespace embark_assist {
df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset);
df::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);

@ -6,6 +6,9 @@
#include <Export.h>
#include <PluginManager.h>
// 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 <std::string> & parameters)
{

@ -1 +1 @@
Subproject commit 189d0d8f63b5d34ac3f779254b6ab5ff4763459a
Subproject commit 8ef283377c9830fb932ea888d89b551873af36cf