Merge branch 'develop' into persist

develop
lethosor 2019-08-22 22:00:50 -04:00
commit 07575095fd
47 changed files with 3992 additions and 1246 deletions

7
.gitignore vendored

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

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

@ -3,15 +3,13 @@
[![Build Status](https://travis-ci.org/DFHack/dfhack.svg?branch=develop)](https://travis-ci.org/DFHack/dfhack)
[![Documentation Status](https://readthedocs.org/projects/dfhack/badge)](https://dfhack.readthedocs.org)
[![License](https://img.shields.io/badge/license-ZLib-blue.svg)](https://en.wikipedia.org/wiki/Zlib_License)
[![Github Issues](http://githubbadges.herokuapp.com/DFHack/dfhack/issues)](https://github.com/DFHack/dfhack/issues)
[![Open Pulls](http://githubbadges.herokuapp.com/DFHack/dfhack/pulls)](https://github.com/DFHack/dfhack/pulls)
DFHack is a Dwarf Fortress memory access library, distributed with scripts
and plugins implementing a wide variety of useful functions and tools.
The full documentation [is available online here](https://dfhack.readthedocs.org),
from the README.html page in the DFHack distribution, or as raw text in the `./docs` folder.
If you're an end-user, modder, or interested in contributing to DFHack -
If you're an end-user, modder, or interested in contributing to DFHack -
go read those docs.
If that's unclear or you need more help, try

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

@ -0,0 +1,61 @@
/**
Copyright © 2018 Pauli <suokkos@gmail.com>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any
damages arising from the use of this software.
Permission is granted to anyone to use this software for any
purpose, including commercial applications, and to alter it and
redistribute it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must
not claim that you wrote the original software. If you use this
software in a product, an acknowledgment in the product
documentation would be appreciated but is not required.
2. Altered source versions must be plainly marked as such, and
must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any source
distribution.
*/
#pragma once
#ifdef _MSC_VER
#include <windows.h>
#else
#include <pthread.h>
#endif
#include <stdlib.h>
/*! \file dfhack_llimits.h
* dfhack specific lua porting header that overrides lua defaults for thread
* safety.
*/
#ifdef _MSC_VER
typedef CRITICAL_SECTION mutex_t;
#else
typedef pthread_mutex_t mutex_t;
#endif
struct lua_extra_state {
mutex_t* mutex;
};
#define luai_mutex(L) ((lua_extra_state*)lua_getextraspace(L))->mutex
#ifdef _MSC_VER
#define luai_userstateopen(L) luai_mutex(L) = (mutex_t*)malloc(sizeof(mutex_t)); InitializeCriticalSection(luai_mutex(L))
#define luai_userstateclose(L) lua_unlock(L); DeleteCriticalSection(luai_mutex(L)); free(luai_mutex(L))
#define lua_lock(L) EnterCriticalSection(luai_mutex(L))
#define lua_unlock(L) LeaveCriticalSection(luai_mutex(L))
#else
#define luai_userstateopen(L) luai_mutex(L) = (mutex_t*)malloc(sizeof(mutex_t)); *luai_mutex(L) = PTHREAD_MUTEX_INITIALIZER
#define luai_userstateclose(L) lua_unlock(L); pthread_mutex_destroy(luai_mutex(L)); free(luai_mutex(L))
#define lua_lock(L) pthread_mutex_lock(luai_mutex(L))
#define lua_unlock(L) pthread_mutex_unlock(luai_mutex(L))
#endif

@ -69,6 +69,7 @@ Kris Parker kaypy
Kromtec Kromtec
Kurik Amudnil
Lethosor lethosor
LordGolias LordGolias
Mason11987 Mason11987
Matt Regul mattregul
Matthew Cline
@ -87,6 +88,7 @@ Milo Christiansen milochristiansen
MithrilTuxedo MithrilTuxedo
mizipzor mizipzor
moversti moversti
napagokc napagokc
Neil Little nmlittle
Nick Rart nickrart comestible
Nikolay Amiantov abbradar

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

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

@ -2848,4 +2848,5 @@ in the `lua-api` file under `lua-plugins`:
* `eventful`
* `building-hacks`
* `luasocket`
* `map-render`
* `cxxrandom`

@ -37,6 +37,25 @@ changelog.txt uses a syntax similar to RST, with a few special sequences:
================================================================================
# Future
## Fixes
- `embark-assistant`:
- fixed bug causing crash on worlds without generated metals (as well as pruning vectors as originally intended).
- fixed bug causing mineral matching to fail to cut off at the magma sea, reporting presence of things that aren't (like DF does currently).
## Misc Improvements
- `embark-assistant`:
- Changed Waterfall detection to look for level drop rather than just presence.
- Changed matching to take incursions, i.e. parts of other biomes, into consideration when evaluating tiles. This allows for e.g. finding multiple biomes on single tile embarks.
- Changed overlay display to show when incursion surveying is incomplete.
- Changed overlay display to show Evil Weather.
- `RemoteFortressReader`:
- added basic framework for controlling and reading the menus in DF, currently only supports the building menu.
- added support for reading item raws.
- added a check for wheather or not the game is currently saving or loading, for utilities to check if it's safe to read from DF.
- guestimate unit facing direction, and position within tiles.
- get unit age.
- get unit wounds.
## Internals
- Fixed some OpenGL build issues with `stonesense`

@ -1428,6 +1428,7 @@ bool Core::loadScriptFile(color_ostream &out, string fname, bool silent)
static void run_dfhack_init(color_ostream &out, Core *core)
{
CoreSuspender lock;
if (!df::global::world || !df::global::ui || !df::global::gview)
{
out.printerr("Key globals are missing, skipping loading dfhack.init.\n");
@ -1529,6 +1530,7 @@ Core::Core() :
HotkeyMutex{},
HotkeyCond{},
alias_mutex{},
started{false},
misc_data_mutex{},
CoreSuspendMutex{},
CoreWakeup{},
@ -1538,7 +1540,7 @@ Core::Core() :
// init the console. This must be always the first step!
plug_mgr = 0;
errorstate = false;
started = false;
vinfo = 0;
memset(&(s_mods), 0, sizeof(s_mods));
// set up hotkey capture
@ -1548,7 +1550,6 @@ Core::Core() :
last_pause_state = false;
top_viewscreen = NULL;
screen_window = NULL;
server = NULL;
color_ostream::log_errors_to_stderr = true;
@ -1766,6 +1767,8 @@ bool Core::Init()
// create plugin manager
plug_mgr = new PluginManager(this);
plug_mgr->init();
cerr << "Starting the TCP listener.\n";
auto listen = ServerMain::listen(RemoteClient::GetDefaultPort());
IODATA *temp = new IODATA;
temp->core = this;
temp->plug_mgr = plug_mgr;
@ -1790,9 +1793,7 @@ bool Core::Init()
started = true;
modstate = 0;
cerr << "Starting the TCP listener.\n";
server = new ServerMain();
if (!server->listen(RemoteClient::GetDefaultPort()))
if (!listen.get())
cerr << "TCP listen failed.\n";
if (df::global::ui_sidebar_menus)
@ -2318,6 +2319,8 @@ int Core::Shutdown ( void )
HotkeyCond.notify_one();
}
ServerMain::block();
d->hotkeythread.join();
d->iothread.join();

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

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

@ -285,7 +285,7 @@ namespace DFHack
df::viewscreen *top_viewscreen;
bool last_pause_state;
// Very important!
bool started;
std::atomic<bool> started;
// Additional state change scripts
std::vector<StateChangeScript> state_change_scripts;
@ -310,7 +310,6 @@ namespace DFHack
friend class CoreSuspenderBase;
friend struct CoreSuspendClaimMain;
friend struct CoreSuspendReleaseMain;
ServerMain *server;
};
class CoreSuspenderBase : protected std::unique_lock<std::recursive_mutex> {

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

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

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

@ -1 +1 @@
Subproject commit 2be1fc4afea4d3345b9b76d0f27f56087ac9b6e0
Subproject commit c192f9798b5d134e777b1f37c8ebc0df4bd53bda

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

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

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

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

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

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

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

@ -62,7 +62,10 @@ namespace embark_assist {
&state->survey_results,
&mlt);
embark_assist::survey::survey_embark(&mlt, &state->site_info, false);
embark_assist::survey::survey_embark(&mlt,
&state->survey_results,
&state->site_info,
false);
embark_assist::overlay::set_embark(&state->site_info);
embark_assist::survey::survey_region_sites(&state->region_sites);
@ -224,7 +227,7 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
// Find the end of the normal inorganic definitions.
embark_assist::main::state->max_inorganic = 0;
for (uint16_t i = 0; i < world->raws.inorganics.size(); i++) {
if (world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) embark_assist::main::state->max_inorganic = i;
if (!world->raws.inorganics[i]->flags.is_set(df::inorganic_flags::GENERATED)) embark_assist::main::state->max_inorganic = i;
}
embark_assist::main::state->max_inorganic++; // To allow it to be used as size() replacement
@ -253,7 +256,7 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
embark_assist::main::state->survey_results[i][k].flux_count = 0;
embark_assist::main::state->survey_results[i][k].min_region_soil = 10;
embark_assist::main::state->survey_results[i][k].max_region_soil = 0;
embark_assist::main::state->survey_results[i][k].waterfall = false;
embark_assist::main::state->survey_results[i][k].max_waterfall = 0;
embark_assist::main::state->survey_results[i][k].river_size = embark_assist::defs::river_sizes::None;
for (uint8_t l = 1; l < 10; l++) {
@ -291,7 +294,7 @@ command_result embark_assistant(color_ostream &out, std::vector <std::string> &
embark_assist::defs::mid_level_tiles mlt;
embark_assist::survey::survey_mid_level_tile(&embark_assist::main::state->geo_summary, &embark_assist::main::state->survey_results, &mlt);
embark_assist::survey::survey_embark(&mlt, &embark_assist::main::state->site_info, false);
embark_assist::survey::survey_embark(&mlt, &embark_assist::main::state->survey_results, &embark_assist::main::state->site_info, false);
embark_assist::overlay::set_embark(&embark_assist::main::state->site_info);
return CR_OK;

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

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

File diff suppressed because it is too large Load Diff

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -1 +1 @@
Subproject commit 6b4df3995f53e0579fc590a78d68e54fc2ca2b81
Subproject commit 03e96477ca84e42c87db93bd2d781c73687795a8

@ -1 +1 @@
Subproject commit c8394fa75ff20abcdcdbe975dbf157d21882172e
Subproject commit 189d0d8f63b5d34ac3f779254b6ab5ff4763459a