Merge remote-tracking branch 'DFHack/develop' into remote_control

develop
Japa 2018-02-13 21:29:09 +05:30
commit 60e9839f63
45 changed files with 6605 additions and 156 deletions

@ -3,13 +3,17 @@ language: cpp
cache: cache:
pip: true pip: true
directories: directories:
- $HOME/DF-travis
- $HOME/lua53 - $HOME/lua53
addons: addons:
apt: apt:
packages: &default_packages packages: &default_packages
- libsdl-image1.2-dev
- libsdl-ttf2.0-dev
- libsdl1.2-dev
- libxml-libxml-perl - libxml-libxml-perl
- libxml-libxslt-perl - libxml-libxslt-perl
- zlib1g-dev:i386 - zlib1g-dev
matrix: matrix:
include: include:
- env: GCC_VERSION=4.8 - env: GCC_VERSION=4.8
@ -19,11 +23,15 @@ matrix:
- ubuntu-toolchain-r-test - ubuntu-toolchain-r-test
packages: packages:
- *default_packages - *default_packages
- gcc-4.8-multilib - gcc-4.8
- g++-4.8-multilib - g++-4.8
before_install: before_install:
- export DF_VERSION=$(sh travis/get-df-version.sh)
- export DF_FOLDER="$HOME/DF-travis/$DF_VERSION"
- pip install --user "sphinx==1.4" "requests[security]" - pip install --user "sphinx==1.4" "requests[security]"
- sh travis/build-lua.sh - sh travis/build-lua.sh
- sh travis/download-df.sh
- echo "export DFHACK_HEADLESS=1" >> "$HOME/.dfhackrc"
script: script:
- export PATH="$PATH:$HOME/lua53/bin" - export PATH="$PATH:$HOME/lua53/bin"
- git tag tmp-travis-build - git tag tmp-travis-build
@ -37,8 +45,12 @@ script:
- python travis/script-syntax.py --ext=rb --cmd="ruby -c" - python travis/script-syntax.py --ext=rb --cmd="ruby -c"
- mkdir build-travis - mkdir build-travis
- cd build-travis - cd build-travis
- cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DBUILD_DOCS:BOOL=ON - cmake .. -DCMAKE_C_COMPILER=gcc-$GCC_VERSION -DCMAKE_CXX_COMPILER=g++-$GCC_VERSION -DDFHACK_BUILD_ARCH=64 -DBUILD_DOCS:BOOL=ON -DCMAKE_INSTALL_PREFIX="$DF_FOLDER"
- make -j3 - make -j3 install
- mv "$DF_FOLDER"/dfhack.init-example "$DF_FOLDER"/dfhack.init
- cd ..
- cp travis/dfhack_travis.init "$DF_FOLDER"/
- python travis/run-tests.py "$DF_FOLDER"
notifications: notifications:
email: false email: false
irc: irc:

@ -36,6 +36,33 @@ Changelog
.. contents:: .. contents::
:depth: 2 :depth: 2
DFHack 0.44.05-r2
=================
New Plugins
-----------
- `embark-assistant`: adds more information and features to embark screen
New Scripts
-----------
- `adv-fix-sleepers`: fixes units in adventure mode who refuse to wake up (:bug:`6798`)
- `hermit`: blocks caravans, migrants, diplomats (for hermit challenge)
New Features
------------
- With ``PRINT_MODE:TEXT``, setting the ``DFHACK_HEADLESS`` environment variable
will hide DF's display and allow the console to be used normally. (Note that
this is intended for testing and is not very useful for actual gameplay.)
Fixes
-----
- `devel/inject-raws`: fixed gloves and shoes (old typo causing errors)
- `view-item-info`: fixed an error with some shields
Misc Improvements
-----------------
- `autochop`: can now exclude trees with fruit,
DFHack 0.44.05-r1 DFHack 0.44.05-r1
================= =================

@ -31,6 +31,7 @@ Clément Vuchener cvuchener
Dan Amlund danamlund Dan Amlund danamlund
David Corbett dscorbett David Corbett dscorbett
David Seguin dseguin David Seguin dseguin
David Timm dtimm
Deon Deon
DoctorVanGogh DoctorVanGogh DoctorVanGogh DoctorVanGogh
Donald Ruegsegger hashaash Donald Ruegsegger hashaash

@ -430,6 +430,45 @@ Other init files
directory, will be run when any world or that save is loaded. directory, will be run when any world or that save is loaded.
Environment variables
=====================
DFHack's behavior can be adjusted with some environment variables. For example,
on UNIX-like systems::
DFHACK_SOME_VAR=1 ./dfhack
- ``DFHACK_PORT``: the port to use for the RPC server (used by ``dfhack-run``
and `remotefortressreader` among others) instead of the default ``5000``. As
with the default, if this port cannot be used, the server is not started.
- ``DFHACK_DISABLE_CONSOLE``: if set, the DFHack console is not set up. This is
the default behavior if ``PRINT_MODE:TEXT`` is set in ``data/init/init.txt``.
Intended for situations where DFHack cannot run in a terminal window.
- ``DFHACK_HEADLESS``: if set, and ``PRINT_MODE:TEXT`` is set, DF's display will
be hidden, and the console will be started unless ``DFHACK_DISABLE_CONSOLE``
is also set. Intended for non-interactive gameplay only.
- ``DFHACK_NO_GLOBALS``, ``DFHACK_NO_VTABLES``: ignores all global or vtable
addresses in ``symbols.xml``, respectively. Intended for development use -
e.g. to make sure tools do not crash when these addresses are missing.
- ``DFHACK_NO_DEV_PLUGINS``: if set, any plugins from the plugins/devel folder
that are built and installed will not be loaded on startup.
- ``DFHACK_LOG_MEM_RANGES`` (macOS only): if set, logs memory ranges to
``stderr.log``. Note that `devel/lsmem` can also do this.
Other (non-DFHack-specific) variables that affect DFHack:
- ``TERM``: if this is set to ``dumb`` or ``cons25`` on \*nix, the console will
not support any escape sequences (arrow keys, etc.).
- ``LANG``, ``LC_CTYPE``: if either of these contain "UTF8" or "UTF-8" (not case
sensitive), ``DF2CONSOLE()`` will produce UTF-8-encoded text. Note that this
should be the case in most UTF-8-capable \*nix terminal emulators already.
Miscellaneous Notes Miscellaneous Notes
=================== ===================
This section is for odd but important notes that don't fit anywhere else. This section is for odd but important notes that don't fit anywhere else.

@ -526,6 +526,18 @@ nopause
Disables pausing (both manual and automatic) with the exception of pause forced Disables pausing (both manual and automatic) with the exception of pause forced
by `reveal` ``hell``. This is nice for digging under rivers. by `reveal` ``hell``. This is nice for digging under rivers.
.. _embark-assistant:
embark-assistant
================
This plugin provides embark site selection help. It has to be run with the
``embark-assistant`` command while the pre-embark screen is displayed and shows
extended (and correct(?)) resource information for the embark rectangle as well
as normally undisplayed sites in the current embark region. It also has a site
selection tool with more options than DF's vanilla search tool. For detailed
help invoke the in game info screen. Requires 42 lines to display properly.
.. _embark-tools: .. _embark-tools:
embark-tools embark-tools

@ -57,6 +57,7 @@ SET(MAIN_SOURCES
Core.cpp Core.cpp
ColorText.cpp ColorText.cpp
DataDefs.cpp DataDefs.cpp
Error.cpp
VTableInterpose.cpp VTableInterpose.cpp
LuaWrapper.cpp LuaWrapper.cpp
LuaTypes.cpp LuaTypes.cpp
@ -319,7 +320,7 @@ ADD_DEPENDENCIES(dfhack-version git-describe)
ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES}) ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES})
ADD_DEPENDENCIES(dfhack generate_headers generate_proto_core) ADD_DEPENDENCIES(dfhack generate_headers generate_proto_core)
ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${PROJECT_PROTO_SRCS}) ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp Error.cpp ${PROJECT_PROTO_SRCS})
ADD_DEPENDENCIES(dfhack-client dfhack) ADD_DEPENDENCIES(dfhack-client dfhack)
ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp)

@ -81,6 +81,10 @@ using namespace DFHack;
#include "SDL_events.h" #include "SDL_events.h"
#ifdef LINUX_BUILD
#include <dlfcn.h>
#endif
using namespace tthread; using namespace tthread;
using namespace df::enums; using namespace df::enums;
using df::global::init; using df::global::init;
@ -1638,7 +1642,24 @@ bool Core::Init()
cerr << "Initializing Console.\n"; cerr << "Initializing Console.\n";
// init the console. // init the console.
bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT)); bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT));
if (is_text_mode || getenv("DFHACK_DISABLE_CONSOLE")) bool is_headless = bool(getenv("DFHACK_HEADLESS"));
if (is_headless)
{
#ifdef LINUX_BUILD
auto endwin = (int(*)(void))dlsym(RTLD_DEFAULT, "endwin");
if (endwin)
{
endwin();
}
else
{
cerr << "endwin(): bind failed" << endl;
}
#else
cerr << "Headless mode not supported on Windows" << endl;
#endif
}
if ((is_text_mode && !is_headless) || getenv("DFHACK_DISABLE_CONSOLE"))
{ {
con.init(true); con.init(true);
cerr << "Console is not available. Use dfhack-run to send commands.\n"; cerr << "Console is not available. Use dfhack-run to send commands.\n";
@ -1718,7 +1739,7 @@ bool Core::Init()
HotkeyMutex = new mutex(); HotkeyMutex = new mutex();
HotkeyCond = new condition_variable(); HotkeyCond = new condition_variable();
if (!is_text_mode) if (!is_text_mode || is_headless)
{ {
cerr << "Starting IO thread.\n"; cerr << "Starting IO thread.\n";
// create IO thread // create IO thread

@ -0,0 +1,43 @@
#include "Error.h"
#include "MiscUtils.h"
using namespace DFHack::Error;
inline std::string safe_str(const char *s)
{
return s ? s : "(NULL)";
}
NullPointer::NullPointer(const char *varname)
:All("NULL pointer: " + safe_str(varname)),
varname(varname)
{}
InvalidArgument::InvalidArgument(const char *expr)
:All("Invalid argument; expected: " + safe_str(expr)),
expr(expr)
{}
VTableMissing::VTableMissing(const char *name)
:All("Missing vtable address: " + safe_str(name)),
name(name)
{}
SymbolsXmlParse::SymbolsXmlParse(const char* desc, int id, int row, int col)
:AllSymbols(stl_sprintf("error %d: %s, at row %d col %d", id, desc, row, col)),
desc(safe_str(desc)), id(id), row(row), col(col)
{}
SymbolsXmlBadAttribute::SymbolsXmlBadAttribute(const char *attr)
:AllSymbols("attribute is either missing or invalid: " + safe_str(attr)),
attr(safe_str(attr))
{}
SymbolsXmlNoRoot::SymbolsXmlNoRoot()
:AllSymbols("no root element")
{}
SymbolsXmlUnderspecifiedEntry::SymbolsXmlUnderspecifiedEntry(const char *where)
:AllSymbols("Underspecified symbol file entry, each entry needs to set both the name attribute and have a value. parent: " + safe_str(where)),
where(safe_str(where))
{}

@ -88,6 +88,10 @@ DFhackCExport int SDL_PollEvent(SDL::Event* event)
struct WINDOW; struct WINDOW;
DFhackCExport int wgetch(WINDOW *win) DFhackCExport int wgetch(WINDOW *win)
{ {
if (getenv("DFHACK_HEADLESS"))
{
return 0;
}
static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch"); static int (*_wgetch)(WINDOW * win) = (int (*)( WINDOW * )) dlsym(RTLD_NEXT, "wgetch");
if(!_wgetch) if(!_wgetch)
{ {

@ -1076,20 +1076,8 @@ int LuaWrapper::method_wrapper_core(lua_State *state, function_identity_base *id
try { try {
id->invoke(state, 1); id->invoke(state, 1);
} }
catch (Error::NullPointer &e) { catch (Error::All &e) {
const char *vn = e.varname(); field_error(state, UPVAL_METHOD_NAME, e.what(), "invoke");
std::string tmp = stl_sprintf("NULL pointer: %s", vn ? vn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
}
catch (Error::InvalidArgument &e) {
const char *vn = e.expr();
std::string tmp = stl_sprintf("Invalid argument; expected: %s", vn ? vn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
}
catch (Error::VTableMissing &e) {
const char *cn = e.name();
std::string tmp = stl_sprintf("Missing vtable address: %s", cn ? cn : "?");
field_error(state, UPVAL_METHOD_NAME, tmp.c_str(), "invoke");
} }
catch (std::exception &e) { catch (std::exception &e) {
std::string tmp = stl_sprintf("C++ exception: %s", e.what()); std::string tmp = stl_sprintf("C++ exception: %s", e.what());
@ -1107,17 +1095,8 @@ int Lua::CallWithCatch(lua_State *state, int (*fn)(lua_State*), const char *cont
try { try {
return fn(state); return fn(state);
} }
catch (Error::NullPointer &e) { catch (Error::All &e) {
const char *vn = e.varname(); return luaL_error(state, "%s: %s", context, e.what());
return luaL_error(state, "%s: NULL pointer: %s", context, vn ? vn : "?");
}
catch (Error::InvalidArgument &e) {
const char *vn = e.expr();
return luaL_error(state, "%s: Invalid argument; expected: %s", context, vn ? vn : "?");
}
catch (Error::VTableMissing &e) {
const char *cn = e.name();
return luaL_error(state, "%s: Missing vtable address: %s", context, cn ? cn : "?");
} }
catch (std::exception &e) { catch (std::exception &e) {
return luaL_error(state, "%s: C++ exception: %s", context, e.what()); return luaL_error(state, "%s: C++ exception: %s", context, e.what());

@ -25,7 +25,6 @@ distribution.
#include "Internal.h" #include "Internal.h"
#include "Export.h" #include "Export.h"
#include "MiscUtils.h" #include "MiscUtils.h"
#include "Error.h"
#ifndef LINUX_BUILD #ifndef LINUX_BUILD
#include <Windows.h> #include <Windows.h>
@ -41,18 +40,6 @@ distribution.
#include <sstream> #include <sstream>
#include <map> #include <map>
const char *DFHack::Error::NullPointer::what() const throw() {
return "DFHack::Error::NullPointer";
}
const char *DFHack::Error::InvalidArgument::what() const throw() {
return "DFHack::Error::InvalidArgument";
}
const char *DFHack::Error::VTableMissing::what() const throw() {
return "DFHack::Error::VTableMissing";
}
std::string stl_sprintf(const char *fmt, ...) { std::string stl_sprintf(const char *fmt, ...) {
va_list lst; va_list lst;
va_start(lst, fmt); va_start(lst, fmt);

@ -797,6 +797,25 @@ PluginManager::~PluginManager()
void PluginManager::init() void PluginManager::init()
{ {
loadAll(); loadAll();
bool any_loaded = false;
for (auto p : all_plugins)
{
if (p.second->getState() == Plugin::PS_LOADED)
{
any_loaded = true;
break;
}
}
if (!any_loaded && !listPlugins().empty())
{
Core::printerr("\n"
"All plugins present failed to load.\n"
"If you are using Windows XP, this is probably due to a Visual Studio 2015 bug.\n"
"Windows XP is unsupported by Microsoft as of 2014, so we do not support it.\n\n"
"If this was unexpected and you are not using Windows XP, please report this.\n\n"
);
}
} }
bool PluginManager::addPlugin(string name) bool PluginManager::addPlugin(string name)

@ -63,6 +63,8 @@ using namespace DFHack;
#include "tinythread.h" #include "tinythread.h"
using namespace tthread; using namespace tthread;
#include "jsoncpp.h"
using dfproto::CoreTextNotification; using dfproto::CoreTextNotification;
using dfproto::CoreTextFragment; using dfproto::CoreTextFragment;
using google::protobuf::MessageLite; using google::protobuf::MessageLite;
@ -284,7 +286,11 @@ void ServerConnection::threadFn()
} }
else else
{ {
if (!fn->in()->ParseFromArray(buf.get(), header.size)) if (((fn->flags & SF_ALLOW_REMOTE) != SF_ALLOW_REMOTE) && strcmp(socket->GetClientAddr(), "127.0.0.1") != 0)
{
stream.printerr("In call to %s: forbidden host: %s\n", fn->name, socket->GetClientAddr());
}
else if (!fn->in()->ParseFromArray(buf.get(), header.size))
{ {
stream.printerr("In call to %s: could not decode input args.\n", fn->name); stream.printerr("In call to %s: could not decode input args.\n", fn->name);
} }
@ -375,8 +381,44 @@ bool ServerMain::listen(int port)
socket->Initialize(); socket->Initialize();
if (!socket->Listen("127.0.0.1", port)) std::string filename("dfhack-config/remote-server.json");
return false;
Json::Value configJson;
std::ifstream inFile(filename, std::ios_base::in);
bool allow_remote = false;
if (inFile.is_open())
{
inFile >> configJson;
inFile.close();
allow_remote = configJson.get("allow_remote", "false").asBool();
port = configJson.get("port", port).asInt();
}
configJson["allow_remote"] = allow_remote;
configJson["port"] = port;
std::ofstream outFile(filename, std::ios_base::trunc);
if (outFile.is_open())
{
outFile << configJson;
outFile.close();
}
if (allow_remote)
{
if (!socket->Listen(NULL, port))
return false;
}
else
{
if (!socket->Listen("127.0.0.1", port))
return false;
}
thread = new tthread::thread(threadFn, this); thread = new tthread::thread(threadFn, this);
thread->detach(); thread->detach();

@ -654,29 +654,29 @@ CoreService::CoreService() {
suspend_depth = 0; suspend_depth = 0;
// These 2 methods must be first, so that they get id 0 and 1 // These 2 methods must be first, so that they get id 0 and 1
addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND); addMethod("BindMethod", &CoreService::BindMethod, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addMethod("RunCommand", &CoreService::RunCommand, SF_DONT_SUSPEND); addMethod("RunCommand", &CoreService::RunCommand, SF_DONT_SUSPEND);
// Add others here: // Add others here:
addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND); addMethod("CoreSuspend", &CoreService::CoreSuspend, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND); addMethod("CoreResume", &CoreService::CoreResume, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addMethod("RunLua", &CoreService::RunLua); addMethod("RunLua", &CoreService::RunLua);
// Functions: // Functions:
addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND); addFunction("GetVersion", GetVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND); addFunction("GetDFVersion", GetDFVersion, SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addFunction("GetWorldInfo", GetWorldInfo); addFunction("GetWorldInfo", GetWorldInfo, SF_ALLOW_REMOTE);
addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND); addFunction("ListEnums", ListEnums, SF_CALLED_ONCE | SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND); addFunction("ListJobSkills", ListJobSkills, SF_CALLED_ONCE | SF_DONT_SUSPEND | SF_ALLOW_REMOTE);
addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE); addFunction("ListMaterials", ListMaterials, SF_CALLED_ONCE | SF_ALLOW_REMOTE);
addFunction("ListUnits", ListUnits); addFunction("ListUnits", ListUnits, SF_ALLOW_REMOTE);
addFunction("ListSquads", ListSquads); addFunction("ListSquads", ListSquads, SF_ALLOW_REMOTE);
addFunction("SetUnitLabors", SetUnitLabors); addFunction("SetUnitLabors", SetUnitLabors, SF_ALLOW_REMOTE);
} }
CoreService::~CoreService() CoreService::~CoreService()

@ -41,118 +41,91 @@ namespace DFHack
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma push #pragma push
/** /**
* C4275 is - The warning officially is non dll-interface class 'std::exception' used as base for * C4275 - The warning officially is non dll-interface class 'std::exception' used as base for
* dll-interface class * dll-interface class
* *
* Basically, its saying that you might have an ABI problem if you mismatch compilers. We don't * Basically, it's saying that you might have an ABI problem if you mismatch compilers. We don't
* care since we build all of DFhack at once against whatever Toady is using * care since we build all of DFhack at once against whatever Toady is using
*/ */
#pragma warning(disable: 4275) #pragma warning(disable: 4275)
#endif #endif
class DFHACK_EXPORT All : public std::exception{}; class DFHACK_EXPORT All : public std::exception
{
public:
const std::string full;
All(const std::string &full)
:full(full)
{}
virtual const char *what() const noexcept
{
return full.c_str();
}
virtual ~All() noexcept {}
};
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma pop #pragma pop
#endif #endif
class DFHACK_EXPORT NullPointer : public All { class DFHACK_EXPORT NullPointer : public All {
const char *varname_;
public: public:
NullPointer(const char *varname_ = NULL) : varname_(varname_) {} const char *const varname;
const char *varname() const { return varname_; } NullPointer(const char *varname = NULL);
virtual const char *what() const throw();
}; };
#define CHECK_NULL_POINTER(var) \ #define CHECK_NULL_POINTER(var) \
{ if (var == NULL) throw DFHack::Error::NullPointer(#var); } { if (var == NULL) throw DFHack::Error::NullPointer(#var); }
class DFHACK_EXPORT InvalidArgument : public All { class DFHACK_EXPORT InvalidArgument : public All {
const char *expr_;
public: public:
InvalidArgument(const char *expr_ = NULL) : expr_(expr_) {} const char *const expr;
const char *expr() const { return expr_; } InvalidArgument(const char *expr = NULL);
virtual const char *what() const throw();
}; };
#define CHECK_INVALID_ARGUMENT(expr) \ #define CHECK_INVALID_ARGUMENT(expr) \
{ if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); } { if (!(expr)) throw DFHack::Error::InvalidArgument(#expr); }
class DFHACK_EXPORT VTableMissing : public All { class DFHACK_EXPORT VTableMissing : public All {
const char *name_;
public: public:
VTableMissing(const char *name_ = NULL) : name_(name_) {} const char *const name;
const char *name() const { return name_; } VTableMissing(const char *name = NULL);
virtual const char *what() const throw();
}; };
class DFHACK_EXPORT AllSymbols : public All{}; class DFHACK_EXPORT AllSymbols : public All
{
public:
AllSymbols(const std::string &full)
:All(full)
{}
};
// Syntax errors and whatnot, the xml can't be read // Syntax errors and whatnot, the xml can't be read
class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols class DFHACK_EXPORT SymbolsXmlParse : public AllSymbols
{ {
public: public:
SymbolsXmlParse(const char* _desc, int _id, int _row, int _col) SymbolsXmlParse(const char* desc, int id, int row, int col);
:desc(_desc), id(_id), row(_row), col(_col)
{
std::stringstream s;
s << "error " << id << ": " << desc << ", at row " << row << " col " << col;
full = s.str();
}
std::string full;
const std::string desc; const std::string desc;
const int id; const int id;
const int row; const int row;
const int col; const int col;
virtual ~SymbolsXmlParse() throw(){};
virtual const char* what() const throw()
{
return full.c_str();
}
}; };
class DFHACK_EXPORT SymbolsXmlBadAttribute : public All class DFHACK_EXPORT SymbolsXmlBadAttribute : public AllSymbols
{ {
public: public:
SymbolsXmlBadAttribute(const char* _attr) : attr(_attr) SymbolsXmlBadAttribute(const char* attr);
{
std::stringstream s;
s << "attribute is either missing or invalid: " << attr;
full = s.str();
}
std::string full;
std::string attr; std::string attr;
virtual ~SymbolsXmlBadAttribute() throw(){};
virtual const char* what() const throw()
{
return full.c_str();
}
}; };
class DFHACK_EXPORT SymbolsXmlNoRoot : public All class DFHACK_EXPORT SymbolsXmlNoRoot : public AllSymbols
{ {
public: public:
SymbolsXmlNoRoot() {} SymbolsXmlNoRoot();
virtual ~SymbolsXmlNoRoot() throw(){};
virtual const char* what() const throw()
{
return "Symbol file is missing root element.";
}
}; };
class DFHACK_EXPORT SymbolsXmlUnderspecifiedEntry : public All class DFHACK_EXPORT SymbolsXmlUnderspecifiedEntry : public AllSymbols
{ {
public: public:
SymbolsXmlUnderspecifiedEntry(const char * _where) : where(_where) SymbolsXmlUnderspecifiedEntry(const char *where);
{
std::stringstream s;
s << "Underspecified symbol file entry, each entry needs to set both the name attribute and have a value. parent: " << where;
full = s.str();
}
virtual ~SymbolsXmlUnderspecifiedEntry() throw(){};
std::string where; std::string where;
std::string full;
virtual const char* what() const throw()
{
return full.c_str();
}
}; };
} }
} }

@ -46,7 +46,10 @@ namespace DFHack
SF_CALLED_ONCE = 1, SF_CALLED_ONCE = 1,
// Don't automatically suspend the core around the call. // Don't automatically suspend the core around the call.
// The function is supposed to manage locking itself. // The function is supposed to manage locking itself.
SF_DONT_SUSPEND = 2 SF_DONT_SUSPEND = 2,
// The function is considered safe to call from a remote computer.
// All other functions cannot be allowed for security reasons.
SF_ALLOW_REMOTE = 4
}; };
class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase { class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase {

@ -111,6 +111,7 @@ if (BUILD_SUPPORTED)
add_subdirectory(diggingInvaders) add_subdirectory(diggingInvaders)
DFHACK_PLUGIN(dwarfvet dwarfvet.cpp) DFHACK_PLUGIN(dwarfvet dwarfvet.cpp)
DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua)
add_subdirectory(embark-assistant)
DFHACK_PLUGIN(embark-tools embark-tools.cpp) DFHACK_PLUGIN(embark-tools embark-tools.cpp)
DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua) DFHACK_PLUGIN(eventful eventful.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(fastdwarf fastdwarf.cpp) DFHACK_PLUGIN(fastdwarf fastdwarf.cpp)

@ -16,6 +16,7 @@
#include "df/items_other_id.h" #include "df/items_other_id.h"
#include "df/job.h" #include "df/job.h"
#include "df/map_block.h" #include "df/map_block.h"
#include "df/material.h"
#include "df/plant.h" #include "df/plant.h"
#include "df/plant_raw.h" #include "df/plant_raw.h"
#include "df/tile_dig_designation.h" #include "df/tile_dig_designation.h"
@ -48,6 +49,26 @@ static bool autochop_enabled = false;
static int min_logs, max_logs; static int min_logs, max_logs;
static const int LOG_CAP_MAX = 99999; static const int LOG_CAP_MAX = 99999;
static bool wait_for_threshold; static bool wait_for_threshold;
struct Skip {
bool fruit_trees;
bool food_trees;
bool cook_trees;
operator int() {
return (fruit_trees ? 1 : 0) |
(food_trees ? 2 : 0) |
(cook_trees ? 4 : 0);
}
Skip &operator= (int in) {
// set all fields to false if they haven't been set in this save yet
if (in < 0)
in = 0;
fruit_trees = (in & 1);
food_trees = (in & 2);
cook_trees = (in & 4);
return *this;
}
};
static Skip skip;
static PersistentDataItem config_autochop; static PersistentDataItem config_autochop;
@ -179,6 +200,7 @@ static void save_config()
config_autochop.ival(1) = min_logs; config_autochop.ival(1) = min_logs;
config_autochop.ival(2) = max_logs; config_autochop.ival(2) = max_logs;
config_autochop.ival(3) = wait_for_threshold; config_autochop.ival(3) = wait_for_threshold;
config_autochop.ival(4) = skip;
} }
static void initialize() static void initialize()
@ -188,6 +210,7 @@ static void initialize()
min_logs = 80; min_logs = 80;
max_logs = 100; max_logs = 100;
wait_for_threshold = false; wait_for_threshold = false;
skip = 0;
config_autochop = World::GetPersistentData("autochop/config"); config_autochop = World::GetPersistentData("autochop/config");
if (config_autochop.isValid()) if (config_autochop.isValid())
@ -197,6 +220,7 @@ static void initialize()
min_logs = config_autochop.ival(1); min_logs = config_autochop.ival(1);
max_logs = config_autochop.ival(2); max_logs = config_autochop.ival(2);
wait_for_threshold = config_autochop.ival(3); wait_for_threshold = config_autochop.ival(3);
skip = config_autochop.ival(4);
} }
else else
{ {
@ -206,26 +230,86 @@ static void initialize()
} }
} }
static int do_chop_designation(bool chop, bool count_only) static bool skip_plant(const df::plant * plant, bool *restricted)
{
if (restricted)
*restricted = false;
// Skip all non-trees immediately.
if (plant->flags.bits.is_shrub)
return true;
// Skip plants with invalid tile.
df::map_block *cur = Maps::getTileBlock(plant->pos);
if (!cur)
return true;
int x = plant->pos.x % 16;
int y = plant->pos.y % 16;
// Skip all unrevealed plants.
if (cur->designation[x][y].bits.hidden)
return true;
df::tiletype_material material = tileMaterial(cur->tiletype[x][y]);
if (material != tiletype_material::TREE)
return true;
const df::plant_raw *plant_raw = df::plant_raw::find(plant->material);
// Skip fruit trees if set.
if (skip.fruit_trees && plant_raw->material_defs.type_drink != -1)
{
if (restricted)
*restricted = true;
return true;
}
if (skip.food_trees || skip.cook_trees)
{
df::material * mat;
for (int idx = 0; idx < plant_raw->material.size(); idx++)
{
mat = plant_raw->material[idx];
if (skip.food_trees && mat->flags.is_set(material_flags::EDIBLE_RAW))
{
if (restricted)
*restricted = true;
return true;
}
if (skip.cook_trees && mat->flags.is_set(material_flags::EDIBLE_COOKED))
{
if (restricted)
*restricted = true;
return true;
}
}
}
return false;
}
static int do_chop_designation(bool chop, bool count_only, int *skipped = nullptr)
{ {
int count = 0; int count = 0;
if (skipped)
{
*skipped = 0;
}
for (size_t i = 0; i < world->plants.all.size(); i++) for (size_t i = 0; i < world->plants.all.size(); i++)
{ {
const df::plant *plant = world->plants.all[i]; const df::plant *plant = world->plants.all[i];
df::map_block *cur = Maps::getTileBlock(plant->pos);
if (!cur)
continue;
int x = plant->pos.x % 16;
int y = plant->pos.y % 16;
if (plant->flags.bits.is_shrub) bool restricted = false;
continue; if (skip_plant(plant, &restricted))
if (cur->designation[x][y].bits.hidden) {
continue; if (restricted && skipped)
{
df::tiletype_material material = tileMaterial(cur->tiletype[x][y]); ++*skipped;
if (material != tiletype_material::TREE) }
continue; continue;
}
if (!count_only && !watchedBurrows.isValidPos(plant->pos)) if (!count_only && !watchedBurrows.isValidPos(plant->pos))
continue; continue;
@ -367,7 +451,11 @@ static void do_autochop()
class ViewscreenAutochop : public dfhack_viewscreen class ViewscreenAutochop : public dfhack_viewscreen
{ {
public: public:
ViewscreenAutochop() ViewscreenAutochop():
selected_column(0),
current_log_count(0),
marked_tree_count(0),
skipped_tree_count(0)
{ {
edit_mode = EDIT_NONE; edit_mode = EDIT_NONE;
burrows_column.multiselect = true; burrows_column.multiselect = true;
@ -401,7 +489,7 @@ public:
burrows_column.filterDisplay(); burrows_column.filterDisplay();
current_log_count = get_log_count(); current_log_count = get_log_count();
marked_tree_count = do_chop_designation(false, true); marked_tree_count = do_chop_designation(false, true, &skipped_tree_count);
} }
void change_min_logs(int delta) void change_min_logs(int delta)
@ -501,13 +589,21 @@ public:
{ {
int count = do_chop_designation(true, false); int count = do_chop_designation(true, false);
message = "Trees marked for chop: " + int_to_string(count); message = "Trees marked for chop: " + int_to_string(count);
marked_tree_count = do_chop_designation(false, true); marked_tree_count = do_chop_designation(false, true, &skipped_tree_count);
if (skipped_tree_count)
{
message += ", skipped: " + int_to_string(skipped_tree_count);
}
} }
else if (input->count(interface_key::CUSTOM_U)) else if (input->count(interface_key::CUSTOM_U))
{ {
int count = do_chop_designation(false, false); int count = do_chop_designation(false, false);
message = "Trees unmarked: " + int_to_string(count); message = "Trees unmarked: " + int_to_string(count);
marked_tree_count = do_chop_designation(false, true); marked_tree_count = do_chop_designation(false, true, &skipped_tree_count);
if (skipped_tree_count)
{
message += ", skipped: " + int_to_string(skipped_tree_count);
}
} }
else if (input->count(interface_key::CUSTOM_N)) else if (input->count(interface_key::CUSTOM_N))
{ {
@ -554,6 +650,18 @@ public:
{ {
change_max_logs(10); change_max_logs(10);
} }
else if (input->count(interface_key::CUSTOM_F))
{
skip.fruit_trees = !skip.fruit_trees;
}
else if (input->count(interface_key::CUSTOM_E))
{
skip.food_trees = !skip.food_trees;
}
else if (input->count(interface_key::CUSTOM_C))
{
skip.cook_trees = !skip.cook_trees;
}
else if (enabler->tracking_on && enabler->mouse_lbut) else if (enabler->tracking_on && enabler->mouse_lbut)
{ {
if (burrows_column.setHighlightByMouse()) if (burrows_column.setHighlightByMouse())
@ -598,13 +706,14 @@ public:
} }
++y; ++y;
OutputToggleString(x, y, "Autochop", "a", autochop_enabled, true, left_margin);
OutputHotkeyString(x, y, "Designate Now", "d", true, left_margin); using namespace df::enums::interface_key;
OutputHotkeyString(x, y, "Undesignate Now", "u", true, left_margin); OutputToggleString(x, y, "Autochop", CUSTOM_A, autochop_enabled, true, left_margin);
OutputHotkeyString(x, y, "Designate Now", CUSTOM_D, true, left_margin);
OutputHotkeyString(x, y, "Undesignate Now", CUSTOM_U, true, left_margin);
OutputHotkeyString(x, y, "Toggle Burrow", "Enter", true, left_margin); OutputHotkeyString(x, y, "Toggle Burrow", "Enter", true, left_margin);
if (autochop_enabled) if (autochop_enabled)
{ {
using namespace df::enums::interface_key;
const struct { const struct {
const char *caption; const char *caption;
int count; int count;
@ -615,7 +724,7 @@ public:
{"Min Logs: ", min_logs, edit_mode == EDIT_MIN, CUSTOM_N, {CUSTOM_H, CUSTOM_J, CUSTOM_SHIFT_H, CUSTOM_SHIFT_J}}, {"Min Logs: ", min_logs, edit_mode == EDIT_MIN, CUSTOM_N, {CUSTOM_H, CUSTOM_J, CUSTOM_SHIFT_H, CUSTOM_SHIFT_J}},
{"Max Logs: ", max_logs, edit_mode == EDIT_MAX, CUSTOM_M, {CUSTOM_K, CUSTOM_L, CUSTOM_SHIFT_K, CUSTOM_SHIFT_L}} {"Max Logs: ", max_logs, edit_mode == EDIT_MAX, CUSTOM_M, {CUSTOM_K, CUSTOM_L, CUSTOM_SHIFT_K, CUSTOM_SHIFT_L}}
}; };
for (size_t i = 0; i < sizeof(rows)/sizeof(rows[0]); ++i) for (size_t i = 0; i < sizeof(rows) / sizeof(rows[0]); ++i)
{ {
auto row = rows[i]; auto row = rows[i];
OutputHotkeyString(x, y, row.caption, row.key); OutputHotkeyString(x, y, row.caption, row.key);
@ -629,13 +738,16 @@ public:
if (edit_mode == EDIT_NONE) if (edit_mode == EDIT_NONE)
{ {
x = std::max(x, prev_x + 10); x = std::max(x, prev_x + 10);
for (size_t j = 0; j < sizeof(row.skeys)/sizeof(row.skeys[0]); ++j) for (size_t j = 0; j < sizeof(row.skeys) / sizeof(row.skeys[0]); ++j)
OutputString(COLOR_LIGHTGREEN, x, y, DFHack::Screen::getKeyDisplay(row.skeys[j])); OutputString(COLOR_LIGHTGREEN, x, y, DFHack::Screen::getKeyDisplay(row.skeys[j]));
OutputString(COLOR_WHITE, x, y, ": Step"); OutputString(COLOR_WHITE, x, y, ": Step");
} }
OutputString(COLOR_WHITE, x, y, "", true, left_margin); OutputString(COLOR_WHITE, x, y, "", true, left_margin);
} }
OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin); OutputHotkeyString(x, y, "No limit", CUSTOM_SHIFT_N, true, left_margin);
OutputToggleString(x, y, "Skip Fruit Trees", CUSTOM_F, skip.fruit_trees, true, left_margin);
OutputToggleString(x, y, "Skip Edible Product Trees", CUSTOM_E, skip.food_trees, true, left_margin);
OutputToggleString(x, y, "Skip Cookable Product Trees", CUSTOM_C, skip.cook_trees, true, left_margin);
} }
++y; ++y;
@ -660,6 +772,7 @@ private:
int selected_column; int selected_column;
int current_log_count; int current_log_count;
int marked_tree_count; int marked_tree_count;
int skipped_tree_count;
MapExtras::MapCache mcache; MapExtras::MapCache mcache;
string message; string message;
enum { EDIT_NONE, EDIT_MIN, EDIT_MAX } edit_mode; enum { EDIT_NONE, EDIT_MIN, EDIT_MAX } edit_mode;

@ -1870,15 +1870,11 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest
{ {
typedef df::viewscreen_dwarfmodest interpose_base; typedef df::viewscreen_dwarfmodest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ()) DEFINE_VMETHOD_INTERPOSE(void, render, ())
{ {
INTERPOSE_NEXT(render)(); INTERPOSE_NEXT(render)();
CoreSuspendClaimer suspend;
if (Maps::IsValid()) if (Maps::IsValid())
{ {
dm_lua::call("render_all"); dm_lua::call("render_all");
@ -1886,7 +1882,6 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest
} }
}; };
IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, render); IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, render);
static bool set_monitoring_mode(const string &mode, const bool &state) static bool set_monitoring_mode(const string &mode, const bool &state)
@ -1933,8 +1928,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
load_config(); load_config();
if (is_enabled != enable) if (is_enabled != enable)
{ {
if (!INTERPOSE_HOOK(dwarf_monitor_hook, feed).apply(enable) || if (!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable))
!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable))
return CR_FAILURE; return CR_FAILURE;
reset(); reset();

@ -0,0 +1,48 @@
PROJECT (embark-assistant)
# A list of source files
SET(PROJECT_SRCS
biome_type.cpp
embark-assistant.cpp
finder_ui.cpp
help_ui.cpp
matcher.cpp
overlay.cpp
screen.cpp
survey.cpp
)
# A list of headers
SET(PROJECT_HDRS
biome_type.h
defs.h
embark-assistant.h
finder_ui.h
help_ui.h
matcher.h
overlay.h
screen.h
survey.h
)
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)
# mash them together (headers are marked as headers and nothing will try to compile them)
LIST(APPEND PROJECT_SRCS ${PROJECT_HDRS})
# option to use a thread for no particular reason
#OPTION(SKELETON_THREAD "Use threads in the skeleton plugin." ON)
#linux
IF(UNIX)
add_definitions(-DLINUX_BUILD)
SET(PROJECT_LIBS
# add any extra linux libs here
${PROJECT_LIBS}
)
# windows
ELSE(UNIX)
SET(PROJECT_LIBS
# add any extra windows libs here
${PROJECT_LIBS}
$(NOINHERIT)
)
ENDIF(UNIX)
# this makes sure all the stuff is put in proper places and linked to dfhack
DFHACK_PLUGIN(embark-assistant ${PROJECT_SRCS} LINK_LIBRARIES ${PROJECT_LIBS})

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

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

@ -0,0 +1,256 @@
#pragma once
#include <array>
#include <string>
#include <vector>
using namespace std;
using std::array;
using std::ostringstream;
using std::string;
using std::vector;
namespace embark_assist {
namespace defs {
// Survey types
//
enum class river_sizes {
None,
Brook,
Stream,
Minor,
Medium,
Major
};
struct mid_level_tile {
bool aquifer = false;
bool clay = false;
bool sand = false;
bool flux = false;
int8_t soil_depth;
int8_t offset;
int16_t elevation;
bool river_present = false;
int16_t river_elevation = 100;
int8_t biome_offset;
uint8_t savagery_level; // 0 - 2
uint8_t evilness_level; // 0 - 2
std::vector<bool> metals;
std::vector<bool> economics;
std::vector<bool> minerals;
};
typedef std::array<std::array<mid_level_tile, 16>, 16> mid_level_tiles;
// typedef mid_level_tile mid_level_tiles[16][16];
struct region_tile_datum {
bool surveyed = false;
uint16_t aquifer_count = 0;
uint16_t clay_count = 0;
uint16_t sand_count = 0;
uint16_t flux_count = 0;
uint8_t min_region_soil = 10;
uint8_t max_region_soil = 0;
bool waterfall = false;
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
uint8_t biome_count;
bool evil_weather[10];
bool evil_weather_possible;
bool evil_weather_full;
bool reanimating[10];
bool reanimating_possible;
bool reanimating_full;
bool thralling[10];
bool thralling_possible;
bool thralling_full;
uint16_t savagery_count[3];
uint16_t evilness_count[3];
std::vector<bool> metals;
std::vector<bool> economics;
std::vector<bool> minerals;
};
struct geo_datum {
uint8_t soil_size = 0;
bool top_soil_only = true;
bool top_soil_aquifer_only = true;
bool aquifer_absent = true;
bool clay_absent = true;
bool sand_absent = true;
bool flux_absent = true;
std::vector<bool> possible_metals;
std::vector<bool> possible_economics;
std::vector<bool> possible_minerals;
};
typedef std::vector<geo_datum> geo_data;
struct sites {
uint8_t x;
uint8_t y;
char type;
};
struct site_infos {
bool aquifer;
bool aquifer_full;
uint8_t min_soil;
uint8_t max_soil;
bool flat;
bool waterfall;
bool clay;
bool sand;
bool flux;
std::vector<uint16_t> metals;
std::vector<uint16_t> economics;
std::vector<uint16_t> minerals;
// Could add savagery, evilness, and biomes, but DF provides those easily.
};
typedef std::vector<sites> site_lists;
typedef std::vector<std::vector<region_tile_datum>> world_tile_data;
typedef bool mlt_matches[16][16];
// An embark region match is indicated by marking the top left corner
// tile as a match. Thus, the bottom and right side won't show matches
// unless the appropriate dimension has a width of 1.
struct matches {
bool preliminary_match;
bool contains_match;
mlt_matches mlt_match;
};
typedef std::vector<std::vector<matches>> match_results;
// matcher types
//
enum class evil_savagery_values : int8_t {
NA = -1,
All,
Present,
Absent
};
enum class evil_savagery_ranges : int8_t {
Low,
Medium,
High
};
enum class aquifer_ranges : int8_t {
NA = -1,
All,
Present,
Partial,
Not_All,
Absent
};
enum class river_ranges : int8_t {
NA = -1,
None,
Brook,
Stream,
Minor,
Medium,
Major
};
enum class yes_no_ranges : int8_t {
NA = -1,
Yes,
No
};
enum class all_present_ranges : int8_t {
All,
Present
};
enum class present_absent_ranges : int8_t {
NA = -1,
Present,
Absent
};
enum class soil_ranges : int8_t {
NA = -1,
None,
Very_Shallow,
Shallow,
Deep,
Very_Deep
};
/* // Future possible enhancement
enum class freezing_ranges : int8_t {
NA = -1,
Permanent,
At_Least_Partial,
Partial,
At_Most_Partial,
Never
};
*/
struct finders {
uint16_t x_dim;
uint16_t y_dim;
evil_savagery_values savagery[static_cast<int8_t>(evil_savagery_ranges::High) + 1];
evil_savagery_values evilness[static_cast<int8_t>(evil_savagery_ranges::High) + 1];
aquifer_ranges aquifer;
river_ranges min_river;
river_ranges max_river;
yes_no_ranges waterfall;
yes_no_ranges flat;
present_absent_ranges clay;
present_absent_ranges sand;
present_absent_ranges flux;
soil_ranges soil_min;
all_present_ranges soil_min_everywhere;
soil_ranges soil_max;
/*freezing_ranges freezing;*/
yes_no_ranges evil_weather; // Will probably blow up with the magic release arcs...
yes_no_ranges reanimation;
yes_no_ranges thralling;
int8_t biome_count_min; // N/A(-1), 1-9
int8_t biome_count_max; // N/A(-1), 1-9
int8_t region_type_1; // N/A(-1), df::world_region_type
int8_t region_type_2; // N/A(-1), df::world_region_type
int8_t region_type_3; // N/A(-1), df::world_region_type
int8_t biome_1; // N/A(-1), df::biome_type
int8_t biome_2; // N/A(-1), df::biome_type
int8_t biome_3; // N/A(-1), df::biome_type
int16_t metal_1; // N/A(-1), 0-max_inorganic;
int16_t metal_2; // N/A(-1), 0-max_inorganic;
int16_t metal_3; // N/A(-1), 0-max_inorganic;
int16_t economic_1; // N/A(-1), 0-max_inorganic;
int16_t economic_2; // N/A(-1), 0-max_inorganic;
int16_t economic_3; // N/A(-1), 0-max_inorganic;
int16_t mineral_1; // N/A(-1), 0-max_inorganic;
int16_t mineral_2; // N/A(-1), 0-max_inorganic;
int16_t mineral_3; // N/A(-1), 0-max_inorganic;
};
struct match_iterators {
bool active;
uint16_t x; // x position of focus when iteration started so we can return it.
uint16_t y; // y
uint16_t i;
uint16_t k;
bool x_right;
bool y_down;
bool inhibit_x_turn;
bool inhibit_y_turn;
uint16_t count;
finders finder;
};
typedef void(*find_callbacks) (embark_assist::defs::finders finder);
}
}

@ -0,0 +1,296 @@
#include "Core.h"
#include <Console.h>
#include <Export.h>
#include <PluginManager.h>
#include <time.h>
#include <modules/Gui.h>
#include <modules/Screen.h>
#include "DataDefs.h"
#include "df/coord2d.h"
#include "df/inorganic_flags.h"
#include "df/inorganic_raw.h"
#include "df/interfacest.h"
#include "df/viewscreen.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "df/world.h"
#include "df/world_data.h"
#include "df/world_geo_biome.h"
#include "df/world_raws.h"
#include "defs.h"
#include "embark-assistant.h"
#include "finder_ui.h"
#include "matcher.h"
#include "overlay.h"
#include "survey.h"
DFHACK_PLUGIN("embark-assistant");
using namespace DFHack;
using namespace df::enums;
using namespace Gui;
REQUIRE_GLOBAL(world);
namespace embark_assist {
namespace main {
struct states {
embark_assist::defs::geo_data geo_summary;
embark_assist::defs::world_tile_data survey_results;
embark_assist::defs::site_lists region_sites;
embark_assist::defs::site_infos site_info;
embark_assist::defs::match_results match_results;
embark_assist::defs::match_iterators match_iterator;
uint16_t max_inorganic;
};
static states *state = nullptr;
void embark_update ();
void shutdown();
//===============================================================================
void embark_update() {
auto screen = Gui::getViewscreenByType<df::viewscreen_choose_start_sitest>(0);
embark_assist::defs::mid_level_tiles mlt;
embark_assist::survey::initiate(&mlt);
embark_assist::survey::survey_mid_level_tile(&state->geo_summary,
&state->survey_results,
&mlt);
embark_assist::survey::survey_embark(&mlt, &state->site_info, false);
embark_assist::overlay::set_embark(&state->site_info);
embark_assist::survey::survey_region_sites(&state->region_sites);
embark_assist::overlay::set_sites(&state->region_sites);
embark_assist::overlay::set_mid_level_tile_match(state->match_results.at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match);
}
//===============================================================================
void match() {
// color_ostream_proxy out(Core::getInstance().getConsole());
uint16_t count = embark_assist::matcher::find(&state->match_iterator,
&state->geo_summary,
&state->survey_results,
&state->match_results);
embark_assist::overlay::match_progress(count, &state->match_results, !state->match_iterator.active);
if (!state->match_iterator.active) {
auto screen = Gui::getViewscreenByType<df::viewscreen_choose_start_sitest>(0);
embark_assist::overlay::set_mid_level_tile_match(state->match_results.at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match);
}
}
//===============================================================================
void clear_match() {
// color_ostream_proxy out(Core::getInstance().getConsole());
if (state->match_iterator.active) {
embark_assist::matcher::move_cursor(state->match_iterator.x, state->match_iterator.y);
}
embark_assist::survey::clear_results(&state->match_results);
embark_assist::overlay::clear_match_results();
embark_assist::main::state->match_iterator.active = false;
}
//===============================================================================
void find(embark_assist::defs::finders finder) {
// color_ostream_proxy out(Core::getInstance().getConsole());
state->match_iterator.x = embark_assist::survey::get_last_pos().x;
state->match_iterator.y = embark_assist::survey::get_last_pos().y;
state->match_iterator.finder = finder;
embark_assist::overlay::initiate_match();
}
//===============================================================================
void shutdown() {
// color_ostream_proxy out(Core::getInstance().getConsole());
embark_assist::survey::shutdown();
embark_assist::finder_ui::shutdown();
embark_assist::overlay::shutdown();
delete state;
state = nullptr;
}
}
}
//=======================================================================================
command_result embark_assistant (color_ostream &out, std::vector <std::string> & parameters);
//=======================================================================================
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 */
// 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"
" displayed and shows extended (and correct(?)) resource information for\n"
" the embark rectangle as well as normally undisplayed sites in the\n"
" current embark region. It also has a site selection tool with more\n"
" options than DF's vanilla search tool. For detailed help invoke the\n"
" in game info screen. Requires 42 lines to display properly.\n"
));
return CR_OK;
}
//=======================================================================================
DFhackCExport command_result plugin_shutdown (color_ostream &out)
{
return CR_OK;
}
//=======================================================================================
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event)
{
switch (event) {
case DFHack::SC_UNKNOWN:
break;
case DFHack::SC_WORLD_LOADED:
break;
case DFHack::SC_WORLD_UNLOADED:
case DFHack::SC_MAP_LOADED:
if (embark_assist::main::state) {
embark_assist::main::shutdown();
}
break;
case DFHack::SC_MAP_UNLOADED:
break;
case DFHack::SC_VIEWSCREEN_CHANGED:
break;
case DFHack::SC_CORE_INITIALIZED:
break;
case DFHack::SC_BEGIN_UNLOAD:
break;
case DFHack::SC_PAUSED:
break;
case DFHack::SC_UNPAUSED:
break;
}
return CR_OK;
}
//=======================================================================================
command_result embark_assistant(color_ostream &out, std::vector <std::string> & parameters)
{
if (!parameters.empty())
return CR_WRONG_USAGE;
CoreSuspender suspend;
auto screen = Gui::getViewscreenByType<df::viewscreen_choose_start_sitest>(0);
if (!screen) {
out.printerr("This plugin works only in the embark site selection phase.\n");
return CR_WRONG_USAGE;
}
df::world_data *world_data = world->world_data;
if (embark_assist::main::state) {
out.printerr("You can't invoke the embark assistant while it's already active.\n");
return CR_WRONG_USAGE;
}
embark_assist::main::state = new embark_assist::main::states;
embark_assist::main::state->match_iterator.active = false;
// 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;
}
embark_assist::main::state->max_inorganic++; // To allow it to be used as size() replacement
if (!embark_assist::overlay::setup(plugin_self,
embark_assist::main::embark_update,
embark_assist::main::match,
embark_assist::main::clear_match,
embark_assist::main::find,
embark_assist::main::shutdown,
embark_assist::main::state->max_inorganic)) {
return CR_FAILURE;
}
embark_assist::survey::setup(embark_assist::main::state->max_inorganic);
embark_assist::main::state->geo_summary.resize(world_data->geo_biomes.size());
embark_assist::main::state->survey_results.resize(world->worldgen.worldgen_parms.dim_x);
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
embark_assist::main::state->survey_results[i].resize(world->worldgen.worldgen_parms.dim_y);
for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
embark_assist::main::state->survey_results[i][k].surveyed = false;
embark_assist::main::state->survey_results[i][k].aquifer_count = 0;
embark_assist::main::state->survey_results[i][k].clay_count = 0;
embark_assist::main::state->survey_results[i][k].sand_count = 0;
embark_assist::main::state->survey_results[i][k].flux_count = 0;
embark_assist::main::state->survey_results[i][k].min_region_soil = 10;
embark_assist::main::state->survey_results[i][k].max_region_soil = 0;
embark_assist::main::state->survey_results[i][k].waterfall = false;
embark_assist::main::state->survey_results[i][k].river_size = embark_assist::defs::river_sizes::None;
for (uint8_t l = 1; l < 10; l++) {
embark_assist::main::state->survey_results[i][k].biome_index[l] = -1;
embark_assist::main::state->survey_results[i][k].biome[l] = -1;
embark_assist::main::state->survey_results[i][k].evil_weather[l] = false;
embark_assist::main::state->survey_results[i][k].reanimating[l] = false;
embark_assist::main::state->survey_results[i][k].thralling[l] = false;
}
for (uint8_t l = 0; l < 2; l++) {
embark_assist::main::state->survey_results[i][k].savagery_count[l] = 0;
embark_assist::main::state->survey_results[i][k].evilness_count[l] = 0;
}
embark_assist::main::state->survey_results[i][k].metals.resize(embark_assist::main::state->max_inorganic);
embark_assist::main::state->survey_results[i][k].economics.resize(embark_assist::main::state->max_inorganic);
embark_assist::main::state->survey_results[i][k].minerals.resize(embark_assist::main::state->max_inorganic);
}
}
embark_assist::survey::high_level_world_survey(&embark_assist::main::state->geo_summary,
&embark_assist::main::state->survey_results);
embark_assist::main::state->match_results.resize(world->worldgen.worldgen_parms.dim_x);
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
embark_assist::main::state->match_results[i].resize(world->worldgen.worldgen_parms.dim_y);
}
embark_assist::survey::clear_results(&embark_assist::main::state->match_results);
embark_assist::survey::survey_region_sites(&embark_assist::main::state->region_sites);
embark_assist::overlay::set_sites(&embark_assist::main::state->region_sites);
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::overlay::set_embark(&embark_assist::main::state->site_info);
return CR_OK;
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,17 @@
#pragma once
#include "PluginManager.h"
#include "DataDefs.h"
#include "defs.h"
using namespace DFHack;
namespace embark_assist {
namespace finder_ui {
void init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic);
void activate();
void shutdown();
}
}

@ -0,0 +1,314 @@
#include "Core.h"
#include <Console.h>
#include <string>
#include <set>
#include <modules/Gui.h>
#include "Types.h"
#include "help_ui.h"
#include "screen.h"
using std::vector;
namespace embark_assist{
namespace help_ui {
enum class pages {
Intro,
General,
Finder,
Caveats
};
class ViewscreenHelpUi : public dfhack_viewscreen
{
public:
ViewscreenHelpUi();
void feed(std::set<df::interface_key> *input);
void render();
std::string getFocusString() { return "Help UI"; }
private:
pages current_page = pages::Intro;
};
//===============================================================================
void ViewscreenHelpUi::feed(std::set<df::interface_key> *input) {
if (input->count(df::interface_key::LEAVESCREEN))
{
input->clear();
Screen::dismiss(this);
return;
}
else if (input->count(df::interface_key::CHANGETAB)) {
switch (current_page) {
case pages::Intro:
current_page = pages::General;
break;
case pages::General:
current_page = pages::Finder;
break;
case pages::Finder:
current_page = pages::Caveats;
break;
case pages::Caveats:
current_page = pages::Intro;
break;
}
}
else if (input->count(df::interface_key::SEC_CHANGETAB)) {
switch (current_page) {
case pages::Intro:
current_page = pages::Caveats;
break;
case pages::General:
current_page = pages::Intro;
break;
case pages::Finder:
current_page = pages::General;
break;
case pages::Caveats:
current_page = pages::Intro;
break;
}
}
}
//===============================================================================
void ViewscreenHelpUi::render() {
color_ostream_proxy out(Core::getInstance().getConsole());
auto screen_size = DFHack::Screen::getWindowSize();
Screen::Pen pen(' ', COLOR_WHITE);
Screen::Pen site_pen = Screen::Pen(' ', COLOR_YELLOW, COLOR_BLACK, false);
Screen::Pen pen_lr(' ', COLOR_LIGHTRED);
std::vector<std::string> help_text;
Screen::clear();
switch (current_page) {
case pages::Intro:
Screen::drawBorder("Embark Assistant Help/Info Introduction Page");
help_text.push_back("Embark Assistant is used on the embark selection screen to provide information");
help_text.push_back("to help selecting a suitable embark site. It provides three services:");
help_text.push_back("- Display of normally invisible sites overlayed on the region map.");
help_text.push_back("- Embark rectangle resources. More detailed and correct than vanilla DF.");
help_text.push_back("- Site find search. Richer set of selection criteria than the vanilla");
help_text.push_back(" DF Find that Embark Assistant suppresses (by using the same key).");
help_text.push_back("");
help_text.push_back("The functionality requires a screen height of at least 42 lines to display");
help_text.push_back("correctly (that's the height of the Finder screen), as fitting everything");
help_text.push_back("onto a standard 80*25 screen would be too challenging. The help is adjusted");
help_text.push_back("to fit into onto an 80*42 screen.");
help_text.push_back("This help/info is split over several screens, and you can move between them");
help_text.push_back("using the TAB/Shift-TAB keys, and leave the help from any screen using ESC.");
help_text.push_back("");
help_text.push_back("When the Embark Assistant is started it provides site information (if any)");
help_text.push_back("as an overlay over the region map. Beneath that you will find a summary of");
help_text.push_back("the resources in the current embark rectangle (explained in more detail on");
help_text.push_back("the the next screen).");
help_text.push_back("On the right side the command keys the Embark Assistant uses are listed");
help_text.push_back("(this partially overwrites the DFHack Embark Tools information until the");
help_text.push_back("screen is resized). It can also be mentioned that the DF 'f'ind key help");
help_text.push_back("at the bottom of the screen is masked as the functionality is overridden by");
help_text.push_back("the Embark Assistant.");
help_text.push_back("Main screen control keys used by the Embark Assistant:");
help_text.push_back("i: Info/Help. Brings up this display.");
help_text.push_back("f: Brings up the Find Embark screen. See the Find page for more information.");
help_text.push_back("c: Clears the results of a Find operation, and also cancels an operation if");
help_text.push_back(" one is under way.");
help_text.push_back("q: Quits the Embark Assistant and brings you back to the vanilla DF interface.");
help_text.push_back(" It can be noted that the Embark Assistant automatically cancels itself");
help_text.push_back(" when DF leaves the embark screen either through <ESC>Abort Game or by");
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.");
break;
case pages::General:
Screen::drawBorder("Embark Assistant Help/Info General Page");
help_text.push_back("The Embark Assistant overlays the region map with characters indicating sites");
help_text.push_back("normally not displayed by DF. The following key is used:");
help_text.push_back("C: Camp");
help_text.push_back("c: Cave. Only displayed if the DF worldgen parameter does not display caves.");
help_text.push_back("i: Important Location. The author doesn't actually know what those are.");
help_text.push_back("l: Lair");
help_text.push_back("L: Labyrinth");
help_text.push_back("M: Monument. The author is unsure how/if this is broken down further.");
help_text.push_back("S: Shrine");
help_text.push_back("V: Vault");
help_text.push_back("The Embark info below the region map differs from the vanilla DF display in a");
help_text.push_back("few respects. Firstly, it shows resources in the embark rectangle, rather than");
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("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("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("In addition to the above, the Find functionality can also produce blinking");
help_text.push_back("overlays over the region map and the middle world map to indicate where");
help_text.push_back("matching embarks are found. The region display marks the top left corner of");
help_text.push_back("a matching embark rectangle as a matching tile.");
break;
case pages::Finder:
Screen::drawBorder("Embark Assistant Help/Info Find Page");
help_text.push_back("The Embark Assist Finder page is brought up with the f command key.");
help_text.push_back("The top of the Finder page lists the command keys available on the page:");
help_text.push_back("4/6 or horizontal arrow keys to move between the element and element values.");
help_text.push_back("8/2 or vertical arrow keys to move up/down in the current list.");
help_text.push_back("ENTER to select a value in the value list, entering it among the selections.");
help_text.push_back("f to activate the Find functionality using the values in the middle column.");
help_text.push_back("ESC to leave the screen without activating a Find operation.");
help_text.push_back("The X and Y dimensions are those of the embark to search for. Unlike DF");
help_text.push_back("itself these parameters are initiated to match the actual embark rectangle");
help_text.push_back("when a new search is initiated (prior results are cleared.");
help_text.push_back("The 6 Savagery and Evilness parameters takes some getting used to. They");
help_text.push_back("allow for searching for embarks with e.g. has both Good and Evil tiles.");
help_text.push_back("All as a parameter means every embark tile has to have this value.");
help_text.push_back("Present means at least one embark tile has to have this value.");
help_text.push_back("Absent means the feature mustn't exist in any of the embark tiles.");
help_text.push_back("N/A means no restrictions are applied.");
help_text.push_back("The Aquifer criterion introduces some new parameters:");
help_text.push_back("Partial means at least one tile has to have an aquifer, but it also has");
help_text.push_back("to be absent from at least one tile. Not All means an aquifer is tolerated");
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("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("The parameters for biomes, regions, etc. all require that the required");
help_text.push_back("feature is Present in the embark, and entering the same value multiple");
help_text.push_back("times does nothing (the first match ticks all requirements off). It can be");
help_text.push_back("noted that all the Economic materials are found in the much longer Mineral");
help_text.push_back("list. Note that Find is a fairly time consuming task (is it is in vanilla).");
break;
case pages::Caveats:
Screen::drawBorder("Embark Assistant Help/Info Caveats Page");
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("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("data is generated as well.");
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");
help_text.push_back(" to load feature shells and detailed region data, and this costs the");
help_text.push_back(" least when done one feature shell at a time.");
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("- 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");
help_text.push_back(" whenever an aquifer supporting layer is present at a depth of 3 or");
help_text.push_back(" more.");
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(" 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("- 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("- Right world map overlay not implemented as author has failed to");
help_text.push_back(" emulate the sizing logic exactly.");
help_text.push_back("Version 0.1 2017-08-30");
break;
}
// Add control keys to first line.
embark_assist::screen::paintString(pen_lr, 1, 1, "TAB/Shift-TAB");
embark_assist::screen::paintString(pen, 14, 1, ":Next/Previous Page");
embark_assist::screen::paintString(pen_lr, 34, 1, "ESC");
embark_assist::screen::paintString(pen, 37, 1, ":Leave Info/Help");
for (uint16_t i = 0; i < help_text.size(); i++) {
embark_assist::screen::paintString(pen, 1, 2 + i, help_text[i]);
}
switch (current_page) {
case pages::Intro:
embark_assist::screen::paintString(pen_lr, 1, 26, "i");
embark_assist::screen::paintString(pen_lr, 1, 27, "f");
embark_assist::screen::paintString(pen_lr, 1, 28, "c");
embark_assist::screen::paintString(pen_lr, 1, 30, "q");
break;
case pages::General:
embark_assist::screen::paintString(site_pen, 1, 4, "C");
embark_assist::screen::paintString(site_pen, 1, 5, "c");
embark_assist::screen::paintString(site_pen, 1, 6, "i");
embark_assist::screen::paintString(site_pen, 1, 7, "l");
embark_assist::screen::paintString(site_pen, 1, 8, "L");
embark_assist::screen::paintString(site_pen, 1, 9, "M");
embark_assist::screen::paintString(site_pen, 1, 10, "S");
embark_assist::screen::paintString(site_pen, 1, 11, "V");
break;
case pages::Finder:
embark_assist::screen::paintString(pen_lr, 1, 4, "4/6");
embark_assist::screen::paintString(pen_lr, 1, 5, "8/2");
embark_assist::screen::paintString(pen_lr, 1, 6, "ENTER");
embark_assist::screen::paintString(pen_lr, 1, 7, "f");
embark_assist::screen::paintString(pen_lr, 1, 8, "ESC");
break;
case pages::Caveats:
break;
}
dfhack_viewscreen::render();
}
//===============================================================================
ViewscreenHelpUi::ViewscreenHelpUi() {
}
}
}
//===============================================================================
// Exported operations
//===============================================================================
void embark_assist::help_ui::init(DFHack::Plugin *plugin_self) {
Screen::show(new embark_assist::help_ui::ViewscreenHelpUi(), plugin_self);
}

@ -0,0 +1,15 @@
#pragma once
#include "PluginManager.h"
#include "DataDefs.h"
#include "defs.h"
using namespace DFHack;
namespace embark_assist {
namespace help_ui {
void init(DFHack::Plugin *plugin_self);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,21 @@
#pragma once
#include "DataDefs.h"
#include "defs.h"
using namespace DFHack;
namespace embark_assist {
namespace matcher {
void move_cursor(uint16_t x, uint16_t y);
// Used to iterate over the whole world to generate a map of world tiles
// that contain matching embarks.
//
uint16_t find(embark_assist::defs::match_iterators *iterator,
embark_assist::defs::geo_data *geo_summary,
embark_assist::defs::world_tile_data *survey_results,
embark_assist::defs::match_results *match_results);
}
}

@ -0,0 +1,439 @@
#include <modules/Gui.h>
#include "df/coord2d.h"
#include "df/inorganic_raw.h"
#include "df/dfhack_material_category.h"
#include "df/interface_key.h"
#include "df/viewscreen.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "df/world.h"
#include "df/world_raws.h"
#include "finder_ui.h"
#include "help_ui.h"
#include "overlay.h"
#include "screen.h"
using df::global::world;
namespace embark_assist {
namespace overlay {
DFHack::Plugin *plugin_self;
const Screen::Pen empty_pen = Screen::Pen('\0', COLOR_YELLOW, COLOR_BLACK, false);
const Screen::Pen yellow_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_YELLOW, false);
const Screen::Pen green_x_pen = Screen::Pen('X', COLOR_BLACK, COLOR_GREEN, false);
struct display_strings {
Screen::Pen pen;
std::string text;
};
typedef Screen::Pen *pen_column;
struct states {
int blink_count = 0;
bool show = true;
bool matching = false;
bool match_active = false;
embark_update_callbacks embark_update;
match_callbacks match_callback;
clear_match_callbacks clear_match_callback;
embark_assist::defs::find_callbacks find_callback;
shutdown_callbacks shutdown_callback;
Screen::Pen site_grid[16][16];
uint8_t current_site_grid = 0;
std::vector<display_strings> embark_info;
Screen::Pen region_match_grid[16][16];
pen_column *world_match_grid = nullptr;
uint16_t match_count = 0;
uint16_t max_inorganic;
};
static states *state = nullptr;
//====================================================================
/* // Attempt to replicate the DF logic for sizing the right world map. This
// code seems to compute the values correctly, but the author hasn't been
// able to apply them at the same time as DF does to 100%.
// DF seems to round down on 0.5 values.
df::coord2d world_dimension_size(uint16_t available_screen, uint16_t map_size) {
uint16_t result;
for (uint16_t factor = 1; factor < 17; factor++) {
result = map_size / factor;
if ((map_size - result * factor) * 2 != factor) {
result = (map_size + factor / 2) / factor;
}
if (result <= available_screen) {
return {result, factor};
}
}
return{16, 16}; // Should never get here.
}
*/
//====================================================================
class ViewscreenOverlay : public df::viewscreen_choose_start_sitest
{
public:
typedef df::viewscreen_choose_start_sitest interpose_base;
void send_key(const df::interface_key &key)
{
std::set< df::interface_key > keys;
keys.insert(key);
this->feed(&keys);
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
// color_ostream_proxy out(Core::getInstance().getConsole());
if (input->count(df::interface_key::CUSTOM_Q)) {
state->shutdown_callback();
return;
}
else if (input->count(df::interface_key::SETUP_LOCAL_X_MUP) ||
input->count(df::interface_key::SETUP_LOCAL_X_MDOWN) ||
input->count(df::interface_key::SETUP_LOCAL_Y_MUP) ||
input->count(df::interface_key::SETUP_LOCAL_Y_MDOWN) ||
input->count(df::interface_key::SETUP_LOCAL_X_UP) ||
input->count(df::interface_key::SETUP_LOCAL_X_DOWN) ||
input->count(df::interface_key::SETUP_LOCAL_Y_UP) ||
input->count(df::interface_key::SETUP_LOCAL_Y_DOWN)) {
INTERPOSE_NEXT(feed)(input);
state->embark_update();
}
else if (input->count(df::interface_key::CUSTOM_C)) {
state->match_active = false;
state->matching = false;
state->clear_match_callback();
}
else if (input->count(df::interface_key::CUSTOM_F)) {
if (!state->match_active && !state->matching) {
embark_assist::finder_ui::init(embark_assist::overlay::plugin_self, state->find_callback, state->max_inorganic);
}
}
else if (input->count(df::interface_key::CUSTOM_I)) {
embark_assist::help_ui::init(embark_assist::overlay::plugin_self);
}
else {
INTERPOSE_NEXT(feed)(input);
}
}
//====================================================================
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
// color_ostream_proxy out(Core::getInstance().getConsole());
auto current_screen = Gui::getViewscreenByType<df::viewscreen_choose_start_sitest>(0);
int16_t x = current_screen->location.region_pos.x;
int16_t y = current_screen->location.region_pos.y;
auto width = Screen::getWindowSize().x;
auto height = Screen::getWindowSize().y;
state->blink_count++;
if (state->blink_count == 35) {
state->blink_count = 0;
state->show = !state->show;
}
if (state->matching) state->show = true;
Screen::drawBorder("Embark Assistant");
Screen::Pen pen_lr(' ', COLOR_LIGHTRED);
Screen::Pen pen_w(' ', COLOR_WHITE);
Screen::paintString(pen_lr, width - 28, 20, "i", false);
Screen::paintString(pen_w, width - 27, 20, ":Embark Assistant Info", false);
Screen::paintString(pen_lr, width - 28, 21, "f", false);
Screen::paintString(pen_w, width - 27, 21, ":Find Embark ", false);
Screen::paintString(pen_lr, width - 28, 22, "c", false);
Screen::paintString(pen_w, width - 27, 22, ":Cancel/Clear Find", false);
Screen::paintString(pen_lr, width - 28, 23, "q", false);
Screen::paintString(pen_w, width - 27, 23, ":Quit Embark Assistant", false);
Screen::paintString(pen_w, width - 28, 25, "Matching World Tiles", false);
Screen::paintString(empty_pen, width - 7, 25, to_string(state->match_count), false);
if (height > 25) { // Mask the vanilla DF find help as it's overridden.
Screen::paintString(pen_w, 50, height - 2, " ", false);
}
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
if (state->site_grid[i][k].ch) {
Screen::paintTile(state->site_grid[i][k], i + 1, k + 2);
}
}
}
for (auto i = 0; i < state->embark_info.size(); i++) {
embark_assist::screen::paintString(state->embark_info[i].pen, 1, i + 19, state->embark_info[i].text, false);
}
if (state->show) {
int16_t left_x = x - (width / 2 - 7 - 18 + 1) / 2;
int16_t right_x;
int16_t top_y = y - (height - 8 - 2 + 1) / 2;
int16_t bottom_y;
if (left_x < 0) { left_x = 0; }
if (top_y < 0) { top_y = 0; }
right_x = left_x + width / 2 - 7 - 18;
bottom_y = top_y + height - 8 - 2;
if (right_x >= world->worldgen.worldgen_parms.dim_x) {
right_x = world->worldgen.worldgen_parms.dim_x - 1;
left_x = right_x - (width / 2 - 7 - 18);
}
if (bottom_y >= world->worldgen.worldgen_parms.dim_y) {
bottom_y = world->worldgen.worldgen_parms.dim_y - 1;
top_y = bottom_y - (height - 8 - 2);
}
if (left_x < 0) { left_x = 0; }
if (top_y < 0) { top_y = 0; }
for (uint16_t i = left_x; i <= right_x; i++) {
for (uint16_t k = top_y; k <= bottom_y; k++) {
if (state->world_match_grid[i][k].ch) {
Screen::paintTile(state->world_match_grid[i][k], i - left_x + 18, k - top_y + 2);
}
}
}
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
if (state->region_match_grid[i][k].ch) {
Screen::paintTile(state->region_match_grid[i][k], i + 1, k + 2);
}
}
}
/* // Stuff for trying to replicate the DF right world map sizing logic. Close, but not there.
Screen::Pen pen(' ', COLOR_YELLOW);
// Boundaries of the top level world map
Screen::paintString(pen, width / 2 - 5, 2, "X", false); // Marks UL corner of right world map. Constant
// Screen::paintString(pen, width - 30, 2, "X", false); // Marks UR corner of right world map area.
// Screen::paintString(pen, width / 2 - 5, height - 8, "X", false); // BL corner of right world map area.
// Screen::paintString(pen, width - 30, height - 8, "X", false); // BR corner of right world map area.
uint16_t l_width = width - 30 - (width / 2 - 5) + 1; // Horizontal space available for right world map.
uint16_t l_height = height - 8 - 2 + 1; // Vertical space available for right world map.
df::coord2d size_factor_x = world_dimension_size(l_width, world->worldgen.worldgen_parms.dim_x);
df::coord2d size_factor_y = world_dimension_size(l_height, world->worldgen.worldgen_parms.dim_y);
Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2, "X", false);
Screen::paintString(pen, width / 2 - 5, 2 + size_factor_y.x - 1, "X", false);
Screen::paintString(pen, width / 2 - 5 + size_factor_x.x - 1, 2 + size_factor_y.x - 1, "X", false);
*/
}
if (state->matching) {
embark_assist::overlay::state->match_callback();
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, feed);
IMPLEMENT_VMETHOD_INTERPOSE(embark_assist::overlay::ViewscreenOverlay, render);
}
}
//====================================================================
bool embark_assist::overlay::setup(DFHack::Plugin *plugin_self,
embark_update_callbacks embark_update_callback,
match_callbacks match_callback,
clear_match_callbacks clear_match_callback,
embark_assist::defs::find_callbacks find_callback,
shutdown_callbacks shutdown_callback,
uint16_t max_inorganic)
{
// color_ostream_proxy out(Core::getInstance().getConsole());
state = new(states);
embark_assist::overlay::plugin_self = plugin_self;
embark_assist::overlay::state->embark_update = embark_update_callback;
embark_assist::overlay::state->match_callback = match_callback;
embark_assist::overlay::state->clear_match_callback = clear_match_callback;
embark_assist::overlay::state->find_callback = find_callback;
embark_assist::overlay::state->shutdown_callback = shutdown_callback;
embark_assist::overlay::state->max_inorganic = max_inorganic;
embark_assist::overlay::state->match_active = false;
state->world_match_grid = new pen_column[world->worldgen.worldgen_parms.dim_x];
if (!state->world_match_grid) {
return false; // Out of memory
}
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
state->world_match_grid[i] = new Screen::Pen[world->worldgen.worldgen_parms.dim_y];
if (!state->world_match_grid[i]) { // Out of memory.
return false;
}
}
clear_match_results();
return INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, feed).apply(true) &&
INTERPOSE_HOOK(embark_assist::overlay::ViewscreenOverlay, render).apply(true);
}
//====================================================================
void embark_assist::overlay::set_sites(embark_assist::defs::site_lists *site_list) {
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
state->site_grid[i][k] = empty_pen;
}
}
for (uint16_t i = 0; i < site_list->size(); i++) {
state->site_grid[site_list->at(i).x][site_list->at(i).y].ch = site_list->at(i).type;
}
}
//====================================================================
void embark_assist::overlay::initiate_match() {
embark_assist::overlay::state->matching = true;
}
//====================================================================
void embark_assist::overlay::match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done) {
// color_ostream_proxy out(Core::getInstance().getConsole());
state->matching = !done;
state->match_count = count;
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
if (match_results->at(i).at(k).preliminary_match) {
state->world_match_grid[i][k] = yellow_x_pen;
} else if (match_results->at(i).at(k).contains_match) {
state->world_match_grid[i][k] = green_x_pen;
}
else {
state->world_match_grid[i][k] = empty_pen;
}
}
}
}
//====================================================================
void embark_assist::overlay::set_embark(embark_assist::defs::site_infos *site_info) {
state->embark_info.clear();
if (site_info->sand) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_YELLOW), "Sand" });
}
if (site_info->clay) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_RED), "Clay" });
}
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Soil " + std::to_string(site_info->min_soil) + " - " + std::to_string(site_info->max_soil) });
if (site_info->flat) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BROWN), "Flat" });
}
if (site_info->aquifer) {
if (site_info->aquifer_full) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Aquifer" });
}
else {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_LIGHTBLUE), "Aquifer" });
}
}
if (site_info->waterfall) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_BLUE), "Waterfall" });
}
if (site_info->flux) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), "Flux" });
}
for (auto const& i : site_info->metals) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_GREY), world->raws.inorganics[i]->id });
}
for (auto const& i : site_info->economics) {
state->embark_info.push_back({ Screen::Pen(' ', COLOR_WHITE), world->raws.inorganics[i]->id });
}
}
//====================================================================
void embark_assist::overlay::set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches) {
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
if (mlt_matches[i][k]) {
state->region_match_grid[i][k] = green_x_pen;
}
else {
state->region_match_grid[i][k] = empty_pen;
}
}
}
}
//====================================================================
void embark_assist::overlay::clear_match_results() {
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
for (uint16_t k = 0; k < world->worldgen.worldgen_parms.dim_y; k++) {
state->world_match_grid[i][k] = empty_pen;
}
}
for (uint8_t i = 0; i < 16; i++) {
for (uint8_t k = 0; k < 16; k++) {
state->region_match_grid[i][k] = empty_pen;
}
}
}
//====================================================================
void embark_assist::overlay::shutdown() {
if (state &&
state->world_match_grid) {
INTERPOSE_HOOK(ViewscreenOverlay, render).remove();
INTERPOSE_HOOK(ViewscreenOverlay, feed).remove();
for (uint16_t i = 0; i < world->worldgen.worldgen_parms.dim_x; i++) {
delete[] state->world_match_grid[i];
}
delete[] state->world_match_grid;
}
if (state) {
state->embark_info.clear();
delete state;
state = nullptr;
}
}

@ -0,0 +1,37 @@
#pragma once
#include <VTableInterpose.h>
#include "PluginManager.h"
#include "DataDefs.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "defs.h"
using df::global::enabler;
using df::global::gps;
namespace embark_assist {
namespace overlay {
typedef void(*embark_update_callbacks)();
typedef void(*match_callbacks)();
typedef void(*clear_match_callbacks)();
typedef void(*shutdown_callbacks)();
bool setup(DFHack::Plugin *plugin_self,
embark_update_callbacks embark_update_callback,
match_callbacks match_callback,
clear_match_callbacks clear_match_callback,
embark_assist::defs::find_callbacks find_callback,
shutdown_callbacks shutdown_callback,
uint16_t max_inorganic);
void set_sites(embark_assist::defs::site_lists *site_list);
void initiate_match();
void match_progress(uint16_t count, embark_assist::defs::match_results *match_results, bool done);
void set_embark(embark_assist::defs::site_infos *site_info);
void set_mid_level_tile_match(embark_assist::defs::mlt_matches mlt_matches);
void clear_match_results();
void shutdown();
}
}

@ -0,0 +1,27 @@
#include "screen.h"
namespace embark_assist {
namespace screen {
}
}
bool embark_assist::screen::paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map) {
auto screen_size = DFHack::Screen::getWindowSize();
if (y < 1 || y + 1 >= screen_size.y || x < 1)
{
return false; // Won't paint outside of the screen or on the frame
}
if (x + text.length() - 1 < screen_size.x - 2) {
DFHack::Screen::paintString(pen, x, y, text, map);
}
else if (x < screen_size.x - 2) {
DFHack::Screen::paintString(pen, x, y, text.substr(0, screen_size.x - 2 - x + 1), map);
}
else {
return false;
}
return true;
}

@ -0,0 +1,7 @@
#include "modules/Screen.h"
namespace embark_assist {
namespace screen {
bool paintString(const DFHack::Screen::Pen &pen, int x, int y, const std::string &text, bool map = false);
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,38 @@
#pragma once
#include <map>
#include "DataDefs.h"
#include "df/coord2d.h"
#include "defs.h"
using namespace DFHack;
namespace embark_assist {
namespace survey {
void setup(uint16_t max_inorganic);
df::coord2d get_last_pos();
void initiate(embark_assist::defs::mid_level_tiles *mlt);
void clear_results(embark_assist::defs::match_results *match_results);
void high_level_world_survey(embark_assist::defs::geo_data *geo_summary,
embark_assist::defs::world_tile_data *survey_results);
void 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);
df::coord2d apply_offset(uint16_t x, uint16_t y, int8_t offset);
void survey_region_sites(embark_assist::defs::site_lists *site_list);
void survey_embark(embark_assist::defs::mid_level_tiles *mlt,
embark_assist::defs::site_infos *site_info,
bool use_cache);
void shutdown();
}
}

@ -98,6 +98,7 @@
#include "tweaks/kitchen-prefs-empty.h" #include "tweaks/kitchen-prefs-empty.h"
#include "tweaks/max-wheelbarrow.h" #include "tweaks/max-wheelbarrow.h"
#include "tweaks/military-assign.h" #include "tweaks/military-assign.h"
#include "tweaks/pausing-fps-counter.h"
#include "tweaks/nestbox-color.h" #include "tweaks/nestbox-color.h"
#include "tweaks/shift-8-scroll.h" #include "tweaks/shift-8-scroll.h"
#include "tweaks/stable-cursor.h" #include "tweaks/stable-cursor.h"
@ -232,6 +233,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Preserve list order and cursor position when assigning to squad,\n" " Preserve list order and cursor position when assigning to squad,\n"
" i.e. stop the rightmost list of the Positions page of the military\n" " i.e. stop the rightmost list of the Positions page of the military\n"
" screen from constantly jumping to the top.\n" " screen from constantly jumping to the top.\n"
" tweak pausing-fps-counter [disable]\n"
" Replace fortress mode FPS counter with one that stops counting \n"
" when paused.\n"
" tweak shift-8-scroll [disable]\n" " tweak shift-8-scroll [disable]\n"
" Gives Shift+8 (or *) priority when scrolling menus, instead of \n" " Gives Shift+8 (or *) priority when scrolling menus, instead of \n"
" scrolling the map\n" " scrolling the map\n"
@ -303,6 +307,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_HOOK("nestbox-color", nestbox_color_hook, drawBuilding); TWEAK_HOOK("nestbox-color", nestbox_color_hook, drawBuilding);
TWEAK_HOOK("pausing-fps-counter", dwarfmode_pausing_fps_counter_hook, render);
TWEAK_HOOK("pausing-fps-counter", title_pausing_fps_counter_hook, render);
TWEAK_HOOK("shift-8-scroll", shift_8_scroll_hook, feed); TWEAK_HOOK("shift-8-scroll", shift_8_scroll_hook, feed);
TWEAK_HOOK("stable-cursor", stable_cursor_hook, feed); TWEAK_HOOK("stable-cursor", stable_cursor_hook, feed);

@ -0,0 +1,137 @@
#include "df/global_objects.h"
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "df/enabler.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_titlest.h"
struct dwarfmode_pausing_fps_counter_hook : df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
static const uint32_t history_length = 3;
// whether init.txt have [FPS:YES]
static bool init_have_fps_yes()
{
static bool first = true;
static bool init_have_fps_yes;
if (first && df::global::gps)
{
// if first time called, then display_frames is set iff init.txt have [FPS:YES]
first = false;
init_have_fps_yes = (df::global::gps->display_frames == 1);
}
return init_have_fps_yes;
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (!df::global::pause_state || !df::global::enabler || !df::global::world
|| !df::global::gps || !df::global::pause_state)
return;
// if init.txt does not have [FPS:YES] then dont show this FPS counter
if (!dwarfmode_pausing_fps_counter_hook::init_have_fps_yes())
return;
static bool prev_paused = true;
static uint32_t prev_clock = 0;
static int32_t prev_frames = 0;
static uint32_t elapsed_clock = 0;
static uint32_t elapsed_frames = 0;
static double history[history_length];
if (prev_clock == 0)
{
// init
for (int i = 0; i < history_length; i++)
history[i] = 0.0;
}
// disable default FPS counter because it is rendered on top of this FPS counter.
if (df::global::gps->display_frames == 1)
df::global::gps->display_frames = 0;
if (*df::global::pause_state)
prev_paused = true;
else
{
uint32_t clock = df::global::enabler->clock;
int32_t frames = df::global::world->frame_counter;
if (!prev_paused && prev_clock != 0
&& clock >= prev_clock && frames >= prev_frames)
{
// if we were previously paused, then dont add clock/frames,
// but wait for the next time render is called.
elapsed_clock += clock - prev_clock;
elapsed_frames += frames - prev_frames;
}
prev_paused = false;
prev_clock = clock;
prev_frames = frames;
// add FPS to history every second or after at least one frame.
if (elapsed_clock >= 1000 && elapsed_frames >= 1)
{
double fps = elapsed_frames / (elapsed_clock / 1000.0);
for (int i = history_length - 1; i >= 1; i--)
history[i] = history[i - 1];
history[0] = fps;
elapsed_clock = 0;
elapsed_frames = 0;
}
}
// average fps over a few seconds to stabilize the counter.
double fps_sum = 0.0;
int fps_count = 0;
for (int i = 0; i < history_length; i++)
{
if (history[i] > 0.0)
{
fps_sum += history[i];
fps_count++;
}
}
double fps = fps_count == 0 ? 1.0 : fps_sum / fps_count;
double gfps = df::global::enabler->calculated_gfps;
std::stringstream fps_counter;
fps_counter << "FPS:"
<< setw(4) << fixed << setprecision(fps >= 1.0 ? 0 : 2) << fps
<< " (" << gfps << ")";
// show this FPS counter same as the default counter.
int x = 10;
int y = 0;
OutputString(COLOR_WHITE, x, y, fps_counter.str(),
false, 0, COLOR_CYAN, false);
}
};
struct title_pausing_fps_counter_hook : df::viewscreen_titlest {
typedef df::viewscreen_titlest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
// if init.txt have FPS:YES then enable default FPS counter when exiting dwarf mode.
// So it is enabled if starting adventure mode without exiting dwarf fortress.
if (dwarfmode_pausing_fps_counter_hook::init_have_fps_yes()
&& df::global::gps && df::global::gps->display_frames == 0)
df::global::gps->display_frames = 1;
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dwarfmode_pausing_fps_counter_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(title_pausing_fps_counter_hook, render);

@ -1 +1 @@
Subproject commit 8d079a59122d9ba72ce9c0f7687402a343d09bc7 Subproject commit 28a1ccc9883d85106fa3b0ed59ddd57fcfc3f5c7

@ -0,0 +1,10 @@
function set_test_stage(stage)
local f = io.open('test_stage.txt', 'w')
f:write(stage)
f:close()
end
print('running tests')
set_test_stage('done')
dfhack.run_command('die')

@ -0,0 +1,2 @@
:lua dfhack.internal.addScriptPath(os.getenv('TRAVIS_BUILD_DIR'))
test/main

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

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

@ -0,0 +1,30 @@
import os, subprocess, sys
MAX_TRIES = 5
dfhack = 'Dwarf Fortress.exe' if sys.platform == 'win32' else './dfhack'
test_stage = 'test_stage.txt'
def get_test_stage():
if os.path.isfile(test_stage):
return open(test_stage).read().strip()
return '0'
os.chdir(sys.argv[1])
if os.path.exists(test_stage):
os.remove(test_stage)
tries = 0
while True:
tries += 1
stage = get_test_stage()
print('Run #%i: stage=%s' % (tries, get_test_stage()))
if stage == 'done':
print('Done!')
os.remove(test_stage)
sys.exit(0)
if tries > MAX_TRIES:
print('Too many tries - aborting')
sys.exit(1)
os.system(dfhack)