Merge remote-tracking branch 'remotes/DFHack/develop' into RemoteServerUnsafe

# Conflicts:
#	plugins/proto/RemoteFortressReader.proto
#	plugins/remotefortressreader/remotefortressreader.cpp
#	scripts
develop
Japa 2018-02-12 20:19:40 +05:30
commit 7b19c9b8f0
89 changed files with 8920 additions and 535 deletions

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

@ -140,9 +140,9 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac
endif()
# set up versioning.
set(DF_VERSION "0.44.02")
SET(DFHACK_RELEASE "r0")
SET(DFHACK_PRERELEASE TRUE)
set(DF_VERSION "0.44.05")
set(DFHACK_RELEASE "r1")
set(DFHACK_PRERELEASE FALSE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")

@ -12,13 +12,13 @@
Sections for each release are added as required, and consist solely of the
following in order as subheadings::
Internals
Lua
Ruby
New [Internal Commands | Plugins | Scripts | Tweaks | Features]
Fixes
Misc Improvements
Removed
Internals
Lua
Ruby
When referring to a script, plugin, or command, use backticks (```) to
create a link to the relevant documentation - and check that the docs are
@ -36,6 +36,138 @@ Changelog
.. contents::
:depth: 2
DFHack 0.44.05-r2
=================
New Plugins
-----------
- `embark-assistant`: adds more information and features to embark screen
New Scripts
-----------
- `adv-fix-sleepers`: fixes units in adventure mode who refuse to wake up (:bug:`6798`)
- `hermit`: blocks caravans, migrants, diplomats (for hermit challenge)
New Features
------------
- With ``PRINT_MODE:TEXT``, setting the ``DFHACK_HEADLESS`` environment variable
will hide DF's display and allow the console to be used normally. (Note that
this is intended for testing and is not very useful for actual gameplay.)
Fixes
-----
- `devel/inject-raws`: fixed gloves and shoes (old typo causing errors)
- `view-item-info`: fixed an error with some shields
Misc Improvements
-----------------
- `autochop`: can now exclude trees with fruit,
DFHack 0.44.05-r1
=================
New Scripts
-----------
- `break-dance`: Breaks up a stuck dance activity
- `cannibalism`: Allows consumption of sapient corpses
- `devel/check-other-ids`: Checks the validity of "other" vectors in the
``world`` global
- `fillneeds`: Use with a unit selected to make them focused and unstressed
- `firestarter`: Lights things on fire: items, locations, entire inventories even!
- `flashstep`: Teleports adventurer to cursor
- `ghostly`: Turns an adventurer into a ghost or back
- `gui/cp437-table`: An in-game CP437 table
- `questport`: Sends your adventurer to the location of your quest log cursor
- `view-unit-reports`: opens the reports screen with combat reports for the selected unit
Fixes
-----
- Fixed issues with the console output color affecting the prompt on Windows
- `createitem`: stopped items from teleporting away in some forts
- `devel/inject-raws`: now recognizes spaces in reaction names
- `dig`: added support for designation priorities - fixes issues with
designations from ``digv`` and related commands having extremely high priority
- `dwarfmonitor`:
- fixed display of creatures and poetic/music/dance forms on ``prefs`` screen
- added "view unit" option
- now exposes the selected unit to other tools
- `gui/gm-unit`: can now edit mining skill
- `gui/quickcmd`: stopped error from adding too many commands
- `names`: fixed many errors
- `quicksave`: fixed an issue where the "Saving..." indicator often wouldn't appear
Misc Improvements
-----------------
- The console now provides suggestions for built-in commands
- `binpatch`: now reports errors for empty patch files
- `devel/export-dt-ini`: avoid hardcoding flags
- `exportlegends`:
- reordered some tags to match DF's order
- added progress indicators for exporting long lists
- `force`: now provides useful help
- `full-heal`:
- can now select corpses to resurrect
- now resets body part temperatures upon resurrection to prevent creatures
from freezing/melting again
- now resets units' vanish countdown to reverse effects of `exterminate`
- `gui/gm-editor`: added enum names to enum edit dialogs
- `gui/gm-unit`:
- made skill search case-insensitive
- added a profession editor
- misc. layout improvements
- `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards
- `gui/pathable`: added tile types to sidebar
- `gui/rename`: added "clear" and "special characters" options
- `launch`: can now ride creatures
- `modtools/skill-change`:
- now updates skill levels appropriately
- only prints output if ``-loud`` is passed
- `names`: can now edit names of units
- `remotefortressreader`:
- support for moving adventurers
- suport for item stack sizes, vehicles, gem shapes, item volume, art images, item improvements
- some performance improvements
Removed
-------
- `warn-stuck-trees`: :bug:`9252` fixed in DF 0.44.01
- `tweak`: ``kitchen-keys``: :bug:`614` fixed in DF 0.44.04
Internals
---------
- ``Gui::getAnyUnit()`` supports many more screens/menus
- New globals available:
- ``version``
- ``min_load_version``
- ``movie_version``
- ``basic_seed``
- ``title``
- ``title_spaced``
- ``ui_building_resize_radius``
- ``soul_next_id``
Lua
---
- Added a new ``dfhack.console`` API
- Exposed ``get_vector()`` (from C++) for all types that support ``find()``,
e.g. ``df.unit.get_vector() == df.global.world.units.all``
- Improved ``json`` I/O error messages
- Stopped a crash when trying to create instances of classes whose vtable
addresses are not available
DFHack 0.43.05-r3
=================

@ -55,6 +55,9 @@ keybinding add Alt-S@dwarfmode/Default gui/settings-manager
# change quantity of manager orders
keybinding add Alt-Q@jobmanagement/Main gui/manager-quantity
# view combat reports for the selected unit/corpse/spatter
keybinding add Ctrl-Shift-R view-unit-reports
##############################
# Generic adv mode bindings #
##############################
@ -207,7 +210,6 @@ tweak civ-view-agreement
tweak eggs-fertile
tweak fps-min
tweak hide-priority
tweak kitchen-keys
tweak kitchen-prefs-empty
tweak max-wheelbarrow
tweak shift-8-scroll

@ -17,6 +17,7 @@ AndreasPK AndreasPK
Angus Mezick amezick
Antalia tamarakorr
Anuradha Dissanayake falconne
Atkana Atkana
AtomicChicken AtomicChicken
belal jimhester
Ben Lubar BenLubar
@ -27,8 +28,10 @@ Carter Bray Qartar
Chris Dombroski cdombroski
Clayton Hughes
Clément Vuchener cvuchener
Dan Amlund danamlund
David Corbett dscorbett
David Seguin dseguin
David Timm dtimm
Deon
DoctorVanGogh DoctorVanGogh
Donald Ruegsegger hashaash
@ -55,6 +58,7 @@ Jonas Ask
kane-t kane-t
Kelly Kinkade ab9rf
Kris Parker kaypy
Kromtec Kromtec
Kurik Amudnil
Lethosor lethosor
Mason11987 Mason11987
@ -84,6 +88,7 @@ Patrik Lundell PatrikLundell
Paul Fenwick pjf
PeridexisErrant PeridexisErrant
Petr Mrázek peterix
Pfhreak Pfhreak
potato
Priit Laes plaes
Putnam Putnam3145
@ -131,6 +136,7 @@ Vjek
Warmist warmist
Wes Malone wesQ3
Will Rogers wjrogers
ZechyW ZechyW
Zhentar Zhentar
zilpin zilpin
======================= ======================= ===========================

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

@ -125,12 +125,18 @@ All typed objects have the following built-in features:
* ``ref:delete()``
Destroys the object with the C++ ``delete`` operator.
If destructor is not available, returns *false*.
Destroys the object with the C++ ``delete`` operator. If the destructor is not
available, returns *false*. (This typically only occurs when trying to delete
an instance of a DF class with virtual methods whose vtable address has not
been found; it is impossible for ``delete()`` to determine the validity of
``ref``.)
.. warning::
the lua reference object remains as a dangling
pointer, like a raw C++ pointer would.
``ref`` **must** be an object allocated with ``new``, like in C++. Calling
``obj.field:delete()`` where ``obj`` was allocated with ``new`` will not
work. After ``delete()`` returns, ``ref`` remains as a dangling pointer,
like a raw C++ pointer would. Any accesses to ``ref`` after ``ref:delete()``
has been called are undefined behavior.
* ``ref:assign(object)``
@ -1966,6 +1972,18 @@ unless otherwise noted.
``listdir_recursive()`` returns the initial path and all components following it
for each entry.
Console API
-----------
* ``dfhack.console.clear()``
Clears the console; equivalent to the ``cls`` built-in command.
* ``dfhack.console.flush()``
Flushes all output to the console. This can be useful when printing text that
does not end in a newline but should still be displayed.
Internal API
------------
@ -3759,6 +3777,12 @@ Note that this function lets errors propagate to the caller.
This is intended to only allow scripts that take appropriate action when used
as a module to be loaded.
* ``dfhack.script_help([name, [extension]])``
Returns the contents of the embedded documentation of the specified script.
``extension`` defaults to "lua", and ``name`` defaults to the name of the
script where this function was called.
Enabling and disabling scripts
==============================

@ -37,6 +37,141 @@ Development Changelog
.. contents::
:depth: 2
DFHack 0.44.05-alpha1
=====================
Structures
----------
- ``incident``: re-aligned again to match disassembly
Other Changes
-------------
- `gui/liquids`: added more keybindings: 0-7 to change liquid level, P/B to cycle backwards
DFHack 0.44.04-alpha1
=====================
Fixes
-----
- `devel/inject-raws`: now recognizes spaces in reaction names
- `exportlegends`: fixed an error that could occur when exporting empty lists
Structures
----------
- ``artifact_record``: fixed layout (changed in 0.44.04)
- ``incident``: fixed layout (changed in 0.44.01) - note that many fields have moved
DFHack 0.44.03-beta1
====================
Fixes
-----
- `autolabor`, `autohauler`, `labormanager`: added support for "put item on
display" jobs and building/destroying display furniture
- `gui/gm-editor`: fixed an error when editing primitives in Lua tables
Structures
----------
- Added 7 new globals from DF: ``version``, ``min_load_version``,
``movie_version``, ``basic_seed``, ``title``, ``title_spaced``,
``ui_building_resize_radius``
- Added ``twbt_render_map`` code offset on x64
- Fixed an issue preventing ``enabler`` from being allocated by DFHack
- Added ``job_type.PutItemOnDisplay``
- Found ``renderer`` vtable on osx64
- ``adventure_movement_optionst``, ``adventure_movement_hold_tilest``,
``adventure_movement_climbst``: named coordinate fields
- ``mission``: added type
- ``unit``: added 3 new vmethods: ``getCreatureTile``, ``getCorpseTile``, ``getGlowTile``
- ``viewscreen_assign_display_itemst``: fixed layout on x64 and identified many fields
- ``viewscreen_reportlistst``: fixed layout, added ``mission_id`` vector
- ``world.status``: named ``missions`` vector
Other Changes
-------------
- `devel/dump-offsets`: now ignores ``index`` globals
- `gui/pathable`: added tile types to sidebar
- `modtools/skill-change`:
- now updates skill levels appropriately
- only prints output if ``-loud`` is passed
DFHack 0.44.03-alpha1
=====================
Other Changes
-------------
- Lua: Improved ``json`` I/O error messages
- Lua: Stopped a crash when trying to create instances of classes whose vtable
addresses are not available
DFHack 0.44.02-beta1
====================
Fixes
-----
- Fixed issues with the console output color affecting the prompt on Windows
- `createitem`: stopped items from teleporting away in some forts
- `gui/gm-unit`: can now edit mining skill
- `gui/quickcmd`: stopped error from adding too many commands
- `modtools/create-unit`: fixed error when domesticating units
Structures
----------
- Located ``start_dwarf_count`` offset for all builds except 64-bit Linux;
`startdwarf` should work now
- Added ``buildings_other_id.DISPLAY_CASE``
- Fixed ``viewscreen_titlest.start_savegames`` alignment
- Fixed ``unit`` alignment
- Identified ``historical_entity.unknown1b.deities`` (deity IDs)
API Changes
-----------
- Lua; Exposed ``get_vector()`` (from C++) for all types that support
``find()``, e.g. ``df.unit.get_vector() == df.global.world.units.all``
Additions/Removals
------------------
- Added `devel/check-other-ids`: Checks the validity of "other" vectors in the
``world`` global
- Added `gui/cp437-table`: An in-game CP437 table
- Removed `warn-stuck-trees`: the corresponding DF bug was fixed in 0.44.01
Other Changes
-------------
- The console now provides suggestions for built-in commands
- `devel/export-dt-ini`: avoid hardcoding flags
- `exportlegends`:
- reordered some tags to match DF's order
- added progress indicators for exporting long lists
- `gui/gm-editor`: added enum names to enum edit dialogs
- `gui/gm-unit`: made skill search case-insensitive
- `gui/rename`: added "clear" and "special characters" options
- `remotefortressreader`: includes item stack sizes and some performance improvements
DFHack 0.44.02-alpha1
=====================
Fixes
-----
- Fixed a crash that could occur if a symbol table in symbols.xml had no content
- The Lua API can now wrap functions with 12 or 13 parameters
Structures
----------
- The ``ui_menu_width`` global is now a 2-byte array; the second item is the
former ``ui_area_map_width`` global, which is now removed
- The former ``announcements`` global is now a field in ``d_init``
- ``world`` fields formerly beginning with ``job_`` are now fields of
``world.jobs``, e.g. ``world.job_list`` is now ``world.jobs.list``
API Changes
-----------
- Lua: Added a new ``dfhack.console`` API
DFHack 0.43.05-beta2
====================

@ -311,7 +311,6 @@ Subcommands that persist until disabled or DF quits:
:import-priority-category:
Allows changing the priority of all goods in a
category when discussing an import agreement with the liaison
:kitchen-keys: Fixes DF kitchen meal keybindings (:bug:`614`)
:kitchen-prefs-color: Changes color of enabled items to green in kitchen preferences
:kitchen-prefs-empty: Fixes a layout issue with empty kitchen tabs (:bug:`9000`)
:max-wheelbarrow: Allows assigning more than 3 wheelbarrows to a stockpile
@ -527,6 +526,18 @@ nopause
Disables pausing (both manual and automatic) with the exception of pause forced
by `reveal` ``hell``. This is nice for digging under rivers.
.. _embark-assistant:
embark-assistant
================
This plugin provides embark site selection help. It has to be run with the
``embark-assistant`` command while the pre-embark screen is displayed and shows
extended (and correct(?)) resource information for the embark rectangle as well
as normally undisplayed sites in the current embark region. It also has a site
selection tool with more options than DF's vanilla search tool. For detailed
help invoke the in game info screen. Requires 42 lines to display properly.
.. _embark-tools:
embark-tools
@ -1931,6 +1942,13 @@ Basic commands:
:dfhack-keybind:`digv`
.. note::
All commands implemented by the `dig` plugin (listed by ``ls dig``) support
specifying the designation priority with ``-p#``, ``-p #``, or ``p=#``,
where ``#`` is a number from 1 to 7. If a priority is not specified, the
priority selected in-game is used as the default.
.. _digexp:
digexp

@ -0,0 +1,14 @@
###############
Removed scripts
###############
The following scripts were removed for various reasons.
.. contents::
:depth: 2
.. _warn-stuck-trees:
warn-stuck-trees
================
The corresponding DF bug, :bug:`9252` was fixed in DF 0.44.01.

@ -50,6 +50,7 @@ Other Contents
/docs/Authors
/LICENSE
/NEWS
/docs/Scripts-removed
For Developers
==============

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

@ -62,7 +62,7 @@ using namespace DFHack;
using namespace tthread;
// FIXME: maybe make configurable with an ini option?
#define MAX_CONSOLE_LINES 999;
#define MAX_CONSOLE_LINES 999
namespace DFHack
{
@ -165,7 +165,7 @@ namespace DFHack
// Blank to EOL
char* tmp = (char*)malloc(inf.dwSize.X);
memset(tmp, ' ', inf.dwSize.X);
output(tmp, inf.dwSize.X, 0, inf.dwCursorPosition.Y);
blankout(tmp, inf.dwSize.X, 0, inf.dwCursorPosition.Y);
free(tmp);
COORD coord = {0, inf.dwCursorPosition.Y}; // Windows uses 0-based coordinates
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
@ -210,13 +210,23 @@ namespace DFHack
}
}
void output(const char* str, size_t len, int x, int y)
void blankout(const char* str, size_t len, int x, int y)
{
COORD pos = { (SHORT)x, (SHORT)y };
DWORD count = 0;
WriteConsoleOutputCharacterA(console_out, str, len, pos, &count);
}
void output(const char* str, size_t len, int x, int y)
{
COORD pos = { (SHORT)x, (SHORT)y };
DWORD count = 0;
CONSOLE_SCREEN_BUFFER_INFO inf = { 0 };
GetConsoleScreenBufferInfo(console_out, &inf);
SetConsoleCursorPosition(console_out, pos);
WriteConsoleA(console_out, str, len, &count, NULL);
}
void prompt_refresh()
{
size_t cols = get_columns();
@ -245,7 +255,7 @@ namespace DFHack
// Blank to EOL
char* tmp = (char*)malloc(inf.dwSize.X - (plen + len));
memset(tmp, ' ', inf.dwSize.X - (plen + len));
output(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y);
blankout(tmp, inf.dwSize.X - (plen + len), len + plen, inf.dwCursorPosition.Y);
free(tmp);
}
inf.dwCursorPosition.X = (SHORT)(cooked_cursor + plen);

@ -81,6 +81,10 @@ using namespace DFHack;
#include "SDL_events.h"
#ifdef LINUX_BUILD
#include <dlfcn.h>
#endif
using namespace tthread;
using namespace df::enums;
using df::global::init;
@ -429,10 +433,38 @@ command_result Core::runCommand(color_ostream &out, const std::string &command)
return CR_NOT_IMPLEMENTED;
}
// List of built in commands
static const std::set<std::string> built_in_commands = {
"ls" ,
"help" ,
"type" ,
"load" ,
"unload" ,
"reload" ,
"enable" ,
"disable" ,
"plug" ,
"keybinding" ,
"alias" ,
"fpause" ,
"cls" ,
"die" ,
"kill-lua" ,
"script" ,
"hide" ,
"show" ,
"sc-script"
};
static bool try_autocomplete(color_ostream &con, const std::string &first, std::string &completed)
{
std::vector<std::string> possible;
// Check for possible built in commands to autocomplete first
for (auto const &command : built_in_commands)
if (command.substr(0, first.size()) == first)
possible.push_back(command);
auto plug_mgr = Core::getInstance().getPluginManager();
for (auto it = plug_mgr->begin(); it != plug_mgr->end(); ++it)
{
@ -612,28 +644,12 @@ static std::string sc_event_name (state_change_event id) {
string getBuiltinCommand(std::string cmd)
{
std::string builtin = "";
if (cmd == "ls" ||
cmd == "help" ||
cmd == "type" ||
cmd == "load" ||
cmd == "unload" ||
cmd == "reload" ||
cmd == "enable" ||
cmd == "disable" ||
cmd == "plug" ||
cmd == "keybinding" ||
cmd == "alias" ||
cmd == "fpause" ||
cmd == "cls" ||
cmd == "die" ||
cmd == "kill-lua" ||
cmd == "script" ||
cmd == "hide" ||
cmd == "show" ||
cmd == "sc-script"
)
// Check our list of builtin commands from the header
if (built_in_commands.count(cmd))
builtin = cmd;
// Check for some common aliases for built in commands
else if (cmd == "?" || cmd == "man")
builtin = "help";
@ -1607,6 +1623,18 @@ bool Core::Init()
}
cerr << "Version: " << vinfo->getVersion() << endl;
#if defined(_WIN32)
const OSType expected = OS_WINDOWS;
#elif defined(_DARWIN)
const OSType expected = OS_APPLE;
#else
const OSType expected = OS_LINUX;
#endif
if (expected != vinfo->getOS()) {
cerr << "OS mismatch; resetting to " << int(expected) << endl;
vinfo->setOS(expected);
}
// Init global object pointers
df::global::InitGlobals();
alias_mutex = new recursive_mutex();
@ -1614,7 +1642,24 @@ bool Core::Init()
cerr << "Initializing Console.\n";
// init the console.
bool is_text_mode = (init && init->display.flag.is_set(init_display_flags::TEXT));
if (is_text_mode || getenv("DFHACK_DISABLE_CONSOLE"))
bool is_headless = bool(getenv("DFHACK_HEADLESS"));
if (is_headless)
{
#ifdef LINUX_BUILD
auto endwin = (int(*)(void))dlsym(RTLD_DEFAULT, "endwin");
if (endwin)
{
endwin();
}
else
{
cerr << "endwin(): bind failed" << endl;
}
#else
cerr << "Headless mode not supported on Windows" << endl;
#endif
}
if ((is_text_mode && !is_headless) || getenv("DFHACK_DISABLE_CONSOLE"))
{
con.init(true);
cerr << "Console is not available. Use dfhack-run to send commands.\n";
@ -1694,7 +1739,7 @@ bool Core::Init()
HotkeyMutex = new mutex();
HotkeyCond = new condition_variable();
if (!is_text_mode)
if (!is_text_mode || is_headless)
{
cerr << "Starting IO thread.\n";
// create IO thread

@ -36,6 +36,7 @@ distribution.
#include "DataDefs.h"
#include "DataIdentity.h"
#include "VTableInterpose.h"
#include "Error.h"
#include "MiscUtils.h"
@ -310,7 +311,7 @@ void virtual_identity::adjust_vtable(virtual_ptr obj, virtual_identity *main)
return;
std::cerr << "Attempt to create class '" << getName() << "' without known vtable." << std::endl;
abort();
throw DFHack::Error::VTableMissing(getName());
}
virtual_ptr virtual_identity::clone(virtual_ptr obj)

@ -60,4 +60,5 @@ namespace df {
#define FLD(mode, name) struct_field_info::mode, #name, offsetof(CUR_STRUCT, name)
#define GFLD(mode, name) struct_field_info::mode, #name, (size_t)&df::global::name
#define METHOD(mode, name) struct_field_info::mode, #name, 0, wrap_function(&CUR_STRUCT::name)
#define METHOD_N(mode, func, name) struct_field_info::mode, #name, 0, wrap_function(&CUR_STRUCT::func)
#define FLD_END struct_field_info::END

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

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

@ -2429,6 +2429,23 @@ static const luaL_Reg dfhack_designations_funcs[] = {
{NULL, NULL}
};
/***** Console module *****/
namespace console {
void clear() {
Core::getInstance().getConsole().clear();
}
void flush() {
Core::getInstance().getConsole() << std::flush;
}
}
static const LuaWrapper::FunctionReg dfhack_console_module[] = {
WRAPM(console, clear),
WRAPM(console, flush),
{ NULL, NULL }
};
/***** Internal module *****/
static void *checkaddr(lua_State *L, int idx, bool allow_null = false)
@ -2964,5 +2981,6 @@ void OpenDFHackApi(lua_State *state)
OpenModule(state, "screen", dfhack_screen_module, dfhack_screen_funcs);
OpenModule(state, "filesystem", dfhack_filesystem_module, dfhack_filesystem_funcs);
OpenModule(state, "designations", dfhack_designations_module, dfhack_designations_funcs);
OpenModule(state, "console", dfhack_console_module);
OpenModule(state, "internal", dfhack_internal_module, dfhack_internal_funcs);
}

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

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

@ -395,6 +395,7 @@ static command_result GetWorldInfo(color_ostream &stream,
{
case game_type::DWARF_MAIN:
case game_type::DWARF_RECLAIM:
case game_type::DWARF_UNRETIRE:
out->set_mode(GetWorldInfoOut::MODE_DWARF);
out->set_civ_id(ui->civ_id);
out->set_site_id(ui->site_id);
@ -403,6 +404,7 @@ static command_result GetWorldInfo(color_ostream &stream,
break;
case game_type::ADVENTURE_MAIN:
case game_type::ADVENTURE_ARENA:
out->set_mode(GetWorldInfoOut::MODE_ADVENTURE);
if (auto unit = vector_get(world->units.active, 0))

@ -153,7 +153,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
else if (type == "md5-hash")
{
const char *cstr_value = pMemEntry->Attribute("value");
fprintf(stderr, "%s: MD5: %s\n", cstr_name, cstr_value);
fprintf(stderr, "%s (%s): MD5: %s\n", cstr_name, cstr_os, cstr_value);
if(!cstr_value)
throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name);
mem->addMD5(cstr_value);
@ -161,7 +161,7 @@ void VersionInfoFactory::ParseVersion (TiXmlElement* entry, VersionInfo* mem)
else if (type == "binary-timestamp")
{
const char *cstr_value = pMemEntry->Attribute("value");
fprintf(stderr, "%s: PE: %s\n", cstr_name, cstr_value);
fprintf(stderr, "%s (%s): PE: %s\n", cstr_name, cstr_os, cstr_value);
if(!cstr_value)
throw Error::SymbolsXmlUnderspecifiedEntry(cstr_name);
mem->addPE(strtol(cstr_value, 0, 16));

@ -221,6 +221,29 @@ INSTANTIATE_WRAPPERS(11, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11),
LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11, class A12
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12))
INSTANTIATE_WRAPPERS(12, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12),
(OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12),
(vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12),
(out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);
LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11); LOAD_ARG(A12);)
#undef FW_TARGS
#define FW_TARGS class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9, class A10, class A11, class A12, class A13
INSTANTIATE_RETURN_TYPE((A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13))
INSTANTIATE_WRAPPERS(13, (A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13),
(OSTREAM_ARG,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12,A13),
(vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12,vA13),
(out,vA1,vA2,vA3,vA4,vA5,vA6,vA7,vA8,vA9,vA10,vA11,vA12,vA13),
LOAD_ARG(A1); LOAD_ARG(A2); LOAD_ARG(A3); LOAD_ARG(A4);
LOAD_ARG(A5); LOAD_ARG(A6); LOAD_ARG(A7); LOAD_ARG(A8);
LOAD_ARG(A9); LOAD_ARG(A10); LOAD_ARG(A11); LOAD_ARG(A12);
LOAD_ARG(A13);)
#undef FW_TARGS
#undef FW_TARGSC
#undef INSTANTIATE_WRAPPERS
#undef INSTANTIATE_WRAPPERS2

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

@ -276,6 +276,9 @@ public:
return true;
}
int32_t priorityAt(df::coord2d p);
bool setPriorityAt(df::coord2d p, int32_t priority);
df::tile_occupancy OccupancyAt(df::coord2d p)
{
return index_tile<df::tile_occupancy>(occupancy,p);
@ -544,13 +547,31 @@ class DFHACK_EXPORT MapCache
Block * b= BlockAtTile(tilecoord);
return b ? b->DesignationAt(tilecoord) : df::tile_designation();
}
bool setDesignationAt (DFCoord tilecoord, df::tile_designation des)
// priority is optional, only set if >= 0
bool setDesignationAt (DFCoord tilecoord, df::tile_designation des, int32_t priority = -1)
{
if(Block * b= BlockAtTile(tilecoord))
return b->setDesignationAt(tilecoord, des);
if (Block *b = BlockAtTile(tilecoord))
{
if (!b->setDesignationAt(tilecoord, des))
return false;
if (priority >= 0 && b->setPriorityAt(tilecoord, priority))
return false;
return true;
}
return false;
}
int32_t priorityAt (DFCoord tilecoord)
{
Block *b = BlockAtTile(tilecoord);
return b ? b->priorityAt(tilecoord) : -1;
}
bool setPriorityAt (DFCoord tilecoord, int32_t priority)
{
Block *b = BlockAtTile(tilecoord);
return b ? b->setPriorityAt(tilecoord, priority) : false;
}
df::tile_occupancy occupancyAt (DFCoord tilecoord)
{
Block * b= BlockAtTile(tilecoord);

@ -46,6 +46,7 @@ distribution.
namespace df {
struct block_square_event;
struct block_square_event_designation_priorityst;
struct block_square_event_frozen_liquidst;
struct block_square_event_grassst;
struct block_square_event_item_spatterst;
@ -321,7 +322,8 @@ extern DFHACK_EXPORT bool SortBlockEvents(df::map_block *block,
std::vector<df::block_square_event_grassst *>* grass = 0,
std::vector<df::block_square_event_world_constructionst *>* constructions = 0,
std::vector<df::block_square_event_spoorst *>* spoors = 0,
std::vector<df::block_square_event_item_spatterst *>* items = 0
std::vector<df::block_square_event_item_spatterst *>* items = 0,
std::vector<df::block_square_event_designation_priorityst *>* priorities = 0
);
/// remove a block event from the block by address

@ -17,6 +17,7 @@ local function load_patch(name)
local old_bytes = {}
local new_bytes = {}
local has_bytes = false
for line in file:lines() do
if string.match(line, '^%x+:') then
@ -34,10 +35,14 @@ local function load_patch(name)
old_bytes[offset] = oldv
new_bytes[offset] = newv
has_bytes = true
end
end
file:close()
if not has_bytes then
return nil, 'no patch bytes found'
end
return { name = name, old_bytes = old_bytes, new_bytes = new_bytes }
end

@ -562,6 +562,61 @@ function dfhack.run_script_with_env(envVars, name, flags, ...)
return script_code(...), env
end
local function current_script_name()
local frame = 1
while true do
local info = debug.getinfo(frame, 'f')
if not info then break end
if info.func == dfhack.run_script_with_env then
local i = 1
while true do
local name, value = debug.getlocal(frame, i)
if not name then break end
if name == 'name' then
return value
end
i = i + 1
end
break
end
frame = frame + 1
end
end
function dfhack.script_help(script_name, extension)
script_name = script_name or current_script_name()
extension = extension or 'lua'
local full_name = script_name .. '.' .. extension
local path = dfhack.internal.findScript(script_name .. '.' .. extension)
or error("Could not find script: " .. full_name)
local begin_seq, end_seq
if extension == 'rb' then
begin_seq = '=begin'
end_seq = '=end'
else
begin_seq = '[====['
end_seq = ']====]'
end
local f = io.open(path) or error("Could not open " .. path)
local in_help = false
local help = ''
for line in f:lines() do
if line:endswith(begin_seq) then
in_help = true
elseif in_help then
if line:endswith(end_seq) then
break
end
if line ~= script_name and line ~= ('='):rep(#script_name) then
help = help .. line .. '\n'
end
end
end
f:close()
help = help:gsub('^\n+', ''):gsub('\n+$', '')
return help
end
local function _run_command(...)
args = {...}
if type(args[1]) == 'table' then

@ -18,8 +18,8 @@ refreshSidebar = dfhack.gui.refreshSidebar
function getPanelLayout()
local dims = dfhack.gui.getDwarfmodeViewDims()
local area_pos = df.global.ui_area_map_width
local menu_pos = df.global.ui_menu_width
local area_pos = df.global.ui_menu_width[1]
local menu_pos = df.global.ui_menu_width[0]
if dims.menu_forced then
menu_pos = area_pos - 1

@ -21,6 +21,9 @@ function encode_file(data, path, ...)
end
local contents = encode(data, ...)
local f = io.open(path, 'w')
if not f then
error('Could not write to ' .. tostring(path))
end
f:write(contents)
f:close()
end
@ -32,7 +35,7 @@ end
function decode_file(path, ...)
local f = io.open(path)
if not f then
error('Could not open ' .. path)
error('Could not read from ' .. tostring(path))
end
local contents = f:read('*all')
f:close()

@ -48,17 +48,22 @@ using namespace DFHack;
#include "DataDefs.h"
#include "df/announcement_flags.h"
#include "df/announcements.h"
#include "df/assign_trade_status.h"
#include "df/building_cagest.h"
#include "df/building_civzonest.h"
#include "df/building_furnacest.h"
#include "df/building_trapst.h"
#include "df/building_type.h"
#include "df/building_workshopst.h"
#include "df/d_init.h"
#include "df/game_mode.h"
#include "df/general_ref.h"
#include "df/global_objects.h"
#include "df/graphic.h"
#include "df/historical_figure.h"
#include "df/interfacest.h"
#include "df/item_corpsepiecest.h"
#include "df/item_corpsest.h"
#include "df/job.h"
#include "df/layer_object_listst.h"
#include "df/occupation.h"
@ -74,7 +79,10 @@ using namespace DFHack;
#include "df/ui_unit_view_mode.h"
#include "df/unit.h"
#include "df/unit_inventory_item.h"
#include "df/viewscreen_announcelistst.h"
#include "df/viewscreen_assign_display_itemst.h"
#include "df/viewscreen_buildinglistst.h"
#include "df/viewscreen_customize_unitst.h"
#include "df/viewscreen_dungeon_monsterstatusst.h"
#include "df/viewscreen_dungeonmodest.h"
#include "df/viewscreen_dwarfmodest.h"
@ -88,6 +96,7 @@ using namespace DFHack;
#include "df/viewscreen_layer_noblelistst.h"
#include "df/viewscreen_layer_overall_healthst.h"
#include "df/viewscreen_layer_stockpilest.h"
#include "df/viewscreen_layer_unit_healthst.h"
#include "df/viewscreen_layer_unit_relationshipst.h"
#include "df/viewscreen_locationsst.h"
#include "df/viewscreen_petst.h"
@ -96,6 +105,7 @@ using namespace DFHack;
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_unitlistst.h"
#include "df/viewscreen_unitst.h"
#include "df/viewscreen_reportlistst.h"
#include "df/viewscreen_workquota_conditionst.h"
#include "df/viewscreen_workshop_profilest.h"
#include "df/world.h"
@ -108,7 +118,6 @@ using df::global::ui;
using df::global::world;
using df::global::selection_rect;
using df::global::ui_menu_width;
using df::global::ui_area_map_width;
using df::global::gamemode;
static df::layer_object_listst *getLayerList(df::viewscreen_layer *layer, int idx)
@ -818,6 +827,9 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top)
using df::global::ui_look_cursor;
using df::global::ui_look_list;
using df::global::ui_selected_unit;
using df::global::ui_building_in_assign;
using df::global::ui_building_assign_units;
using df::global::ui_building_item_cursor;
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_unitst, top))
{
@ -934,30 +946,138 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top)
return NULL;
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_reportlistst, top))
return vector_get(screen->units, screen->cursor);
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_announcelistst, top))
{
if (screen->unit) {
// in (r)eports -> enter
auto *report = vector_get(screen->reports, screen->sel_idx);
if (report)
{
for (df::unit *unit : world->units.all)
{
if (unit && screen->report_type >= 0 && screen->report_type < 3
&& unit != screen->unit) // find 'other' unit related to this report
{
for (int32_t report_id : unit->reports.log[screen->report_type])
{
if (report_id == report->id)
return unit;
}
}
}
}
} else {
// in (a)nnouncements
return NULL; // cannot determine unit from reports
}
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_militaryst, top))
{
if (screen->page == df::viewscreen_layer_militaryst::T_page::Positions) {
auto positions = getLayerList(screen, 1);
if (positions && positions->enabled && positions->active)
return vector_get(screen->positions.assigned, positions->cursor);
auto candidates = getLayerList(screen, 2);
if (candidates && candidates->enabled && candidates->active)
return vector_get(screen->positions.candidates, candidates->cursor);
}
if (screen->page == df::viewscreen_layer_militaryst::T_page::Equip) {
auto positions = getLayerList(screen, 1);
if (positions && positions->enabled && positions->active)
return vector_get(screen->equip.units, positions->cursor);
}
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_layer_unit_healthst, top))
return screen->unit;
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_customize_unitst, top))
return screen->unit;
if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedUnit();
if (!Gui::dwarfmode_hotkey(top))
return NULL;
if (!ui)
return NULL;
// general assigning units in building, i.e. (q)uery cage -> (a)ssign
if (ui_building_in_assign && *ui_building_in_assign
&& ui_building_assign_units && ui_building_item_cursor
&& ui->main.mode != Zones) // dont show for (i) zone
return vector_get(*ui_building_assign_units, *ui_building_item_cursor);
if (ui->follow_unit != -1)
return df::unit::find(ui->follow_unit);
switch (ui->main.mode) {
case ViewUnits:
{
if (!ui_selected_unit)
if (!ui_selected_unit || !ui_selected_unit)
return NULL;
return vector_get(world->units.active, *ui_selected_unit);
}
case ZonesPitInfo: // (i) zone -> (P)it
case ZonesPenInfo: // (i) zone -> pe(N)
{
if (ui_building_assign_units || ui_building_item_cursor)
return vector_get(*ui_building_assign_units, *ui_building_item_cursor);
return NULL;
}
case Burrows:
{
if (ui->burrows.in_add_units_mode)
return vector_get(ui->burrows.list_units, ui->burrows.unit_cursor_pos);
return NULL;
}
case QueryBuilding:
{
if (df::building *building = getAnyBuilding(top))
{
if (VIRTUAL_CAST_VAR(cage, df::building_cagest, building))
{
if (ui_building_item_cursor)
return df::unit::find(vector_get(cage->assigned_units, *ui_building_item_cursor));
}
}
return NULL;
}
case LookAround:
{
if (!ui_look_list || !ui_look_cursor)
return NULL;
auto item = vector_get(ui_look_list->items, *ui_look_cursor);
if (item && item->type == df::ui_look_list::T_items::Unit)
return item->unit;
else
return NULL;
if (auto item = vector_get(ui_look_list->items, *ui_look_cursor))
{
if (item->type == df::ui_look_list::T_items::Unit)
return item->unit;
else if (item->type == df::ui_look_list::T_items::Item)
{
if (VIRTUAL_CAST_VAR(corpse, df::item_corpsest, item->item))
return df::unit::find(corpse->unit_id); // loo(k) at corpse
else if (VIRTUAL_CAST_VAR(corpsepiece, df::item_corpsepiecest, item->item))
return df::unit::find(corpsepiece->unit_id); // loo(k) at corpse piece
}
else if (item->type == df::ui_look_list::T_items::Spatter)
{
// loo(k) at blood/ichor/.. spatter with a name
MaterialInfo mat;
if (mat.decode(item->spatter_mat_type, item->spatter_mat_index) && mat.figure)
return df::unit::find(mat.figure->unit_id);
}
}
return NULL;
}
default:
return NULL;
@ -1043,6 +1163,15 @@ df::item *Gui::getAnyItem(df::viewscreen *top)
return NULL;
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_assign_display_itemst, top))
{
if (screen->sel_column == df::viewscreen_assign_display_itemst::T_sel_column::Items)
return vector_get(screen->items[screen->item_type[screen->sel_type]],
screen->sel_item);
return NULL;
}
if (auto dfscreen = dfhack_viewscreen::try_cast(top))
return dfscreen->getSelectedItem();
@ -1428,13 +1557,13 @@ void Gui::showAutoAnnouncement(
df::announcement_type type, df::coord pos, std::string message, int color, bool bright,
df::unit *unit1, df::unit *unit2
) {
using df::global::announcements;
using df::global::d_init;
df::announcement_flags flags;
flags.bits.D_DISPLAY = flags.bits.A_DISPLAY = true;
if (is_valid_enum_item(type) && announcements)
flags = announcements->flags[type];
if (is_valid_enum_item(type) && d_init)
flags = d_init->announcements.flags[type];
int id = makeAnnouncement(type, flags, pos, message, color, bright);
@ -1508,8 +1637,8 @@ Gui::DwarfmodeDims getDwarfmodeViewDims_default()
dims.area_x1 = dims.area_x2 = dims.menu_x1 = dims.menu_x2 = -1;
dims.menu_forced = false;
int menu_pos = (ui_menu_width ? *ui_menu_width : 2);
int area_pos = (ui_area_map_width ? *ui_area_map_width : 3);
int menu_pos = (ui_menu_width ? (*ui_menu_width)[0] : 2);
int area_pos = (ui_menu_width ? (*ui_menu_width)[1] : 3);
if (ui && ui->main.mode && menu_pos >= area_pos)
{
@ -1715,14 +1844,14 @@ bool Gui::getWindowSize (int32_t &width, int32_t &height)
bool Gui::getMenuWidth(uint8_t &menu_width, uint8_t &area_map_width)
{
menu_width = *df::global::ui_menu_width;
area_map_width = *df::global::ui_area_map_width;
menu_width = (*df::global::ui_menu_width)[0];
area_map_width = (*df::global::ui_menu_width)[1];
return true;
}
bool Gui::setMenuWidth(const uint8_t menu_width, const uint8_t area_map_width)
{
*df::global::ui_menu_width = menu_width;
*df::global::ui_area_map_width = area_map_width;
(*df::global::ui_menu_width)[0] = menu_width;
(*df::global::ui_menu_width)[1] = area_map_width;
return true;
}

@ -49,8 +49,9 @@ using namespace std;
#include "df/block_burrow.h"
#include "df/block_burrow_link.h"
#include "df/block_square_event_grassst.h"
#include "df/block_square_event_designation_priorityst.h"
#include "df/block_square_event_frozen_liquidst.h"
#include "df/block_square_event_grassst.h"
#include "df/building_type.h"
#include "df/builtin_mats.h"
#include "df/burrow.h"
@ -271,6 +272,43 @@ bool MapExtras::Block::setTiletypeAt(df::coord2d pos, df::tiletype tt, bool forc
return true;
}
static df::block_square_event_designation_priorityst *getPriorityEvent(df::map_block *block, bool write)
{
vector<df::block_square_event_designation_priorityst*> events;
Maps::SortBlockEvents(block, 0, 0, 0, 0, 0, 0, 0, &events);
if (events.empty())
{
if (!write)
return NULL;
auto event = df::allocate<df::block_square_event_designation_priorityst>();
block->block_events.push_back((df::block_square_event*)event);
return event;
}
return events[0];
}
int32_t MapExtras::Block::priorityAt(df::coord2d pos)
{
if (!block)
return false;
if (auto event = getPriorityEvent(block, false))
return event->priority[pos.x % 16][pos.y % 16];
return 0;
}
bool MapExtras::Block::setPriorityAt(df::coord2d pos, int32_t priority)
{
if (!block || priority < 0)
return false;
auto event = getPriorityEvent(block, true);
event->priority[pos.x % 16][pos.y % 16] = priority;
return true;
}
bool MapExtras::Block::setVeinMaterialAt(df::coord2d pos, int16_t mat, df::inclusion_type type)
{
using namespace df::enums::tiletype_material;

@ -402,7 +402,8 @@ bool Maps::SortBlockEvents(df::map_block *block,
vector <df::block_square_event_grassst *> *grasses,
vector <df::block_square_event_world_constructionst *> *constructions,
vector <df::block_square_event_spoorst *> *spoors,
vector <df::block_square_event_item_spatterst *> *items)
vector <df::block_square_event_item_spatterst *> *items,
vector <df::block_square_event_designation_priorityst *> *priorities)
{
if (veins)
veins->clear();
@ -456,6 +457,10 @@ bool Maps::SortBlockEvents(df::map_block *block,
if (items)
items->push_back((df::block_square_event_item_spatterst *)evt);
break;
case block_square_event_type::designation_priority:
if (priorities)
priorities->push_back((df::block_square_event_designation_priorityst *)evt);
break;
}
}
return true;

@ -372,12 +372,6 @@ bool Screen::hasActiveScreens(Plugin *plugin)
}
#ifdef _LINUX
// Link to the libgraphics class directly:
class DFHACK_EXPORT enabler_inputst {
public:
std::string GetKeyDisplay(int binding);
};
class DFHACK_EXPORT renderer {
unsigned char *screen;
long *screentexpos;
@ -418,15 +412,6 @@ public:
virtual bool get_mouse_coords(int &x, int &y) { return false; }
virtual bool uses_opengl();
};
#else
struct less_sz {
bool operator() (const string &a, const string &b) const {
if (a.size() < b.size()) return true;
if (a.size() > b.size()) return false;
return a < b;
}
};
static std::map<df::interface_key,std::set<string,less_sz> > *keydisplay = NULL;
#endif
void init_screen_module(Core *core)
@ -435,26 +420,13 @@ void init_screen_module(Core *core)
renderer tmp;
if (!strict_virtual_cast<df::renderer>((virtual_ptr)&tmp))
cerr << "Could not fetch the renderer vtable." << std::endl;
#else
if (!core->vinfo->getAddress("keydisplay", keydisplay))
keydisplay = NULL;
#endif
}
string Screen::getKeyDisplay(df::interface_key key)
{
#ifdef _LINUX
auto enabler = (enabler_inputst*)df::global::enabler;
if (enabler)
return enabler->GetKeyDisplay(key);
#else
if (keydisplay)
{
auto it = keydisplay->find(key);
if (it != keydisplay->end() && !it->second.empty())
return *it->second.begin();
}
#endif
return "?";
}

@ -1,2 +1 @@
repeat -name warn-starving -time 10 -timeUnits days -command [ warn-starving ]
repeat -name warn-stuck-trees -time 10 -timeUnits days -command [ warn-stuck-trees ]

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

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

@ -405,7 +405,8 @@ static const dwarf_state dwarf_states[] = {
BUSY /* MakeRing */,
BUSY /* MakeEarring */,
BUSY /* MakeBracelet */,
BUSY /* MakeGem */
BUSY /* MakeGem */,
BUSY /* PutItemOnDisplay */,
};
// Mode assigned to labors. Either it's a hauling job, or it's not.

@ -373,7 +373,8 @@ static const dwarf_state dwarf_states[] = {
BUSY /* MakeRing */,
BUSY /* MakeEarring */,
BUSY /* MakeBracelet */,
BUSY /* MakeGem */
BUSY /* MakeGem */,
BUSY /* PutItemOnDisplay */,
};
struct labor_info

@ -99,25 +99,22 @@ bool makeItem (df::reaction_product_itemst *prod, df::unit *unit, bool second_it
for (size_t i = 0; i < out_items.size(); i++)
{
bool on_ground = true;
if (container)
{
on_ground = false;
out_items[i]->flags.bits.removed = 1;
if (!Items::moveToContainer(mc, out_items[i], container))
out_items[i]->moveToGround(container->pos.x, container->pos.y, container->pos.z);
}
if (building)
else if (building)
{
on_ground = false;
out_items[i]->flags.bits.removed = 1;
if (!Items::moveToBuilding(mc, out_items[i], (df::building_actual *)building, 0))
out_items[i]->moveToGround(building->centerx, building->centery, building->z);
}
if (on_ground)
out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z);
if (move_to_cursor)
else if (move_to_cursor)
out_items[i]->moveToGround(cursor->x, cursor->y, cursor->z);
else
out_items[i]->moveToGround(unit->pos.x, unit->pos.y, unit->pos.z);
if (is_gloves)
{
// if the reaction creates gloves without handedness, then create 2 sets (left and right)

@ -53,7 +53,14 @@ size_t convert(const std::string& p,bool ishex=false)
conv>>ret;
return ret;
}
bool isAddr(uintptr_t *trg,vector<t_memrange> & ranges)
bool isAddr(void *trg, vector<t_memrange> &ranges)
{
for (auto &r : ranges)
if (r.isInRange(trg))
return true;
return false;
}
bool isAddrAt(uintptr_t *trg, vector<t_memrange> &ranges)
{
if(trg[0]%4==0)
for(size_t i=0;i<ranges.size();i++)
@ -76,7 +83,7 @@ void outputHex(uint8_t *buf,uint8_t *lbuf,size_t len,size_t start,color_ostream
{
con.reset_color();
if(isAddr((uintptr_t *)(buf+j+i),ranges))
if(isAddrAt((uintptr_t *)(buf+j+i),ranges))
con.color(COLOR_LIGHTRED); //coloring in the middle does not work
//TODO make something better?
}
@ -106,6 +113,17 @@ void Deinit()
delete [] memdata.lbuf;
}
}
size_t detect_size(void *addr) {
size_t *size = (size_t*)((char*)addr - 16);
int32_t *tag = (int32_t*)((char*)addr - 8);
if (isAddr(size, memdata.ranges) && *tag == 0x11223344) {
return *size;
}
// default
return 20 * 16;
}
DFhackCExport command_result plugin_onupdate (color_ostream &out)
{
@ -177,7 +195,9 @@ command_result memview (color_ostream &out, vector <string> & parameters)
is_enabled = true;
memdata.state=STATE_ON;
}
if(parameters.size()>1)
if (vector_get(parameters, 1, string("a")).substr(0, 1) == "a")
memdata.len = detect_size(memdata.addr);
else if (parameters.size()>1)
memdata.len=convert(parameters[1]);
else
memdata.len=20*16;

@ -1,16 +1,23 @@
#include <vector>
#include <cstdio>
#include <cstdlib>
#include <stack>
#include <string>
#include <cmath>
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include "modules/Maps.h"
#include "uicommon.h"
#include "modules/Gui.h"
#include "modules/MapCache.h"
#include "modules/Maps.h"
#include "modules/Materials.h"
#include <vector>
#include <cstdio>
#include <stack>
#include <string>
#include <cmath>
#include "df/ui_sidebar_menus.h"
using std::vector;
using std::string;
using std::stack;
@ -27,6 +34,7 @@ command_result digcircle (color_ostream &out, vector <string> & parameters);
command_result digtype (color_ostream &out, vector <string> & parameters);
DFHACK_PLUGIN("dig");
REQUIRE_GLOBAL(ui_sidebar_menus);
REQUIRE_GLOBAL(world);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
@ -97,6 +105,7 @@ enum circle_what
bool dig (MapExtras::MapCache & MCache,
circle_what what,
df::tile_dig_designation type,
int32_t priority,
int32_t x, int32_t y, int32_t z,
int x_max, int y_max
)
@ -175,20 +184,21 @@ bool dig (MapExtras::MapCache & MCache,
break;
}
std::cerr << "allowing tt" << (int)tt << "\n";
MCache.setDesignationAt(at,des);
MCache.setDesignationAt(at,des,priority);
return true;
};
bool lineX (MapExtras::MapCache & MCache,
circle_what what,
df::tile_dig_designation type,
int32_t priority,
int32_t y1, int32_t y2, int32_t x, int32_t z,
int x_max, int y_max
)
{
for(int32_t y = y1; y <= y2; y++)
{
dig(MCache, what, type,x,y,z, x_max, y_max);
dig(MCache, what, type, priority, x,y,z, x_max, y_max);
}
return true;
};
@ -196,17 +206,55 @@ bool lineX (MapExtras::MapCache & MCache,
bool lineY (MapExtras::MapCache & MCache,
circle_what what,
df::tile_dig_designation type,
int32_t priority,
int32_t x1, int32_t x2, int32_t y, int32_t z,
int x_max, int y_max
)
{
for(int32_t x = x1; x <= x2; x++)
{
dig(MCache, what, type,x,y,z, x_max, y_max);
dig(MCache, what, type, priority, x,y,z, x_max, y_max);
}
return true;
};
int32_t parse_priority(color_ostream &out, vector<string> &parameters)
{
int32_t default_priority = ui_sidebar_menus->designation.priority;
for (auto it = parameters.begin(); it != parameters.end(); ++it)
{
const string &s = *it;
if (s.substr(0, 2) == "p=" || s.substr(0, 2) == "-p")
{
if (s.size() >= 3)
{
auto priority = int32_t(1000 * atof(s.c_str() + 2));
parameters.erase(it);
return priority;
}
else if (it + 1 != parameters.end())
{
auto priority = int32_t(1000 * atof((*(it + 1)).c_str()));
parameters.erase(it, it + 2);
return priority;
}
else
{
out.printerr("invalid priority specified; reverting to %i\n", default_priority);
break;
}
}
}
return default_priority;
}
string forward_priority(color_ostream &out, vector<string> &parameters)
{
return string("-p") + int_to_string(parse_priority(out, parameters) / 1000);
}
command_result digcircle (color_ostream &out, vector <string> & parameters)
{
static bool filled = false;
@ -215,6 +263,8 @@ command_result digcircle (color_ostream &out, vector <string> & parameters)
static int diameter = 0;
auto saved_d = diameter;
bool force_help = false;
int32_t priority = parse_priority(out, parameters);
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
@ -293,6 +343,7 @@ command_result digcircle (color_ostream &out, vector <string> & parameters)
" chan = dig channel\n"
"\n"
" # = diameter in tiles (default = 0)\n"
" -p # = designation priority (default = 4)\n"
"\n"
"After you have set the options, the command called with no options\n"
"repeats with the last selected parameters:\n"
@ -326,12 +377,12 @@ command_result digcircle (color_ostream &out, vector <string> & parameters)
// paint center
if(filled)
{
lineY(MCache,what,type, cx - r, cx + r, cy, cz,x_max,y_max);
lineY(MCache, what, type, priority, cx - r, cx + r, cy, cz, x_max, y_max);
}
else
{
dig(MCache, what, type,cx - r, cy, cz,x_max,y_max);
dig(MCache, what, type,cx + r, cy, cz,x_max,y_max);
dig(MCache, what, type, priority, cx - r, cy, cz, x_max, y_max);
dig(MCache, what, type, priority, cx + r, cy, cz, x_max, y_max);
}
adjust = false;
iter = 2;
@ -363,24 +414,24 @@ command_result digcircle (color_ostream &out, vector <string> & parameters)
// paint
if(filled || iter == diameter - 1)
{
lineY(MCache,what,type, left, right, top , cz,x_max,y_max);
lineY(MCache,what,type, left, right, bottom , cz,x_max,y_max);
lineY(MCache, what, type, priority, left, right, top, cz, x_max, y_max);
lineY(MCache, what, type, priority, left, right, bottom, cz, x_max, y_max);
}
else
{
dig(MCache, what, type,left, top, cz,x_max,y_max);
dig(MCache, what, type,left, bottom, cz,x_max,y_max);
dig(MCache, what, type,right, top, cz,x_max,y_max);
dig(MCache, what, type,right, bottom, cz,x_max,y_max);
dig(MCache, what, type, priority, left, top, cz, x_max, y_max);
dig(MCache, what, type, priority, left, bottom, cz, x_max, y_max);
dig(MCache, what, type, priority, right, top, cz, x_max, y_max);
dig(MCache, what, type, priority, right, bottom, cz, x_max, y_max);
}
if(!filled && diff > 1)
{
int lright = cx + lastwhole;
int lleft = cx - lastwhole + adjust;
lineY(MCache,what,type, lleft + 1, left - 1, top + 1 , cz,x_max,y_max);
lineY(MCache,what,type, right + 1, lright - 1, top + 1 , cz,x_max,y_max);
lineY(MCache,what,type, lleft + 1, left - 1, bottom - 1 , cz,x_max,y_max);
lineY(MCache,what,type, right + 1, lright - 1, bottom - 1 , cz,x_max,y_max);
lineY(MCache, what, type, priority, lleft + 1, left - 1, top + 1 , cz, x_max, y_max);
lineY(MCache, what, type, priority, right + 1, lright - 1, top + 1 , cz, x_max, y_max);
lineY(MCache, what, type, priority, lleft + 1, left - 1, bottom - 1 , cz, x_max, y_max);
lineY(MCache, what, type, priority, right + 1, lright - 1, bottom - 1 , cz, x_max, y_max);
}
lastwhole = whole;
}
@ -808,6 +859,8 @@ command_result digexp (color_ostream &out, vector <string> & parameters)
bool force_help = false;
static explo_how how = EXPLO_NOTHING;
static explo_what what = EXPLO_HIDDEN;
int32_t priority = parse_priority(out, parameters);
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "help" || parameters[i] == "?")
@ -963,7 +1016,7 @@ command_result digexp (color_ostream &out, vector <string> & parameters)
if(cross[y][x])
{
des.bits.dig = tile_dig_designation::Default;
mx.setDesignationAt(pos,des);
mx.setDesignationAt(pos,des,priority);
}
}
mx.WriteAll();
@ -984,6 +1037,7 @@ command_result digvx (color_ostream &out, vector <string> & parameters)
// HOTKEY COMMAND: CORE ALREADY SUSPENDED
vector <string> lol;
lol.push_back("x");
lol.push_back(forward_priority(out, parameters));
return digv(out,lol);
}
@ -992,6 +1046,8 @@ command_result digv (color_ostream &out, vector <string> & parameters)
// HOTKEY COMMAND: CORE ALREADY SUSPENDED
uint32_t x_max,y_max,z_max;
bool updown = false;
int32_t priority = parse_priority(out, parameters);
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters.size() && parameters[0]=="x")
@ -1118,7 +1174,7 @@ command_result digv (color_ostream &out, vector <string> & parameters)
des_minus.bits.dig = tile_dig_designation::UpDownStair;
else
des_minus.bits.dig = tile_dig_designation::UpStair;
MCache->setDesignationAt(current-1,des_minus);
MCache->setDesignationAt(current-1,des_minus,priority);
des.bits.dig = tile_dig_designation::DownStair;
}
@ -1130,7 +1186,7 @@ command_result digv (color_ostream &out, vector <string> & parameters)
des_plus.bits.dig = tile_dig_designation::UpDownStair;
else
des_plus.bits.dig = tile_dig_designation::DownStair;
MCache->setDesignationAt(current+1,des_plus);
MCache->setDesignationAt(current+1,des_plus,priority);
if(des.bits.dig == tile_dig_designation::DownStair)
des.bits.dig = tile_dig_designation::UpDownStair;
@ -1140,7 +1196,7 @@ command_result digv (color_ostream &out, vector <string> & parameters)
}
if(des.bits.dig == tile_dig_designation::No)
des.bits.dig = tile_dig_designation::Default;
MCache->setDesignationAt(current,des);
MCache->setDesignationAt(current,des,priority);
}
}
MCache->WriteAll();
@ -1153,6 +1209,7 @@ command_result diglx (color_ostream &out, vector <string> & parameters)
// HOTKEY COMMAND: CORE ALREADY SUSPENDED
vector <string> lol;
lol.push_back("x");
lol.push_back(forward_priority(out, parameters));
return digl(out,lol);
}
@ -1168,6 +1225,8 @@ command_result digl (color_ostream &out, vector <string> & parameters)
uint32_t x_max,y_max,z_max;
bool updown = false;
bool undo = false;
int32_t priority = parse_priority(out, parameters);
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i]=="x")
@ -1326,7 +1385,7 @@ command_result digl (color_ostream &out, vector <string> & parameters)
// undo mode: clear designation
if(undo)
des_minus.bits.dig = tile_dig_designation::No;
MCache->setDesignationAt(current-1,des_minus);
MCache->setDesignationAt(current-1,des_minus,priority);
des.bits.dig = tile_dig_designation::DownStair;
}
@ -1341,7 +1400,7 @@ command_result digl (color_ostream &out, vector <string> & parameters)
// undo mode: clear designation
if(undo)
des_plus.bits.dig = tile_dig_designation::No;
MCache->setDesignationAt(current+1,des_plus);
MCache->setDesignationAt(current+1,des_plus,priority);
if(des.bits.dig == tile_dig_designation::DownStair)
des.bits.dig = tile_dig_designation::UpDownStair;
@ -1354,7 +1413,7 @@ command_result digl (color_ostream &out, vector <string> & parameters)
// undo mode: clear designation
if(undo)
des.bits.dig = tile_dig_designation::No;
MCache->setDesignationAt(current,des);
MCache->setDesignationAt(current,des,priority);
}
}
MCache->WriteAll();
@ -1371,6 +1430,7 @@ command_result digauto (color_ostream &out, vector <string> & parameters)
command_result digtype (color_ostream &out, vector <string> & parameters)
{
//mostly copy-pasted from digv
int32_t priority = parse_priority(out, parameters);
CoreSuspender suspend;
if ( parameters.size() > 1 )
{
@ -1474,7 +1534,7 @@ command_result digtype (color_ostream &out, vector <string> & parameters)
df::tile_designation designation = mCache->designationAt(current);
designation.bits.dig = baseDes.bits.dig;
mCache->setDesignationAt(current, designation);
mCache->setDesignationAt(current, designation,priority);
}
}
}

@ -18,31 +18,35 @@
#include "modules/Translation.h"
#include "modules/World.h"
#include "modules/Maps.h"
#include "df/activity_event.h"
#include "df/activity_entry.h"
#include "df/unit_preference.h"
#include "df/unit_soul.h"
#include "df/activity_event.h"
#include "df/creature_raw.h"
#include "df/dance_form.h"
#include "df/descriptor_color.h"
#include "df/descriptor_shape.h"
#include "df/item_type.h"
#include "df/itemdef_weaponst.h"
#include "df/itemdef_trapcompst.h"
#include "df/itemdef_toyst.h"
#include "df/itemdef_toolst.h"
#include "df/itemdef_instrumentst.h"
#include "df/itemdef_armorst.h"
#include "df/itemdef_ammost.h"
#include "df/itemdef_siegeammost.h"
#include "df/itemdef_armorst.h"
#include "df/itemdef_foodst.h"
#include "df/itemdef_glovesst.h"
#include "df/itemdef_shoesst.h"
#include "df/itemdef_shieldst.h"
#include "df/itemdef_helmst.h"
#include "df/itemdef_instrumentst.h"
#include "df/itemdef_pantsst.h"
#include "df/itemdef_foodst.h"
#include "df/itemdef_shieldst.h"
#include "df/itemdef_shoesst.h"
#include "df/itemdef_siegeammost.h"
#include "df/itemdef_toolst.h"
#include "df/itemdef_toyst.h"
#include "df/itemdef_trapcompst.h"
#include "df/itemdef_weaponst.h"
#include "df/musical_form.h"
#include "df/poetic_form.h"
#include "df/trapcomp_flags.h"
#include "df/creature_raw.h"
#include "df/unit_preference.h"
#include "df/unit_soul.h"
#include "df/viewscreen_unitst.h"
#include "df/world_raws.h"
#include "df/descriptor_shape.h"
#include "df/descriptor_color.h"
using std::deque;
@ -137,6 +141,14 @@ static string getUnitName(df::unit * unit)
return label;
}
template<typename T>
static string getFormName(int32_t id, const string &default_ = "?") {
T *form = T::find(id);
if (form)
return Translation::TranslateName(&form->name);
return default_;
}
static void send_key(const df::interface_key &key)
{
set< df::interface_key > keys;
@ -480,6 +492,8 @@ public:
void render()
{
using namespace df::enums::interface_key;
if (Screen::isDismissed(this))
return;
@ -493,18 +507,18 @@ public:
int32_t y = gps->dimy - 4;
int32_t x = 2;
OutputHotkeyString(x, y, "Leave", "Esc");
OutputHotkeyString(x, y, "Leave", LEAVESCREEN);
x += 13;
string window_label = "Window Months: " + int_to_string(window_days / min_window);
OutputHotkeyString(x, y, window_label.c_str(), "*");
OutputHotkeyString(x, y, window_label.c_str(), SECONDSCROLL_PAGEDOWN);
++y;
x = 2;
OutputHotkeyString(x, y, "Fort Stats", "Shift-D");
OutputHotkeyString(x, y, "Fort Stats", CUSTOM_SHIFT_D);
x += 3;
OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z");
OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z);
}
std::string getFocusString() { return "dwarfmonitor_dwarfstats"; }
@ -1088,6 +1102,8 @@ public:
void render()
{
using namespace df::enums::interface_key;
if (Screen::isDismissed(this))
return;
@ -1102,18 +1118,18 @@ public:
int32_t y = gps->dimy - 4;
int32_t x = 2;
OutputHotkeyString(x, y, "Leave", "Esc");
OutputHotkeyString(x, y, "Leave", LEAVESCREEN);
x += 13;
string window_label = "Window Months: " + int_to_string(window_days / min_window);
OutputHotkeyString(x, y, window_label.c_str(), "*");
OutputHotkeyString(x, y, window_label.c_str(), SECONDSCROLL_PAGEDOWN);
++y;
x = 2;
OutputHotkeyString(x, y, "Dwarf Stats", "Shift-D");
OutputHotkeyString(x, y, "Dwarf Stats", CUSTOM_SHIFT_D);
x += 3;
OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z");
OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z);
}
std::string getFocusString() { return "dwarfmonitor_fortstats"; }
@ -1201,6 +1217,18 @@ struct preference_map
break;
default:
label = ENUM_ATTR_STR(item_type, caption, pref.item_type);
if (label.size())
{
if (label[label.size() - 1] == 's')
label += "es";
else
label += "s";
}
else
{
label = "UNKNOWN";
}
break;
}
@ -1217,15 +1245,13 @@ struct preference_map
{
case (T_type::LikeCreature):
{
label = "Creature :";
Units::getRaceNamePluralById(pref.creature_id);
label = "Creature :" + Units::getRaceNamePluralById(pref.creature_id);
break;
}
case (T_type::HateCreature):
{
label = "Hates :";
Units::getRaceNamePluralById(pref.creature_id);
label = "Hates :" + Units::getRaceNamePluralById(pref.creature_id);
break;
}
@ -1290,6 +1316,22 @@ struct preference_map
case (T_type::LikeColor):
label += "Color :" + raws.language.colors[pref.color_id]->name;
break;
case (T_type::LikePoeticForm):
label += "Poetry :" + getFormName<df::poetic_form>(pref.poetic_form_id);
break;
case (T_type::LikeMusicalForm):
label += "Music :" + getFormName<df::musical_form>(pref.musical_form_id);
break;
case (T_type::LikeDanceForm):
label += "Dance :" + getFormName<df::dance_form>(pref.dance_form_id);
break;
default:
label += string("UNKNOWN ") + ENUM_KEY_STR(unit_preference::T_type, pref.type);
break;
}
}
};
@ -1304,14 +1346,14 @@ public:
preferences_column.auto_select = true;
preferences_column.setTitle("Preference");
preferences_column.bottom_margin = 3;
preferences_column.search_margin = 35;
preferences_column.search_margin = 50;
dwarf_column.multiselect = false;
dwarf_column.auto_select = true;
dwarf_column.allow_null = true;
dwarf_column.setTitle("Units with Preference");
dwarf_column.bottom_margin = 3;
dwarf_column.search_margin = 35;
dwarf_column.search_margin = 50;
populatePreferencesColumn();
}
@ -1444,6 +1486,18 @@ public:
return false;
break;
case (T_type::LikePoeticForm):
return lhs.poetic_form_id == rhs.poetic_form_id;
break;
case (T_type::LikeMusicalForm):
return lhs.musical_form_id == rhs.musical_form_id;
break;
case (T_type::LikeDanceForm):
return lhs.dance_form_id == rhs.dance_form_id;
break;
default:
return false;
}
@ -1483,8 +1537,13 @@ public:
case (T_type::LikeColor):
return COLOR_BLUE;
case (T_type::LikePoeticForm):
case (T_type::LikeMusicalForm):
case (T_type::LikeDanceForm):
return COLOR_LIGHTCYAN;
default:
return false;
return COLOR_LIGHTMAGENTA;
}
return true;
@ -1543,6 +1602,11 @@ public:
dwarf_column.setHighlight(0);
}
df::unit *getSelectedUnit() override
{
return (selected_column == 1) ? dwarf_column.getFirstSelectedElem() : nullptr;
}
void feed(set<df::interface_key> *input)
{
bool key_processed = false;
@ -1572,9 +1636,19 @@ public:
Screen::dismiss(this);
return;
}
else if (input->count(interface_key::CUSTOM_SHIFT_V))
{
df::unit *unit = getSelectedUnit();
if (unit)
{
auto unitscr = df::allocate<df::viewscreen_unitst>();
unitscr->unit = unit;
Screen::show(unitscr);
}
}
else if (input->count(interface_key::CUSTOM_SHIFT_Z))
{
df::unit *selected_unit = (selected_column == 1) ? dwarf_column.getFirstSelectedElem() : nullptr;
df::unit *selected_unit = getSelectedUnit();
if (selected_unit)
{
input->clear();
@ -1610,6 +1684,8 @@ public:
void render()
{
using namespace df::enums::interface_key;
if (Screen::isDismissed(this))
return;
@ -1623,10 +1699,15 @@ public:
int32_t y = gps->dimy - 3;
int32_t x = 2;
OutputHotkeyString(x, y, "Leave", "Esc");
OutputHotkeyString(x, y, "Leave", LEAVESCREEN);
x += 2;
OutputHotkeyString(x, y, "Zoom Unit", "Shift-Z");
OutputHotkeyString(x, y, "View Unit", CUSTOM_SHIFT_V, false, 0,
getSelectedUnit() ? COLOR_WHITE : COLOR_DARKGREY);
x += 2;
OutputHotkeyString(x, y, "Zoom Unit", CUSTOM_SHIFT_Z, false, 0,
getSelectedUnit() ? COLOR_WHITE : COLOR_DARKGREY);
}
std::string getFocusString() { return "dwarfmonitor_preferences"; }
@ -1789,15 +1870,11 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest
{
typedef df::viewscreen_dwarfmodest interpose_base;
DEFINE_VMETHOD_INTERPOSE(void, feed, (set<df::interface_key> *input))
{
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
CoreSuspendClaimer suspend;
if (Maps::IsValid())
{
dm_lua::call("render_all");
@ -1805,7 +1882,6 @@ struct dwarf_monitor_hook : public df::viewscreen_dwarfmodest
}
};
IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(dwarf_monitor_hook, render);
static bool set_monitoring_mode(const string &mode, const bool &state)
@ -1852,8 +1928,7 @@ DFhackCExport command_result plugin_enable(color_ostream &out, bool enable)
load_config();
if (is_enabled != enable)
{
if (!INTERPOSE_HOOK(dwarf_monitor_hook, feed).apply(enable) ||
!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable))
if (!INTERPOSE_HOOK(dwarf_monitor_hook, render).apply(enable))
return CR_FAILURE;
reset();

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

@ -0,0 +1,314 @@
#include "Core.h"
#include <Console.h>
#include <string>
#include <set>
#include <modules/Gui.h>
#include "Types.h"
#include "help_ui.h"
#include "screen.h"
using std::vector;
namespace embark_assist{
namespace help_ui {
enum class pages {
Intro,
General,
Finder,
Caveats
};
class ViewscreenHelpUi : public dfhack_viewscreen
{
public:
ViewscreenHelpUi();
void feed(std::set<df::interface_key> *input);
void render();
std::string getFocusString() { return "Help UI"; }
private:
pages current_page = pages::Intro;
};
//===============================================================================
void ViewscreenHelpUi::feed(std::set<df::interface_key> *input) {
if (input->count(df::interface_key::LEAVESCREEN))
{
input->clear();
Screen::dismiss(this);
return;
}
else if (input->count(df::interface_key::CHANGETAB)) {
switch (current_page) {
case pages::Intro:
current_page = pages::General;
break;
case pages::General:
current_page = pages::Finder;
break;
case pages::Finder:
current_page = pages::Caveats;
break;
case pages::Caveats:
current_page = pages::Intro;
break;
}
}
else if (input->count(df::interface_key::SEC_CHANGETAB)) {
switch (current_page) {
case pages::Intro:
current_page = pages::Caveats;
break;
case pages::General:
current_page = pages::Intro;
break;
case pages::Finder:
current_page = pages::General;
break;
case pages::Caveats:
current_page = pages::Intro;
break;
}
}
}
//===============================================================================
void ViewscreenHelpUi::render() {
color_ostream_proxy out(Core::getInstance().getConsole());
auto screen_size = DFHack::Screen::getWindowSize();
Screen::Pen pen(' ', COLOR_WHITE);
Screen::Pen site_pen = Screen::Pen(' ', COLOR_YELLOW, COLOR_BLACK, false);
Screen::Pen pen_lr(' ', COLOR_LIGHTRED);
std::vector<std::string> help_text;
Screen::clear();
switch (current_page) {
case pages::Intro:
Screen::drawBorder("Embark Assistant Help/Info Introduction Page");
help_text.push_back("Embark Assistant is used on the embark selection screen to provide information");
help_text.push_back("to help selecting a suitable embark site. It provides three services:");
help_text.push_back("- Display of normally invisible sites overlayed on the region map.");
help_text.push_back("- Embark rectangle resources. More detailed and correct than vanilla DF.");
help_text.push_back("- Site find search. Richer set of selection criteria than the vanilla");
help_text.push_back(" DF Find that Embark Assistant suppresses (by using the same key).");
help_text.push_back("");
help_text.push_back("The functionality requires a screen height of at least 42 lines to display");
help_text.push_back("correctly (that's the height of the Finder screen), as fitting everything");
help_text.push_back("onto a standard 80*25 screen would be too challenging. The help is adjusted");
help_text.push_back("to fit into onto an 80*42 screen.");
help_text.push_back("This help/info is split over several screens, and you can move between them");
help_text.push_back("using the TAB/Shift-TAB keys, and leave the help from any screen using ESC.");
help_text.push_back("");
help_text.push_back("When the Embark Assistant is started it provides site information (if any)");
help_text.push_back("as an overlay over the region map. Beneath that you will find a summary of");
help_text.push_back("the resources in the current embark rectangle (explained in more detail on");
help_text.push_back("the the next screen).");
help_text.push_back("On the right side the command keys the Embark Assistant uses are listed");
help_text.push_back("(this partially overwrites the DFHack Embark Tools information until the");
help_text.push_back("screen is resized). It can also be mentioned that the DF 'f'ind key help");
help_text.push_back("at the bottom of the screen is masked as the functionality is overridden by");
help_text.push_back("the Embark Assistant.");
help_text.push_back("Main screen control keys used by the Embark Assistant:");
help_text.push_back("i: Info/Help. Brings up this display.");
help_text.push_back("f: Brings up the Find Embark screen. See the Find page for more information.");
help_text.push_back("c: Clears the results of a Find operation, and also cancels an operation if");
help_text.push_back(" one is under way.");
help_text.push_back("q: Quits the Embark Assistant and brings you back to the vanilla DF interface.");
help_text.push_back(" It can be noted that the Embark Assistant automatically cancels itself");
help_text.push_back(" when DF leaves the embark screen either through <ESC>Abort Game or by");
help_text.push_back(" embarking.");
help_text.push_back("Below this a Matching World Tiles count is displayed. It shows the number");
help_text.push_back("of World Tiles that have at least one embark matching the Find criteria.");
break;
case pages::General:
Screen::drawBorder("Embark Assistant Help/Info General Page");
help_text.push_back("The Embark Assistant overlays the region map with characters indicating sites");
help_text.push_back("normally not displayed by DF. The following key is used:");
help_text.push_back("C: Camp");
help_text.push_back("c: Cave. Only displayed if the DF worldgen parameter does not display caves.");
help_text.push_back("i: Important Location. The author doesn't actually know what those are.");
help_text.push_back("l: Lair");
help_text.push_back("L: Labyrinth");
help_text.push_back("M: Monument. The author is unsure how/if this is broken down further.");
help_text.push_back("S: Shrine");
help_text.push_back("V: Vault");
help_text.push_back("The Embark info below the region map differs from the vanilla DF display in a");
help_text.push_back("few respects. Firstly, it shows resources in the embark rectangle, rather than");
help_text.push_back("DF's display of resources in the region DF currently displays. Secondly, the");
help_text.push_back("DF display doesn't take elevation based soil erosion or the magma sea depth");
help_text.push_back("into consideration, so it can display resources that actually are cut away.");
help_text.push_back("(It can be noted that the DFHack Sand indicator does take these elements into");
help_text.push_back("account).");
help_text.push_back("The info the Embark Assistant displays is:");
help_text.push_back("Sand, if present");
help_text.push_back("Clay, if present");
help_text.push_back("Min and Max soil depth in the embark rectangle.");
help_text.push_back("Flat indicator if all the tiles in the embark have the same elevation.");
help_text.push_back("Aquifer indicator, color coded as blue if all tiles have an aquifer and light");
help_text.push_back("blue if some, but not all, tiles have one.");
help_text.push_back("Waterfall, if the embark has river elevation differences.");
help_text.push_back("Flux, if present");
help_text.push_back("A list of all metals present in the embark.");
help_text.push_back("A list of all economic minerals present in the embark. Both clays and flux");
help_text.push_back("stones are economic, so they show up here as well.");
help_text.push_back("In addition to the above, the Find functionality can also produce blinking");
help_text.push_back("overlays over the region map and the middle world map to indicate where");
help_text.push_back("matching embarks are found. The region display marks the top left corner of");
help_text.push_back("a matching embark rectangle as a matching tile.");
break;
case pages::Finder:
Screen::drawBorder("Embark Assistant Help/Info Find Page");
help_text.push_back("The Embark Assist Finder page is brought up with the f command key.");
help_text.push_back("The top of the Finder page lists the command keys available on the page:");
help_text.push_back("4/6 or horizontal arrow keys to move between the element and element values.");
help_text.push_back("8/2 or vertical arrow keys to move up/down in the current list.");
help_text.push_back("ENTER to select a value in the value list, entering it among the selections.");
help_text.push_back("f to activate the Find functionality using the values in the middle column.");
help_text.push_back("ESC to leave the screen without activating a Find operation.");
help_text.push_back("The X and Y dimensions are those of the embark to search for. Unlike DF");
help_text.push_back("itself these parameters are initiated to match the actual embark rectangle");
help_text.push_back("when a new search is initiated (prior results are cleared.");
help_text.push_back("The 6 Savagery and Evilness parameters takes some getting used to. They");
help_text.push_back("allow for searching for embarks with e.g. has both Good and Evil tiles.");
help_text.push_back("All as a parameter means every embark tile has to have this value.");
help_text.push_back("Present means at least one embark tile has to have this value.");
help_text.push_back("Absent means the feature mustn't exist in any of the embark tiles.");
help_text.push_back("N/A means no restrictions are applied.");
help_text.push_back("The Aquifer criterion introduces some new parameters:");
help_text.push_back("Partial means at least one tile has to have an aquifer, but it also has");
help_text.push_back("to be absent from at least one tile. Not All means an aquifer is tolerated");
help_text.push_back("as long as at least one tile doesn't have one, but it doesn't have to have");
help_text.push_back("any at all.");
help_text.push_back("Min/Max rivers should be self explanatory. The Yes and No values of");
help_text.push_back("Waterfall, Flat, etc. means one has to be Present and Absent respectivey.");
help_text.push_back("Min/Max soil uses the same terminology as DF for 1-4. The Min Soil");
help_text.push_back("Everywhere toggles the Min Soil parameter between acting as All and");
help_text.push_back("and Present.");
help_text.push_back("The parameters for biomes, regions, etc. all require that the required");
help_text.push_back("feature is Present in the embark, and entering the same value multiple");
help_text.push_back("times does nothing (the first match ticks all requirements off). It can be");
help_text.push_back("noted that all the Economic materials are found in the much longer Mineral");
help_text.push_back("list. Note that Find is a fairly time consuming task (is it is in vanilla).");
break;
case pages::Caveats:
Screen::drawBorder("Embark Assistant Help/Info Caveats Page");
help_text.push_back("Find searching first does a sanity check (e.g. max < min) and then a rough");
help_text.push_back("world tile match to find tiles that may have matching embarks. This results");
help_text.push_back("in an overlay of inverted yellow X on top of the middle world map. Then");
help_text.push_back("those tiles are scanned in detail, one feature shell (16*16 world tile");
help_text.push_back("block) at a time, and the results are displayed as green inverted X on");
help_text.push_back("the same map (replacing or erasing the yellow ones). region map overlay");
help_text.push_back("data is generated as well.");
help_text.push_back("");
help_text.push_back("Caveats & technical stuff:");
help_text.push_back("- The Find searching uses simulated cursor movement input to DF to get it");
help_text.push_back(" to load feature shells and detailed region data, and this costs the");
help_text.push_back(" least when done one feature shell at a time.");
help_text.push_back("- The search strategy causes detailed region data to update surveyed");
help_text.push_back(" world info, and this can cause a subsequent search to generate a smaller");
help_text.push_back(" set of preliminary matches (yellow tiles) than a previous search.");
help_text.push_back(" However, this is a bug only if it causes the search to fail to find");
help_text.push_back(" actual existing matches.");
help_text.push_back("- The site info is deduced by the author, so there may be errors and");
help_text.push_back(" there are probably site types that end up not being identified.");
help_text.push_back("- Aquifer indications are based on the author's belief that they occur");
help_text.push_back(" whenever an aquifer supporting layer is present at a depth of 3 or");
help_text.push_back(" more.");
help_text.push_back("- The biome determination logic comes from code provided by Ragundo,");
help_text.push_back(" with only marginal changes by the author. References can be found in");
help_text.push_back(" the source file.");
help_text.push_back("- Thralling is determined by weather material interactions causing");
help_text.push_back(" blinking, which the author believes is one of 4 thralling changes.");
help_text.push_back("- The geo information is gathered by code which is essentially a");
help_text.push_back(" copy of parts of prospector's code adapted for this plugin.");
help_text.push_back("- Clay determination is made by finding the reaction MAKE_CLAY_BRICKS.");
help_text.push_back(" Flux determination is made by finding the reaction PIG_IRON_MAKING.");
help_text.push_back("- Right world map overlay not implemented as author has failed to");
help_text.push_back(" emulate the sizing logic exactly.");
help_text.push_back("Version 0.1 2017-08-30");
break;
}
// Add control keys to first line.
embark_assist::screen::paintString(pen_lr, 1, 1, "TAB/Shift-TAB");
embark_assist::screen::paintString(pen, 14, 1, ":Next/Previous Page");
embark_assist::screen::paintString(pen_lr, 34, 1, "ESC");
embark_assist::screen::paintString(pen, 37, 1, ":Leave Info/Help");
for (uint16_t i = 0; i < help_text.size(); i++) {
embark_assist::screen::paintString(pen, 1, 2 + i, help_text[i]);
}
switch (current_page) {
case pages::Intro:
embark_assist::screen::paintString(pen_lr, 1, 26, "i");
embark_assist::screen::paintString(pen_lr, 1, 27, "f");
embark_assist::screen::paintString(pen_lr, 1, 28, "c");
embark_assist::screen::paintString(pen_lr, 1, 30, "q");
break;
case pages::General:
embark_assist::screen::paintString(site_pen, 1, 4, "C");
embark_assist::screen::paintString(site_pen, 1, 5, "c");
embark_assist::screen::paintString(site_pen, 1, 6, "i");
embark_assist::screen::paintString(site_pen, 1, 7, "l");
embark_assist::screen::paintString(site_pen, 1, 8, "L");
embark_assist::screen::paintString(site_pen, 1, 9, "M");
embark_assist::screen::paintString(site_pen, 1, 10, "S");
embark_assist::screen::paintString(site_pen, 1, 11, "V");
break;
case pages::Finder:
embark_assist::screen::paintString(pen_lr, 1, 4, "4/6");
embark_assist::screen::paintString(pen_lr, 1, 5, "8/2");
embark_assist::screen::paintString(pen_lr, 1, 6, "ENTER");
embark_assist::screen::paintString(pen_lr, 1, 7, "f");
embark_assist::screen::paintString(pen_lr, 1, 8, "ESC");
break;
case pages::Caveats:
break;
}
dfhack_viewscreen::render();
}
//===============================================================================
ViewscreenHelpUi::ViewscreenHelpUi() {
}
}
}
//===============================================================================
// Exported operations
//===============================================================================
void embark_assist::help_ui::init(DFHack::Plugin *plugin_self) {
Screen::show(new embark_assist::help_ui::ViewscreenHelpUi(), plugin_self);
}

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

File diff suppressed because it is too large Load Diff

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

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

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

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

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

File diff suppressed because it is too large Load Diff

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

@ -57,8 +57,6 @@ using df::global::ui;
using df::global::world;
using df::global::gamemode;
using df::global::ui_build_selector;
using df::global::ui_menu_width;
using df::global::ui_area_map_width;
using namespace DFHack::Gui;
using Screen::Pen;

@ -358,6 +358,7 @@ public:
case df::building_type::GrateWall:
case df::building_type::Bookcase:
case df::building_type::Instrument:
case df::building_type::DisplayFurniture:
return df::unit_labor::HAUL_FURNITURE;
case df::building_type::Trap:
case df::building_type::GearAssembly:
@ -463,6 +464,7 @@ public:
case df::building_type::GrateWall:
case df::building_type::Bookcase:
case df::building_type::Instrument:
case df::building_type::DisplayFurniture:
return df::unit_labor::HAUL_FURNITURE;
case df::building_type::AnimalTrap:
return df::unit_labor::TRAPPER;
@ -882,6 +884,7 @@ JobLaborMapper::JobLaborMapper()
job_to_labor_table[df::job_type::MakeEarring] = jlf_make_object;
job_to_labor_table[df::job_type::MakeBracelet] = jlf_make_object;
job_to_labor_table[df::job_type::MakeGem] = jlf_make_object;
job_to_labor_table[df::job_type::PutItemOnDisplay] = jlf_const(df::unit_labor::HAUL_ITEM);
job_to_labor_table[df::job_type::StoreItemInLocation] = jlf_no_labor; // StoreItemInLocation
};

@ -377,7 +377,8 @@ static const dwarf_state dwarf_states[] = {
BUSY /* MakeRing */,
BUSY /* MakeEarring */,
BUSY /* MakeBracelet */,
BUSY /* MakeGem */
BUSY /* MakeGem */,
BUSY /* PutItemOnDisplay */,
};
struct labor_info

@ -113,7 +113,7 @@ public:
for (int i = display_start_offset; i < display_list.size() && i < last_index_able_to_display; i++)
{
++y;
UIColor fg_color = (display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color;
UIColor fg_color = (is_selected_column && display_list[i]->selected) ? COLOR_SELECTED : display_list[i]->color;
UIColor bg_color = (is_selected_column && i == highlighted_index) ? COLOR_HIGHLIGHTED : COLOR_BLACK;
string item_label = display_list[i]->text;

@ -0,0 +1,105 @@
package AdventureControl;
//Attempts to provide a complete framework for reading everything from a fortress needed for vizualization
option optimize_for = LITE_RUNTIME;
import "RemoteFortressReader.proto";
enum AdvmodeMenu
{
Default = 0;
Look = 1;
ConversationAddress = 2;
ConversationSelect = 3;
ConversationSpeak = 4;
Inventory = 5;
Drop = 6;
ThrowItem = 7;
Wear = 8;
Remove = 9;
Interact = 10;
Put = 11;
PutContainer = 12;
Eat = 13;
ThrowAim = 14;
Fire = 15;
Get = 16;
Unk17 = 17;
CombatPrefs = 18;
Companions = 19;
MovementPrefs = 20;
SpeedPrefs = 21;
InteractAction = 22;
MoveCarefully = 23;
Announcements = 24;
UseBuilding = 25;
Travel = 26;
Unk27 = 27;
Unk28 = 28;
SleepConfirm = 29;
SelectInteractionTarget = 30;
Unk31 = 31;
Unk32 = 32;
FallAction = 33;
ViewTracks = 34;
Jump = 35;
Unk36 = 36;
AttackConfirm = 37;
AttackType = 38;
AttackBodypart = 39;
AttackStrike = 40;
Unk41 = 41;
Unk42 = 42;
DodgeDirection = 43;
Unk44 = 44;
Unk45 = 45;
Build = 46;
}
enum CarefulMovementType
{
DEFAULT_MOVEMENT = 0;
RELEASE_ITEM_HOLD = 1;
RELEASE_TILE_HOLD = 2;
ATTACK_CREATURE = 3;
HOLD_TILE = 4;
MOVE = 5;
CLIMB = 6;
HOLD_ITEM = 7;
BUILDING_INTERACT = 8;
ITEM_INTERACT = 9;
ITEM_INTERACT_GUIDE = 10;
ITEM_INTERACT_RIDE = 11;
ITEM_INTERACT_PUSH = 12;
}
enum MiscMoveType
{
SET_CLIMB = 0;
SET_STAND = 1;
SET_CANCEL = 2;
}
message MoveCommandParams
{
optional RemoteFortressReader.Coord direction = 1;
}
message MovementOption
{
optional RemoteFortressReader.Coord dest = 1;
optional RemoteFortressReader.Coord source = 2;
optional RemoteFortressReader.Coord grab = 3;
optional CarefulMovementType movement_type = 4;
}
message MenuContents
{
optional AdvmodeMenu current_menu = 1;
repeated MovementOption movements = 2;
}
message MiscMoveParams
{
optional MiscMoveType type = 1;
}

@ -267,6 +267,17 @@ message Item
optional MatPair type = 5;
optional MatPair material = 6;
optional ColorDefinition dye = 7;
optional int32 stack_size = 8;
optional float subpos_x = 9;
optional float subpos_y = 10;
optional float subpos_z = 11;
optional bool projectile = 12;
optional float velocity_x = 13;
optional float velocity_y = 14;
optional float velocity_z = 15;
optional int32 volume = 16;
repeated ItemImprovement improvements = 17;
optional ArtImage image = 18;
}
message MapBlock
@ -423,6 +434,7 @@ message BlockList
repeated MapBlock map_blocks = 1;
optional int32 map_x = 2;
optional int32 map_y = 3;
repeated Engraving engravings = 4;
}
message PlantDef
@ -867,3 +879,149 @@ message Status
{
repeated Report reports = 1;
}
message ShapeDescriptior
{
optional string id = 1;
optional int32 tile = 2;
}
message Language
{
repeated ShapeDescriptior shapes = 1;
}
enum ImprovementType
{
ART_IMAGE = 0;
COVERED = 1;
RINGS_HANGING = 2;
BANDS = 3;
SPIKES = 4;
ITEMSPECIFIC = 5;
THREAD = 6;
CLOTH = 7;
SEWN_IMAGE = 8;
PAGES = 9;
ILLUSTRATION = 10;
INSTRUMENT_PIECE = 11;
WRITING = 12;
}
message ItemImprovement
{
optional MatPair material = 1;
optional ImprovementType type = 2;
optional int32 shape = 3;
optional int32 specific_type= 4;
optional ArtImage image = 5;
}
enum ArtImageElementType
{
IMAGE_CREATURE = 0;
IMAGE_PLANT = 1;
IMAGE_TREE = 2;
IMAGE_SHAPE = 3;
IMAGE_ITEM = 4;
}
message ArtImageElement
{
optional int32 count = 1;
optional ArtImageElementType type = 2;
optional MatPair creature_item = 3;
optional MatPair material = 5;
optional int32 id = 6;
}
enum ArtImagePropertyType
{
TRANSITIVE_VERB = 0;
INTRANSITIVE_VERB = 1;
}
message ArtImageProperty
{
optional int32 subject = 1;
optional int32 object = 2;
optional ArtImageVerb verb = 3;
optional ArtImagePropertyType type = 4;
}
message ArtImage
{
repeated ArtImageElement elements = 1;
optional MatPair id = 2;
repeated ArtImageProperty properties = 3;
}
message Engraving
{
optional Coord pos = 1;
optional int32 quality = 2;
optional int32 tile = 3;
optional ArtImage image = 4;
optional bool floor = 5;
optional bool west = 6;
optional bool east = 7;
optional bool north = 8;
optional bool south = 9;
optional bool hidden = 10;
optional bool northwest = 11;
optional bool northeast = 12;
optional bool southwest = 13;
optional bool southeast = 14;
}
enum ArtImageVerb
{
VERB_WITHERING = 0;
VERB_SURROUNDEDBY = 1;
VERB_MASSACRING = 2;
VERB_FIGHTING = 3;
VERB_LABORING = 4;
VERB_GREETING = 5;
VERB_REFUSING = 6;
VERB_SPEAKING = 7;
VERB_EMBRACING = 8;
VERB_STRIKINGDOWN = 9;
VERB_MENACINGPOSE = 10;
VERB_TRAVELING = 11;
VERB_RAISING = 12;
VERB_HIDING = 13;
VERB_LOOKINGCONFUSED = 14;
VERB_LOOKINGTERRIFIED = 15;
VERB_DEVOURING = 16;
VERB_ADMIRING = 17;
VERB_BURNING = 18;
VERB_WEEPING = 19;
VERB_LOOKINGDEJECTED = 20;
VERB_CRINGING = 21;
VERB_SCREAMING = 22;
VERB_SUBMISSIVEGESTURE = 23;
VERB_FETALPOSITION = 24;
VERB_SMEAREDINTOSPIRAL = 25;
VERB_FALLING = 26;
VERB_DEAD = 27;
VERB_LAUGHING = 28;
VERB_LOOKINGOFFENDED = 29;
VERB_BEINGSHOT = 30;
VERB_PLAINTIVEGESTURE = 31;
VERB_MELTING = 32;
VERB_SHOOTING = 33;
VERB_TORTURING = 34;
VERB_COMMITTINGDEPRAVEDACT = 35;
VERB_PRAYING = 36;
VERB_CONTEMPLATING = 37;
VERB_COOKING = 38;
VERB_ENGRAVING = 39;
VERB_PROSTRATING = 40;
VERB_SUFFERING = 41;
VERB_BEINGIMPALED = 42;
VERB_BEINGCONTORTED = 43;
VERB_BEINGFLAYED = 44;
VERB_HANGINGFROM = 45;
VERB_BEINGMUTILATED = 46;
VERB_TRIUMPHANTPOSE = 47;
}

@ -2,16 +2,21 @@ PROJECT (remotefortressreader)
# A list of source files
SET(PROJECT_SRCS
remotefortressreader.cpp
adventure_control.cpp
building_reader.cpp
item_reader.cpp
)
# A list of headers
SET(PROJECT_HDRS
adventure_control.h
building_reader.h
item_reader.h
df_version_int.h
)
#proto files to include.
SET(PROJECT_PROTO
../../proto/RemoteFortressReader
../../proto/AdventureControl
)
SET_SOURCE_FILES_PROPERTIES( ${PROJECT_HDRS} PROPERTIES HEADER_FILE_ONLY TRUE)

@ -0,0 +1,317 @@
#include "adventure_control.h"
#include "DataDefs.h"
#include "df/adventure_movement_attack_creaturest.h"
#include "df/adventure_movement_building_interactst.h"
#include "df/adventure_movement_climbst.h"
#include "df/adventure_movement_hold_itemst.h"
#include "df/adventure_movement_hold_tilest.h"
#include "df/adventure_movement_optionst.h"
#include "df/ui_advmode.h"
#include "df/viewscreen.h"
#include "modules/Gui.h"
#include <queue>
using namespace AdventureControl;
using namespace df::enums;
using namespace DFHack;
using namespace Gui;
std::queue<interface_key::interface_key> keyQueue;
void KeyUpdate()
{
if (!keyQueue.empty())
{
getCurViewscreen()->feed_key(keyQueue.front());
keyQueue.pop();
}
}
void SetCoord(df::coord in, RemoteFortressReader::Coord *out)
{
out->set_x(in.x);
out->set_y(in.y);
out->set_z(in.z);
}
command_result MoveCommand(DFHack::color_ostream &stream, const MoveCommandParams *in)
{
auto viewScreen = getCurViewscreen();
if (!in->has_direction())
return CR_WRONG_USAGE;
if (!df::global::ui_advmode->menu == ui_advmode_menu::Default)
return CR_OK;
auto dir = in->direction();
switch (dir.x())
{
case -1:
switch (dir.y())
{
case -1:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_NW_DOWN);
break;
case 0:
viewScreen->feed_key(interface_key::A_MOVE_NW);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_NW_UP);
break;
}
break;
case 0:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_W_DOWN);
break;
case 0:
viewScreen->feed_key(interface_key::A_MOVE_W);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_W_UP);
break;
}
break;
case 1:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_SW_DOWN);
break;
case 0:
viewScreen->feed_key(interface_key::A_MOVE_SW);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_SW_UP);
break;
}
break;
}
break;
case 0:
switch (dir.y())
{
case -1:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_N_DOWN);
break;
case 0:
viewScreen->feed_key(interface_key::A_MOVE_N);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_N_UP);
break;
}
break;
case 0:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_DOWN);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_UP);
break;
}
break;
case 1:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_S_DOWN);
break;
case 0:
viewScreen->feed_key(interface_key::A_MOVE_S);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_S_UP);
break;
}
break;
}
break;
case 1:
switch (dir.y())
{
case -1:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_NE_DOWN);
break;
case 0:
viewScreen->feed_key(interface_key::A_MOVE_NE);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_NE_UP);
break;
}
break;
case 0:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_E_DOWN);
break;
case 0:
viewScreen->feed_key(interface_key::A_MOVE_E);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_E_UP);
break;
}
break;
case 1:
switch (dir.z())
{
case -1:
viewScreen->feed_key(interface_key::A_MOVE_SE_DOWN);
break;
case 0:
viewScreen->feed_key(interface_key::A_MOVE_SE);
break;
case 1:
viewScreen->feed_key(interface_key::A_MOVE_SE_UP);
break;
}
break;
}
break;
}
return CR_OK;
}
command_result JumpCommand(DFHack::color_ostream &stream, const MoveCommandParams *in)
{
if (!in->has_direction())
return CR_WRONG_USAGE;
if (!df::global::ui_advmode->menu == ui_advmode_menu::Default)
return CR_OK;
auto dir = in->direction();
keyQueue.push(interface_key::A_JUMP);
int x = dir.x();
int y = dir.y();
if (x > 0)
{
for (int i = 0; i < x; i++)
{
keyQueue.push(interface_key::CURSOR_RIGHT);
}
}
if (x < 0)
{
for (int i = 0; i > x; i--)
{
keyQueue.push(interface_key::CURSOR_LEFT);
}
}
if (y > 0)
{
for (int i = 0; i < y; i++)
{
keyQueue.push(interface_key::CURSOR_DOWN);
}
}
if (y < 0)
{
for (int i = 0; i > y; i--)
{
keyQueue.push(interface_key::CURSOR_UP);
}
}
keyQueue.push(interface_key::SELECT);
return CR_OK;
}
command_result MenuQuery(DFHack::color_ostream &stream, const EmptyMessage *in, MenuContents *out)
{
auto advUi = df::global::ui_advmode;
if (advUi == NULL)
return CR_FAILURE;
out->set_current_menu((AdvmodeMenu)advUi->menu);
//Fixme: Needs a proper way to control it, but for now, this is the only way to allow Armok Vision to keep going without the user needing to switch to DF.
if (advUi->menu == ui_advmode_menu::FallAction)
{
getCurViewscreen()->feed_key(interface_key::OPTION1);
}
switch (advUi->menu)
{
case ui_advmode_menu::MoveCarefully:
for (size_t i = 0; i < advUi->movements.size(); i++)
{
auto movement = advUi->movements[i];
auto send_movement = out->add_movements();
SetCoord(movement->source, send_movement->mutable_source());
SetCoord(movement->dest, send_movement->mutable_dest());
STRICT_VIRTUAL_CAST_VAR(climbMovement, df::adventure_movement_climbst, movement);
if (climbMovement)
{
SetCoord(climbMovement->grab, send_movement->mutable_grab());
send_movement->set_movement_type(CarefulMovementType::CLIMB);
}
STRICT_VIRTUAL_CAST_VAR(holdTileMovement, df::adventure_movement_hold_tilest, movement);
if (holdTileMovement)
{
SetCoord(holdTileMovement->grab, send_movement->mutable_grab());
send_movement->set_movement_type(CarefulMovementType::HOLD_TILE);
}
}
default:
break;
}
return CR_OK;
}
command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfproto::IntMessage *in)
{
if (!(df::global::ui_advmode->menu == ui_advmode_menu::MoveCarefully))
return CR_OK;
int choice = in->value();
int page = choice / 5;
int select = choice % 5;
for (int i = 0; i < page; i++)
{
keyQueue.push(interface_key::SECONDSCROLL_PAGEDOWN);
}
keyQueue.push((interface_key::interface_key)(interface_key::OPTION1 + select));
return CR_OK;
}
command_result MiscMoveCommand(DFHack::color_ostream &stream, const MiscMoveParams *in)
{
if (!df::global::ui_advmode->menu == ui_advmode_menu::Default)
return CR_OK;
auto type = in->type();
switch (type)
{
case AdventureControl::SET_CLIMB:
getCurViewscreen()->feed_key(interface_key::A_HOLD);
break;
case AdventureControl::SET_STAND:
getCurViewscreen()->feed_key(interface_key::A_STANCE);
break;
case AdventureControl::SET_CANCEL:
getCurViewscreen()->feed_key(interface_key::LEAVESCREEN);
break;
default:
break;
}
return CR_OK;
}

@ -0,0 +1,16 @@
#ifndef ADVENTURE_CONTROL_H
#define ADVENTURE_CONTROL_H
#include "RemoteClient.h"
#include "AdventureControl.pb.h"
DFHack::command_result MoveCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in);
DFHack::command_result JumpCommand(DFHack::color_ostream &stream, const AdventureControl::MoveCommandParams *in);
DFHack::command_result MenuQuery(DFHack::color_ostream &stream, const dfproto::EmptyMessage *in, AdventureControl::MenuContents *out);
DFHack::command_result MovementSelectCommand(DFHack::color_ostream &stream, const dfproto::IntMessage *in);
DFHack::command_result MiscMoveCommand(DFHack::color_ostream &stream, const AdventureControl::MiscMoveParams *in);
void KeyUpdate();
#endif // !ADVENTURE_CONTROL_H

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

@ -0,0 +1,551 @@
#include "item_reader.h"
#include "Core.h"
#include "VersionInfo.h"
#include "df/art_image.h"
#include "df/art_image_chunk.h"
#include "df/art_image_element.h"
#include "df/art_image_element_creaturest.h"
#include "df/art_image_element_itemst.h"
#include "df/art_image_element_plantst.h"
#include "df/art_image_element_shapest.h"
#include "df/art_image_element_treest.h"
#include "df/art_image_element_type.h"
#include "df/art_image_property.h"
#include "df/art_image_property_intransitive_verbst.h"
#include "df/art_image_property_transitive_verbst.h"
#include "df/art_image_ref.h"
#include "df/descriptor_shape.h"
#include "df/item_type.h"
#include "df/item_constructed.h"
#include "df/item_gemst.h"
#include "df/item_smallgemst.h"
#include "df/item_statuest.h"
#include "df/item_threadst.h"
#include "df/item_toolst.h"
#include "df/itemimprovement.h"
#include "df/itemimprovement_art_imagest.h"
#include "df/itemimprovement_bandsst.h"
#include "df/itemimprovement_coveredst.h"
#include "df/itemimprovement_illustrationst.h"
#include "df/itemimprovement_itemspecificst.h"
#include "df/itemimprovement_sewn_imagest.h"
#include "df/itemimprovement_specific_type.h"
#include "df/itemimprovement_threadst.h"
#include "df/itemdef.h"
#include "df/map_block.h"
#include "df/vehicle.h"
#include "df/world.h"
#include "modules/Items.h"
#include "modules/MapCache.h"
#include "modules/Materials.h"
#include "MiscUtils.h"
using namespace DFHack;
using namespace df::enums;
using namespace RemoteFortressReader;
using namespace std;
using namespace df::global;
void CopyImage(const df::art_image * image, ArtImage * netImage)
{
auto id = netImage->mutable_id();
id->set_mat_type(image->id);
id->set_mat_index(image->subid);
for (int i = 0; i < image->elements.size(); i++)
{
auto element = image->elements[i];
auto netElement = netImage->add_elements();
auto elementType = element->getType();
netElement->set_type((ArtImageElementType)elementType);
netElement->set_count(element->count);
switch (elementType)
{
case df::enums::art_image_element_type::CREATURE:
{
VIRTUAL_CAST_VAR(creature, df::art_image_element_creaturest, element);
auto cret = netElement->mutable_creature_item();
cret->set_mat_type(creature->race);
cret->set_mat_index(creature->caste);
break;
}
case df::enums::art_image_element_type::PLANT:
{
VIRTUAL_CAST_VAR(plant, df::art_image_element_plantst, element);
netElement->set_id(plant->plant_id);
break;
}
case df::enums::art_image_element_type::TREE:
{
VIRTUAL_CAST_VAR(tree, df::art_image_element_treest, element);
netElement->set_id(tree->plant_id);
break;
}
case df::enums::art_image_element_type::SHAPE:
{
VIRTUAL_CAST_VAR(shape, df::art_image_element_shapest, element);
netElement->set_id(shape->shape_id);
break;
}
case df::enums::art_image_element_type::ITEM:
{
VIRTUAL_CAST_VAR(item, df::art_image_element_itemst, element);
auto it = netElement->mutable_creature_item();
it->set_mat_type(item->item_type);
it->set_mat_index(item->item_subtype);
netElement->set_id(item->item_id);
auto mat = netElement->mutable_material();
mat->set_mat_type(item->mat_type);
mat->set_mat_index(item->mat_index);
break;
}
default:
break;
}
}
for (int i = 0; i < image->properties.size(); i++)
{
auto dfProperty = image->properties[i];
auto netProperty = netImage->add_properties();
auto propertyType = dfProperty->getType();
netProperty->set_type((ArtImagePropertyType)propertyType);
switch (propertyType)
{
case df::enums::art_image_property_type::transitive_verb:
{
VIRTUAL_CAST_VAR(transitive, df::art_image_property_transitive_verbst, dfProperty);
netProperty->set_subject(transitive->subject);
netProperty->set_object(transitive->object);
netProperty->set_verb((ArtImageVerb)transitive->verb);
break;
}
case df::enums::art_image_property_type::intransitive_verb:
{
VIRTUAL_CAST_VAR(intransitive, df::art_image_property_intransitive_verbst, dfProperty);
netProperty->set_subject(intransitive->subject);
netProperty->set_verb((ArtImageVerb)intransitive->verb);
break;
}
default:
break;
}
}
}
void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem)
{
NetItem->set_id(DfItem->id);
NetItem->set_flags1(DfItem->flags.whole);
NetItem->set_flags2(DfItem->flags2.whole);
auto pos = NetItem->mutable_pos();
pos->set_x(DfItem->pos.x);
pos->set_y(DfItem->pos.y);
pos->set_z(DfItem->pos.z);
auto mat = NetItem->mutable_material();
mat->set_mat_index(DfItem->getMaterialIndex());
mat->set_mat_type(DfItem->getMaterial());
auto type = NetItem->mutable_type();
type->set_mat_type(DfItem->getType());
type->set_mat_index(DfItem->getSubtype());
bool isProjectile = false;
item_type::item_type itemType = DfItem->getType();
switch (itemType)
{
case df::enums::item_type::NONE:
break;
case df::enums::item_type::BAR:
break;
case df::enums::item_type::SMALLGEM:
{
VIRTUAL_CAST_VAR(smallgem_item, df::item_smallgemst, DfItem);
type->set_mat_index(smallgem_item->shape);
break;
}
case df::enums::item_type::BLOCKS:
break;
case df::enums::item_type::ROUGH:
break;
case df::enums::item_type::BOULDER:
break;
case df::enums::item_type::WOOD:
break;
case df::enums::item_type::DOOR:
break;
case df::enums::item_type::FLOODGATE:
break;
case df::enums::item_type::BED:
break;
case df::enums::item_type::CHAIR:
break;
case df::enums::item_type::CHAIN:
break;
case df::enums::item_type::FLASK:
break;
case df::enums::item_type::GOBLET:
break;
case df::enums::item_type::INSTRUMENT:
break;
case df::enums::item_type::TOY:
break;
case df::enums::item_type::WINDOW:
break;
case df::enums::item_type::CAGE:
break;
case df::enums::item_type::BARREL:
break;
case df::enums::item_type::BUCKET:
break;
case df::enums::item_type::ANIMALTRAP:
break;
case df::enums::item_type::TABLE:
break;
case df::enums::item_type::COFFIN:
break;
case df::enums::item_type::STATUE:
{
VIRTUAL_CAST_VAR(statue, df::item_statuest, DfItem);
df::art_image_chunk * chunk = NULL;
GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast<GET_ART_IMAGE_CHUNK>(Core::getInstance().vinfo->getAddress("get_art_image_chunk"));
if (GetArtImageChunk)
{
chunk = GetArtImageChunk(&(world->art_image_chunks), statue->image.id);
}
else
{
for (int i = 0; i < world->art_image_chunks.size(); i++)
{
if (world->art_image_chunks[i]->id == statue->image.id)
chunk = world->art_image_chunks[i];
}
}
if (chunk)
{
CopyImage(chunk->images[statue->image.subid], NetItem->mutable_image());
}
break;
}
case df::enums::item_type::CORPSE:
break;
case df::enums::item_type::WEAPON:
break;
case df::enums::item_type::ARMOR:
break;
case df::enums::item_type::SHOES:
break;
case df::enums::item_type::SHIELD:
break;
case df::enums::item_type::HELM:
break;
case df::enums::item_type::GLOVES:
break;
case df::enums::item_type::BOX:
type->set_mat_index(DfItem->isBag());
break;
case df::enums::item_type::BIN:
break;
case df::enums::item_type::ARMORSTAND:
break;
case df::enums::item_type::WEAPONRACK:
break;
case df::enums::item_type::CABINET:
break;
case df::enums::item_type::FIGURINE:
break;
case df::enums::item_type::AMULET:
break;
case df::enums::item_type::SCEPTER:
break;
case df::enums::item_type::AMMO:
break;
case df::enums::item_type::CROWN:
break;
case df::enums::item_type::RING:
break;
case df::enums::item_type::EARRING:
break;
case df::enums::item_type::BRACELET:
break;
case df::enums::item_type::GEM:
{
VIRTUAL_CAST_VAR(gem_item, df::item_gemst, DfItem);
type->set_mat_index(gem_item->shape);
break;
}
case df::enums::item_type::ANVIL:
break;
case df::enums::item_type::CORPSEPIECE:
break;
case df::enums::item_type::REMAINS:
break;
case df::enums::item_type::MEAT:
break;
case df::enums::item_type::FISH:
break;
case df::enums::item_type::FISH_RAW:
break;
case df::enums::item_type::VERMIN:
break;
case df::enums::item_type::PET:
break;
case df::enums::item_type::SEEDS:
break;
case df::enums::item_type::PLANT:
break;
case df::enums::item_type::SKIN_TANNED:
break;
case df::enums::item_type::PLANT_GROWTH:
break;
case df::enums::item_type::THREAD:
{
VIRTUAL_CAST_VAR(thread, df::item_threadst, DfItem);
if (thread && thread->dye_mat_type >= 0)
{
DFHack::MaterialInfo info;
if (info.decode(thread->dye_mat_type, thread->dye_mat_index))
ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye());
}
break;
}
case df::enums::item_type::CLOTH:
break;
case df::enums::item_type::TOTEM:
break;
case df::enums::item_type::PANTS:
break;
case df::enums::item_type::BACKPACK:
break;
case df::enums::item_type::QUIVER:
break;
case df::enums::item_type::CATAPULTPARTS:
break;
case df::enums::item_type::BALLISTAPARTS:
break;
case df::enums::item_type::SIEGEAMMO:
break;
case df::enums::item_type::BALLISTAARROWHEAD:
break;
case df::enums::item_type::TRAPPARTS:
break;
case df::enums::item_type::TRAPCOMP:
break;
case df::enums::item_type::DRINK:
break;
case df::enums::item_type::POWDER_MISC:
break;
case df::enums::item_type::CHEESE:
break;
case df::enums::item_type::FOOD:
break;
case df::enums::item_type::LIQUID_MISC:
break;
case df::enums::item_type::COIN:
break;
case df::enums::item_type::GLOB:
break;
case df::enums::item_type::ROCK:
break;
case df::enums::item_type::PIPE_SECTION:
break;
case df::enums::item_type::HATCH_COVER:
break;
case df::enums::item_type::GRATE:
break;
case df::enums::item_type::QUERN:
break;
case df::enums::item_type::MILLSTONE:
break;
case df::enums::item_type::SPLINT:
break;
case df::enums::item_type::CRUTCH:
break;
case df::enums::item_type::TRACTION_BENCH:
break;
case df::enums::item_type::ORTHOPEDIC_CAST:
break;
case df::enums::item_type::TOOL:
if (!isProjectile)
{
VIRTUAL_CAST_VAR(tool, df::item_toolst, DfItem);
if (tool)
{
auto vehicle = binsearch_in_vector(world->vehicles.active, tool->vehicle_id);
if (vehicle)
{
NetItem->set_subpos_x(vehicle->offset_x / 100000.0);
NetItem->set_subpos_y(vehicle->offset_y / 100000.0);
NetItem->set_subpos_z(vehicle->offset_z / 140000.0);
}
}
}
break;
case df::enums::item_type::SLAB:
break;
case df::enums::item_type::EGG:
break;
case df::enums::item_type::BOOK:
break;
case df::enums::item_type::SHEET:
break;
case df::enums::item_type::BRANCH:
break;
default:
break;
}
VIRTUAL_CAST_VAR(actual_item, df::item_actual, DfItem);
if (actual_item)
{
NetItem->set_stack_size(actual_item->stack_size);
}
VIRTUAL_CAST_VAR(constructed_item, df::item_constructed, DfItem);
if (constructed_item)
{
for (int i = 0; i < constructed_item->improvements.size(); i++)
{
auto improvement = constructed_item->improvements[i];
if (!improvement)
continue;
improvement_type::improvement_type impType = improvement->getType();
auto netImp = NetItem->add_improvements();
netImp->set_type((ImprovementType)impType);
auto mat = netImp->mutable_material();
mat->set_mat_type(improvement->mat_type);
mat->set_mat_index(improvement->mat_index);
switch (impType)
{
case df::enums::improvement_type::ART_IMAGE:
{
VIRTUAL_CAST_VAR(artImage, df::itemimprovement_art_imagest, improvement);
CopyImage(artImage->getImage(DfItem), netImp->mutable_image());
break;
}
case df::enums::improvement_type::COVERED:
{
VIRTUAL_CAST_VAR(covered, df::itemimprovement_coveredst, improvement);
netImp->set_shape(covered->shape);
break;
}
case df::enums::improvement_type::RINGS_HANGING:
break;
case df::enums::improvement_type::BANDS:
{
VIRTUAL_CAST_VAR(bands, df::itemimprovement_bandsst, improvement);
netImp->set_shape(bands->shape);
break;
}
case df::enums::improvement_type::ITEMSPECIFIC:
{
VIRTUAL_CAST_VAR(specific, df::itemimprovement_itemspecificst, improvement);
netImp->set_specific_type(specific->type);
break;
}
case df::enums::improvement_type::THREAD:
{
VIRTUAL_CAST_VAR(improvement_thread, df::itemimprovement_threadst, improvement);
if (improvement_thread->dye.mat_type >= 0)
{
DFHack::MaterialInfo info;
if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index))
continue;
ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye());
}
break;
}
case df::enums::improvement_type::CLOTH:
break;
case df::enums::improvement_type::SEWN_IMAGE:
break;
case df::enums::improvement_type::PAGES:
break;
case df::enums::improvement_type::ILLUSTRATION:
break;
case df::enums::improvement_type::INSTRUMENT_PIECE:
break;
case df::enums::improvement_type::WRITING:
break;
default:
break;
}
}
}
NetItem->set_volume(DfItem->getVolume());
}
DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out)
{
if (!Core::getInstance().isWorldLoaded()) {
//out->set_available(false);
return CR_OK;
}
FOR_ENUM_ITEMS(item_type, it)
{
MaterialDefinition *mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(-1);
mat_def->set_id(ENUM_KEY_STR(item_type, it));
switch (it)
{
case df::enums::item_type::GEM:
case df::enums::item_type::SMALLGEM:
{
for (int i = 0; i < world->raws.language.shapes.size(); i++)
{
auto shape = world->raws.language.shapes[i];
if (shape->gems_use.whole == 0)
continue;
mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(i);
mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + shape->id);
}
break;
}
case df::enums::item_type::BOX:
{
mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(0);
mat_def->set_id("BOX_CHEST");
mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(1);
mat_def->set_id("BOX_BAG");
break;
}
}
int subtypes = Items::getSubtypeCount(it);
if (subtypes >= 0)
{
for (int i = 0; i < subtypes; i++)
{
mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(i);
df::itemdef * item = Items::getSubtypeDef(it, i);
mat_def->set_id(ENUM_KEY_STR(item_type, it) + "/" + item->id);
}
}
}
return CR_OK;
}

@ -0,0 +1,32 @@
#ifndef ITEM_READER_H
#define ITEM_READER_H
#include <stdint.h>
#include "RemoteClient.h"
#include "RemoteFortressReader.pb.h"
#include "DataDefs.h"
namespace df
{
struct item;
struct map_block;
struct art_image;
struct art_image_chunk;
struct world;
}
namespace MapExtras
{
class MapCache;
}
DFHack::command_result GetItemList(DFHack::color_ostream &stream, const DFHack::EmptyMessage *in, RemoteFortressReader::MaterialList *out);
void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem);
void ConvertDFColorDescriptor(int16_t index, RemoteFortressReader::ColorDefinition * out);
typedef df::art_image_chunk * (*GET_ART_IMAGE_CHUNK)(std::vector<df::art_image_chunk* > *, int);
void CopyImage(const df::art_image * image, RemoteFortressReader::ArtImage * netImage);
#endif // !ITEM_READER_H

@ -1,5 +1,5 @@
#include "df_version_int.h"
#define RFR_VERSION "0.18.0"
#define RFR_VERSION "0.19.0"
#include <cstdio>
#include <time.h>
@ -17,6 +17,7 @@
#include "SDL_events.h"
#include "SDL_keyboard.h"
#include "TileTypes.h"
#include "VersionInfo.h"
#if DF_VERSION_INT > 34011
#include "DFHackVersion.h"
#endif
@ -37,6 +38,7 @@
#include "df/block_square_event_item_spatterst.h"
#include "df/block_square_event_grassst.h"
#endif
#include "df/art_image_element_shapest.h"
#include "df/block_square_event_material_spatterst.h"
#include "df/body_appearance_modifier.h"
#include "df/body_part_layer_raw.h"
@ -51,20 +53,14 @@
#include "df/creature_raw.h"
#include "df/creature_raw.h"
#include "df/descriptor_color.h"
#include "df/descriptor_color.h"
#include "df/descriptor_pattern.h"
#include "df/descriptor_pattern.h"
#include "df/descriptor_shape.h"
#include "df/dfhack_material_category.h"
#include "df/enabler.h"
#include "df/engraving.h"
#include "df/graphic.h"
#include "df/historical_figure.h"
#include "df/item.h"
#include "df/item_constructed.h"
#include "df/item_threadst.h"
#include "df/itemimprovement.h"
#include "df/itemimprovement_threadst.h"
#include "df/itemdef.h"
#include "df/job.h"
#include "df/job_type.h"
#include "df/job_item.h"
@ -77,6 +73,7 @@
#include "df/plant.h"
#include "df/plant_raw_flags.h"
#include "df/projectile.h"
#include "df/proj_itemst.h"
#include "df/proj_unitst.h"
#include "df/region_map_entry.h"
#include "df/report.h"
@ -92,6 +89,7 @@
#include "df/unit.h"
#include "df/unit_inventory_item.h"
#include "df/viewscreen_choose_start_sitest.h"
#include "df/vehicle.h"
#include "df/world.h"
#include "df/world_data.h"
#include "df/world_geo_biome.h"
@ -113,7 +111,9 @@
#include "df/unit_relationship_type.h"
#include "adventure_control.h"
#include "building_reader.h"
#include "item_reader.h"
using namespace DFHack;
using namespace df::enums;
@ -129,6 +129,7 @@ REQUIRE_GLOBAL(world);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(gamemode);
REQUIRE_GLOBAL(ui_advmode);
#endif
// Here go all the command declarations...
@ -145,7 +146,6 @@ static command_result GetUnitListInside(color_ostream &stream, const BlockReques
static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in, ViewInfo *out);
static command_result GetMapInfo(color_ostream &stream, const EmptyMessage *in, MapInfo *out);
static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in);
static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out);
static command_result GetWorldMap(color_ostream &stream, const EmptyMessage *in, WorldMap *out);
static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *in, WorldMap *out);
static command_result GetWorldMapCenter(color_ostream &stream, const EmptyMessage *in, WorldMap *out);
@ -161,8 +161,8 @@ 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);
void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem);
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);
void CopyBlock(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos);
@ -179,6 +179,31 @@ const char* growth_locations[] = {
};
#define GROWTH_LOCATIONS_SIZE 8
#include "df/art_image.h"
#include "df/art_image_chunk.h"
#include "df/art_image_ref.h"
command_result loadArtImageChunk(color_ostream &out, vector <string> & parameters)
{
if (parameters.size() != 1)
return CR_WRONG_USAGE;
if (!Core::getInstance().isWorldLoaded())
{
out.printerr("No world loaded\n");
return CR_FAILURE;
}
GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast<GET_ART_IMAGE_CHUNK>(Core::getInstance().vinfo->getAddress("get_art_image_chunk"));
if (GetArtImageChunk)
{
int index = atoi(parameters[0].c_str());
auto chunk = GetArtImageChunk(&(world->art_image_chunks), index);
out.print("Loaded chunk id: %d\n", chunk->id);
}
return CR_OK;
}
command_result dump_bp_mods(color_ostream &out, vector <string> & parameters)
{
remove("bp_appearance_mods.csv");
@ -233,20 +258,28 @@ command_result RemoteFortressReader_version(color_ostream &out, vector<string> &
return CR_OK;
}
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", "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:
// 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",
"Gets an art image chunk by index, loading from disk if necessary",
loadArtImageChunk, false,
"Usage: load_art_image_chunk N, where N is the id of the chunk to get."));
enableUpdates = true;
return CR_OK;
}
@ -286,6 +319,12 @@ DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
svc->addFunction("GetPauseState", GetPauseState, SF_ALLOW_REMOTE);
svc->addFunction("GetVersionInfo", GetVersionInfo, SF_ALLOW_REMOTE);
svc->addFunction("GetReports", GetReports, SF_ALLOW_REMOTE);
svc->addFunction("MoveCommand", MoveCommand, SF_ALLOW_REMOTE);
svc->addFunction("JumpCommand", JumpCommand, SF_ALLOW_REMOTE);
svc->addFunction("MenuQuery", MenuQuery, SF_ALLOW_REMOTE);
svc->addFunction("MovementSelectCommand", MovementSelectCommand, SF_ALLOW_REMOTE);
svc->addFunction("MiscMoveCommand", MiscMoveCommand, SF_ALLOW_REMOTE);
svc->addFunction("GetLanguage", GetLanguage, SF_ALLOW_REMOTE);
return svc;
}
@ -298,6 +337,14 @@ DFhackCExport command_result plugin_shutdown(color_ostream &out)
return CR_OK;
}
DFhackCExport command_result plugin_onupdate(color_ostream &out)
{
if (!enableUpdates)
return CR_OK;
KeyUpdate();
return CR_OK;
}
uint16_t fletcher16(uint8_t const *data, size_t bytes)
{
uint16_t sum1 = 0xff, sum2 = 0xff;
@ -719,7 +766,7 @@ bool IsspatterChanged(DFCoord pos)
hash ^= fletcher16((uint8_t*)item, sizeof(df::block_square_event_item_spatterst));
}
#endif
if (spatterHashes[pos] != hash)
if (spatterHashes[pos] != hash)
{
spatterHashes[pos] = hash;
return true;
@ -756,6 +803,21 @@ bool areItemsChanged(vector<int> * items)
return result;
}
map<int, int> engravingHashes;
bool isEngravingNew(int index)
{
if (engravingHashes[index])
return false;
engravingHashes[index] = true;
return true;
}
void engravingIsNotNew(int index)
{
engravingHashes[index] = false;
}
static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *in)
{
hashes.clear();
@ -763,6 +825,7 @@ static command_result ResetMapHashes(color_ostream &stream, const EmptyMessage *
buildingHashes.clear();
spatterHashes.clear();
itemHashes.clear();
engravingHashes.clear();
return CR_OK;
}
@ -882,47 +945,6 @@ static command_result GetMaterialList(color_ostream &stream, const EmptyMessage
return CR_OK;
}
static command_result GetItemList(color_ostream &stream, const EmptyMessage *in, MaterialList *out)
{
if (!Core::getInstance().isWorldLoaded()) {
//out->set_available(false);
return CR_OK;
}
FOR_ENUM_ITEMS(item_type, it)
{
MaterialDefinition *mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(-1);
mat_def->set_id(ENUM_KEY_STR(item_type, it));
if (it == item_type::BOX)
{
mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(0);
mat_def->set_id("BOX_CHEST");
mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(1);
mat_def->set_id("BOX_BAG");
}
int subtypes = Items::getSubtypeCount(it);
if (subtypes >= 0)
{
for (int i = 0; i < subtypes; i++)
{
mat_def = out->add_material_list();
mat_def->mutable_mat_pair()->set_mat_type((int)it);
mat_def->mutable_mat_pair()->set_mat_index(i);
df::itemdef * item = Items::getSubtypeDef(it, i);
mat_def->set_id(item->id);
}
}
}
return CR_OK;
}
static command_result GetGrowthList(color_ostream &stream, const EmptyMessage *in, MaterialList *out)
{
if (!Core::getInstance().isWorldLoaded()) {
@ -1196,6 +1218,70 @@ void CopyDesignation(df::map_block * DfBlock, RemoteFortressReader::MapBlock * N
#endif
}
void CopyProjectiles(RemoteFortressReader::MapBlock * NetBlock)
{
for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next)
{
STRICT_VIRTUAL_CAST_VAR(projectile, df::proj_itemst, proj->item);
if (projectile == NULL)
continue;
auto NetItem = NetBlock->add_items();
CopyItem(NetItem, projectile->item);
NetItem->set_projectile(true);
if (projectile->flags.bits.parabolic)
{
NetItem->set_subpos_x(projectile->pos_x / 100000.0);
NetItem->set_subpos_y(projectile->pos_y / 100000.0);
NetItem->set_subpos_z(projectile->pos_z / 140000.0);
NetItem->set_velocity_x(projectile->speed_x / 100000.0);
NetItem->set_velocity_y(projectile->speed_y / 100000.0);
NetItem->set_velocity_z(projectile->speed_z / 140000.0);
}
else
{
DFCoord diff = projectile->target_pos - projectile->origin_pos;
float max_dist = max(max(abs(diff.x), abs(diff.y)), abs(diff.z));
NetItem->set_subpos_x(projectile->origin_pos.x + (diff.x / max_dist * projectile->distance_flown) - projectile->cur_pos.x);
NetItem->set_subpos_y(projectile->origin_pos.y + (diff.y / max_dist * projectile->distance_flown) - projectile->cur_pos.y);
NetItem->set_subpos_z(projectile->origin_pos.z + (diff.z / max_dist * projectile->distance_flown) - projectile->cur_pos.z);
NetItem->set_velocity_x(diff.x / max_dist);
NetItem->set_velocity_y(diff.y / max_dist);
NetItem->set_velocity_z(diff.z / max_dist);
}
}
for (int i = 0; i < world->vehicles.active.size(); i++)
{
bool isProj = false;
auto vehicle = world->vehicles.active[i];
for (auto proj = world->proj_list.next; proj != NULL; proj = proj->next)
{
STRICT_VIRTUAL_CAST_VAR(projectile, df::proj_itemst, proj->item);
if (!projectile)
continue;
if (projectile->item->id == vehicle->item_id)
{
isProj = true;
break;
}
}
if (isProj)
continue;
auto item = Items::findItemByID(vehicle->item_id);
if (!item)
continue;
auto NetItem = NetBlock->add_items();
CopyItem(NetItem, item);
NetItem->set_subpos_x(vehicle->offset_x / 100000.0);
NetItem->set_subpos_y(vehicle->offset_y / 100000.0);
NetItem->set_subpos_z(vehicle->offset_z / 140000.0);
NetItem->set_velocity_x(vehicle->speed_x / 100000.0);
NetItem->set_velocity_y(vehicle->speed_y / 100000.0);
NetItem->set_velocity_z(vehicle->speed_z / 140000.0);
}
}
void CopyBuildings(DFCoord min, DFCoord max, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC)
{
@ -1294,57 +1380,6 @@ void Copyspatters(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetB
}
}
void CopyItem(RemoteFortressReader::Item * NetItem, df::item * DfItem)
{
NetItem->set_id(DfItem->id);
NetItem->set_flags1(DfItem->flags.whole);
NetItem->set_flags2(DfItem->flags2.whole);
auto pos = NetItem->mutable_pos();
pos->set_x(DfItem->pos.x);
pos->set_y(DfItem->pos.y);
pos->set_z(DfItem->pos.z);
auto mat = NetItem->mutable_material();
mat->set_mat_index(DfItem->getMaterialIndex());
mat->set_mat_type(DfItem->getMaterial());
auto type = NetItem->mutable_type();
type->set_mat_type(DfItem->getType());
type->set_mat_index(DfItem->getSubtype());
if (DfItem->getType() == item_type::BOX)
{
type->set_mat_index(DfItem->isBag());
}
auto constructed_item = virtual_cast<df::item_constructed>(DfItem);
if(constructed_item)
{
for (int i = 0; i < constructed_item->improvements.size(); i++)
{
auto improvement = constructed_item->improvements[i];
if (!improvement || improvement->getType() != improvement_type::THREAD)
continue;
auto improvement_thread = virtual_cast<df::itemimprovement_threadst>(improvement);
if (!improvement_thread || improvement_thread->dye.mat_type < 0)
continue;
DFHack::MaterialInfo info;
if (!info.decode(improvement_thread->dye.mat_type, improvement_thread->dye.mat_index))
continue;
ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye());
}
}
else if (DfItem->getType() == item_type::THREAD)
{
auto thread = virtual_cast<df::item_threadst>(DfItem);
if (thread && thread->dye_mat_type >= 0)
{
DFHack::MaterialInfo info;
if (info.decode(thread->dye_mat_type, thread->dye_mat_index))
ConvertDFColorDescriptor(info.material->powder_dye, NetItem->mutable_dye());
}
}
}
void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBlock, MapExtras::MapCache * MC, DFCoord pos)
{
NetBlock->set_map_x(DfBlock->map_pos.x);
@ -1354,13 +1389,13 @@ void CopyItems(df::map_block * DfBlock, RemoteFortressReader::MapBlock * NetBloc
{
int id = DfBlock->items[i];
auto item = df::item::find(id);
if(item)
if (item)
CopyItem(NetBlock->add_items(), item);
}
}
static command_result GetBlockList(color_ostream &stream, const BlockRequest *in, BlockList *out)
{
int x, y, z;
@ -1376,7 +1411,7 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in
if (in->has_blocks_needed())
blocks_needed = in->blocks_needed();
else
blocks_needed = NUMBER_OF_POINTS*(in->max_z() - in->min_z());
blocks_needed = NUMBER_OF_POINTS * (in->max_z() - in->min_z());
int blocks_sent = 0;
int min_x = in->min_x();
int min_y = in->min_y();
@ -1384,8 +1419,8 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in
int max_y = in->max_y();
int min_z = in->min_z();
int max_z = in->max_z();
bool sentBuildings = false; //Always send all the buildings needed on the first block, and none on the rest.
//stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z());
bool firstBlock = true; //Always send all the buildings needed on the first block, and none on the rest.
//stream.print("Got request for blocks from (%d, %d, %d) to (%d, %d, %d).\n", in->min_x(), in->min_y(), in->min_z(), in->max_x(), in->max_y(), in->max_z());
for (int zz = max_z - 1; zz >= min_z; zz--)
{
// (di, dj) is a vector - direction in which we move right now
@ -1417,16 +1452,14 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in
|| block->occupancy[xxx][yyy].bits.building > 0)
nonAir++;
}
if (nonAir > 0 || !sentBuildings)
if (nonAir > 0 || firstBlock)
{
bool tileChanged = IsTiletypeChanged(pos);
bool desChanged = IsDesignationChanged(pos);
bool spatterChanged = IsspatterChanged(pos);
bool buildingChanged = !sentBuildings;
bool itemsChanged = areItemsChanged(&block->items);
//bool bldChanged = IsBuildingChanged(pos);
bool itemsChanged = block->items.size() > 0;
RemoteFortressReader::MapBlock *net_block;
if (tileChanged || desChanged || spatterChanged || buildingChanged || itemsChanged)
if (tileChanged || desChanged || spatterChanged || firstBlock || itemsChanged)
net_block = out->add_map_blocks();
if (tileChanged)
{
@ -1435,10 +1468,11 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in
}
if (desChanged)
CopyDesignation(block, net_block, &MC, pos);
if (buildingChanged)
if (firstBlock)
{
CopyBuildings(DFCoord(min_x * 16, min_y * 16, min_z), DFCoord(max_x * 16, max_y * 16, max_z), net_block, &MC);
sentBuildings = true;
CopyProjectiles(net_block);
firstBlock = false;
}
if (spatterChanged)
Copyspatters(block, net_block, &MC, pos);
@ -1471,6 +1505,54 @@ static command_result GetBlockList(color_ostream &stream, const BlockRequest *in
}
}
}
for (int i = 0; i < world->engravings.size(); i++)
{
auto engraving = world->engravings[i];
if (engraving->pos.x < (min_x * 16) || engraving->pos.x >(max_x * 16))
continue;
if (engraving->pos.y < (min_y * 16) || engraving->pos.x >(max_y * 16))
continue;
if (engraving->pos.z < (min_z * 16) || engraving->pos.x >(max_z * 16))
continue;
if (!isEngravingNew(i))
continue;
df::art_image_chunk * chunk = NULL;
GET_ART_IMAGE_CHUNK GetArtImageChunk = reinterpret_cast<GET_ART_IMAGE_CHUNK>(Core::getInstance().vinfo->getAddress("get_art_image_chunk"));
if (GetArtImageChunk)
{
chunk = GetArtImageChunk(&(world->art_image_chunks), engraving->art_id);
}
else
{
for (int i = 0; i < world->art_image_chunks.size(); i++)
{
if (world->art_image_chunks[i]->id == engraving->art_id)
chunk = world->art_image_chunks[i];
}
}
if (!chunk)
{
engravingIsNotNew(i);
continue;
}
auto netEngraving = out->add_engravings();
ConvertDFCoord(engraving->pos, netEngraving->mutable_pos());
netEngraving->set_quality(engraving->quality);
netEngraving->set_tile(engraving->tile);
CopyImage(chunk->images[engraving->art_subid], netEngraving->mutable_image());
netEngraving->set_floor(engraving->flags.bits.floor);
netEngraving->set_west(engraving->flags.bits.west);
netEngraving->set_east(engraving->flags.bits.east);
netEngraving->set_north(engraving->flags.bits.north);
netEngraving->set_south(engraving->flags.bits.south);
netEngraving->set_hidden(engraving->flags.bits.hidden);
netEngraving->set_northwest(engraving->flags.bits.northwest);
netEngraving->set_northeast(engraving->flags.bits.northeast);
netEngraving->set_southwest(engraving->flags.bits.southwest);
netEngraving->set_southeast(engraving->flags.bits.southeast);
}
MC.trash();
return CR_OK;
}
@ -1705,7 +1787,7 @@ static command_result GetViewInfo(color_ostream &stream, const EmptyMessage *in,
out->set_cursor_pos_y(cy);
out->set_cursor_pos_z(cz);
if(gamemode && *gamemode == GameMode::ADVENTURE)
if (gamemode && *gamemode == GameMode::ADVENTURE)
out->set_follow_unit_id(world->units.active[0]->id);
else
out->set_follow_unit_id(ui->follow_unit);
@ -1752,24 +1834,24 @@ DFCoord GetMapCenter()
}
else
#endif
if (Maps::IsValid())
{
int x, y, z;
Maps::getPosition(x,y,z);
output = DFCoord(x, y, z);
}
#if DF_VERSION_INT > 34011
else
for (int i = 0; i < df::global::world->armies.all.size(); i++)
if (Maps::IsValid())
{
df::army * thisArmy = df::global::world->armies.all[i];
if (thisArmy->flags.is_set(df::enums::army_flags::player))
int x, y, z;
Maps::getPosition(x, y, z);
output = DFCoord(x, y, z);
}
#if DF_VERSION_INT > 34011
else
for (int i = 0; i < df::global::world->armies.all.size(); i++)
{
output.x = (thisArmy->pos.x / 3) - 1;
output.y = (thisArmy->pos.y / 3) - 1;
output.z = thisArmy->pos.z;
df::army * thisArmy = df::global::world->armies.all[i];
if (thisArmy->flags.is_set(df::enums::army_flags::player))
{
output.x = (thisArmy->pos.x / 3) - 1;
output.y = (thisArmy->pos.y / 3) - 1;
output.z = thisArmy->pos.z;
}
}
}
#endif
return output;
}
@ -1988,7 +2070,7 @@ static command_result GetWorldMapNew(color_ostream &stream, const EmptyMessage *
break;
}
#else
out->set_world_poles(WorldPoles::NO_POLES);
out->set_world_poles(WorldPoles::NO_POLES);
#endif
for (int yy = 0; yy < height; yy++)
for (int xx = 0; xx < width; xx++)
@ -2127,7 +2209,7 @@ static void CopyLocalMap(df::world_data * worldData, df::world_region_details* w
break;
}
#else
out->set_world_poles(WorldPoles::NO_POLES);
out->set_world_poles(WorldPoles::NO_POLES);
#endif
df::world_region_details * south = NULL;
@ -2460,7 +2542,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe
if (in != nullptr)
{
list_start = in->list_start();
if(in->list_end() < list_end)
if (in->list_end() < list_end)
list_end = in->list_end();
}
@ -2642,7 +2724,7 @@ static command_result GetPartialCreatureRaws(color_ostream &stream, const ListRe
CopyMat(send_tissue->mutable_material(), orig_tissue->mat_type, orig_tissue->mat_index);
}
}
}
return CR_OK;
}
@ -2855,9 +2937,22 @@ static command_result GetVersionInfo(color_ostream & stream, const EmptyMessage
return CR_OK;
}
int lastSentReportID = -1;
static command_result GetReports(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Status * out)
{
for (int i = 0; i < world->status.reports.size(); i++)
//First find the last report we sent, so it doesn't get resent.
int lastSentIndex = -1;
for (int i = world->status.reports.size() - 1; i >= 0; i--)
{
auto local_rep = world->status.reports[i];
if (local_rep->id <= lastSentReportID)
{
lastSentIndex = i;
break;
}
}
for (int i = lastSentIndex + 1; i < world->status.reports.size(); i++)
{
auto local_rep = world->status.reports[i];
if (!local_rep)
@ -2875,6 +2970,22 @@ static command_result GetReports(color_ostream & stream, const EmptyMessage * in
send_rep->set_id(local_rep->id);
send_rep->set_year(local_rep->year);
send_rep->set_time(local_rep->time);
lastSentReportID = local_rep->id;
}
return CR_OK;
}
static command_result GetLanguage(color_ostream & stream, const EmptyMessage * in, RemoteFortressReader::Language * out)
{
if (!world)
return CR_FAILURE;
for (int i = 0; i < world->raws.language.shapes.size(); i++)
{
auto shape = world->raws.language.shapes[i];
auto netShape = out->add_shapes();
netShape->set_id(shape->id);
netShape->set_tile(shape->tile);
}
return CR_OK;
}

@ -88,8 +88,8 @@ rect2d getMapViewport()
int menu_x1=area_x2-MENU_WIDTH-1;
int view_rb=w-1;
int area_pos=*df::global::ui_area_map_width;
int menu_pos=*df::global::ui_menu_width;
int area_pos=(*df::global::ui_menu_width)[1];
int menu_pos=(*df::global::ui_menu_width)[0];
if(area_pos<3)
{
view_rb=area_x2;

@ -36,8 +36,6 @@ REQUIRE_GLOBAL(enabler);
REQUIRE_GLOBAL(gametype);
REQUIRE_GLOBAL(gps);
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_area_map_width);
REQUIRE_GLOBAL(ui_menu_width);
REQUIRE_GLOBAL(window_x);
REQUIRE_GLOBAL(window_y);
REQUIRE_GLOBAL(window_z);

@ -15,15 +15,15 @@ module DFHack
x, y, z = x.x, x.y, x.z if x.respond_to?(:x)
# compute screen 'map' size (tiles)
menuwidth = ui_menu_width
menuwidth = ui_menu_width[0]
# ui_menu_width shows only the 'tab' status
menuwidth = 1 if menuwidth == 2 and ui_area_map_width == 2 and cursor.x != -30000
menuwidth = 1 if menuwidth == 2 and ui_menu_width[1] == 2 and cursor.x != -30000
menuwidth = 2 if menuwidth == 3 and cursor.x != -30000
w_w = gps.dimx - 2
w_h = gps.dimy - 2
case menuwidth
when 1; w_w -= 55
when 2; w_w -= (ui_area_map_width == 2 ? 24 : 31)
when 2; w_w -= (ui_menu_width[1] == 2 ? 24 : 31)
end
# center view

@ -94,11 +94,11 @@
#include "tweaks/hide-priority.h"
#include "tweaks/hotkey-clear.h"
#include "tweaks/import-priority-category.h"
#include "tweaks/kitchen-keys.h"
#include "tweaks/kitchen-prefs-color.h"
#include "tweaks/kitchen-prefs-empty.h"
#include "tweaks/max-wheelbarrow.h"
#include "tweaks/military-assign.h"
#include "tweaks/pausing-fps-counter.h"
#include "tweaks/nestbox-color.h"
#include "tweaks/shift-8-scroll.h"
#include "tweaks/stable-cursor.h"
@ -117,13 +117,12 @@ DFHACK_PLUGIN_IS_ENABLED(is_enabled);
REQUIRE_GLOBAL(enabler);
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(ui_area_map_width);
REQUIRE_GLOBAL(ui_build_selector);
REQUIRE_GLOBAL(ui_building_in_assign);
REQUIRE_GLOBAL(ui_building_in_resize);
REQUIRE_GLOBAL(ui_building_item_cursor);
REQUIRE_GLOBAL(ui_menu_width);
REQUIRE_GLOBAL(ui_look_cursor);
REQUIRE_GLOBAL(ui_menu_width);
REQUIRE_GLOBAL(ui_sidebar_menus);
REQUIRE_GLOBAL(ui_unit_view_mode);
REQUIRE_GLOBAL(ui_workshop_in_add);
@ -219,8 +218,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" tweak import-priority-category [disable]\n"
" When meeting with a liaison, makes Shift+Left/Right arrow adjust\n"
" the priority of an entire category of imports.\n"
" tweak kitchen-keys [disable]\n"
" Fixes DF kitchen meal keybindings (bug 614)\n"
" tweak kitchen-prefs-color [disable]\n"
" Changes color of enabled items to green in kitchen preferences\n"
" tweak kitchen-prefs-empty [disable]\n"
@ -236,6 +233,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Preserve list order and cursor position when assigning to squad,\n"
" i.e. stop the rightmost list of the Positions page of the military\n"
" screen from constantly jumping to the top.\n"
" tweak pausing-fps-counter [disable]\n"
" Replace fortress mode FPS counter with one that stops counting \n"
" when paused.\n"
" tweak shift-8-scroll [disable]\n"
" Gives Shift+8 (or *) priority when scrolling menus, instead of \n"
" scrolling the map\n"
@ -294,9 +294,6 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_HOOK("import-priority-category", takerequest_hook, feed);
TWEAK_HOOK("import-priority-category", takerequest_hook, render);
TWEAK_HOOK("kitchen-keys", kitchen_keys_hook, feed);
TWEAK_HOOK("kitchen-keys", kitchen_keys_hook, render);
TWEAK_HOOK("kitchen-prefs-color", kitchen_prefs_color_hook, render);
TWEAK_HOOK("kitchen-prefs-empty", kitchen_prefs_empty_hook, render);
@ -310,6 +307,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_HOOK("nestbox-color", nestbox_color_hook, drawBuilding);
TWEAK_HOOK("pausing-fps-counter", dwarfmode_pausing_fps_counter_hook, render);
TWEAK_HOOK("pausing-fps-counter", title_pausing_fps_counter_hook, render);
TWEAK_HOOK("shift-8-scroll", shift_8_scroll_hook, feed);
TWEAK_HOOK("stable-cursor", stable_cursor_hook, feed);

@ -8,8 +8,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst {
struct T_order_mat_data {
std::vector<std::string*> list_entries;
std::vector<int16_t> list_unk1;
std::vector<int32_t> list_unk2;
std::vector<int16_t> mat_types;
std::vector<int32_t> mat_indices;
std::vector<int16_t> list_unk3;
std::vector<int16_t> list_visible;
};
@ -24,8 +24,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst {
}
auto data = new T_order_mat_data;
data->list_entries = scr->list_entries;
data->list_unk1 = scr->list_unk1;
data->list_unk2 = scr->list_unk2;
data->mat_types = scr->mat_types;
data->mat_indices = scr->mat_indices;
data->list_unk3 = scr->list_unk3;
data->list_visible = scr->list_visible;
order_mat_data[scr] = data;
@ -37,8 +37,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst {
{
T_order_mat_data *data = order_mat_data[scr];
scr->list_entries = data->list_entries;
scr->list_unk1 = data->list_unk1;
scr->list_unk2 = data->list_unk2;
scr->mat_types = data->mat_types;
scr->mat_indices = data->mat_indices;
scr->list_unk3 = data->list_unk3;
scr->list_visible = data->list_visible;
delete data;
@ -55,8 +55,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst {
// keep the first item ("no material") around, because attempts to delete it
// result in it still being displayed first, regardless of list_entries[0]
list_entries.resize(1);
list_unk1.resize(1);
list_unk2.resize(1);
mat_types.resize(1);
mat_indices.resize(1);
list_unk3.resize(1);
list_visible.resize(1);
// skip "no material" here
@ -71,8 +71,8 @@ struct condition_material_hook : df::viewscreen_workquota_conditionst {
if (s->find(filter) != std::string::npos)
{
list_entries.push_back(data->list_entries[i]);
list_unk1.push_back(data->list_unk1[i]);
list_unk2.push_back(data->list_unk2[i]);
mat_types.push_back(data->mat_types[i]);
mat_indices.push_back(data->mat_indices[i]);
list_unk3.push_back(data->list_unk3[i]);
// this should be small enough to fit in an int16_t
list_visible.push_back(int16_t(list_entries.size() - 1));

@ -1,68 +0,0 @@
using namespace DFHack;
using namespace df::enums;
using df::global::ui_sidebar_menus;
using df::global::ui_workshop_in_add;
static df::interface_key kitchen_bindings[] = {
df::interface_key::HOTKEY_KITCHEN_COOK_2,
df::interface_key::HOTKEY_KITCHEN_COOK_3,
df::interface_key::HOTKEY_KITCHEN_COOK_4,
// DF uses CUSTOM_R for this reaction in the raws, so this key is recognized
// by this tweak but not displayed
df::interface_key::HOTKEY_KITCHEN_RENDER_FAT
};
struct kitchen_keys_hook : df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
void draw_binding (int row, df::interface_key key)
{
std::string label = Screen::getKeyDisplay(key);
int x = Gui::getDwarfmodeViewDims().menu_x2 - 2 - label.size();
int y = row + 4;
OutputString(COLOR_GREY, x, y, "(");
OutputString(COLOR_LIGHTRED, x, y, label);
OutputString(COLOR_GREY, x, y, ")");
}
bool kitchen_in_add()
{
if (!*ui_workshop_in_add)
return false;
df::building_workshopst *ws = virtual_cast<df::building_workshopst>(world->selected_building);
if (!ws)
return false;
if (ws->type != workshop_type::Kitchen)
return false;
return true;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
if (kitchen_in_add())
{
for (int i = 0; i < 4; i++)
{
if (input->count(kitchen_bindings[i]))
{
ui_sidebar_menus->workshop_job.cursor = i;
input->clear();
input->insert(df::interface_key::SELECT);
}
}
}
INTERPOSE_NEXT(feed)(input);
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (kitchen_in_add())
for (int i = 0; i < 3; i++)
draw_binding(i, kitchen_bindings[i]);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(kitchen_keys_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(kitchen_keys_hook, render);

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

@ -108,7 +108,6 @@ REQUIRE_GLOBAL(ui_building_assign_items);
REQUIRE_GLOBAL(ui_building_in_assign);
REQUIRE_GLOBAL(ui_menu_width);
REQUIRE_GLOBAL(ui_area_map_width);
using namespace DFHack::Gui;
@ -3926,8 +3925,8 @@ public:
return;
int left_margin = gps->dimx - 30;
int8_t a = *ui_menu_width;
int8_t b = *ui_area_map_width;
int8_t a = (*ui_menu_width)[0];
int8_t b = (*ui_menu_width)[1];
if ((a == 1 && b > 1) || (a == 2 && b == 2))
left_margin -= 24;

@ -1 +1 @@
Subproject commit 3baa24fec93461218b5b658de94884ebff0a0b23
Subproject commit 8d079a59122d9ba72ce9c0f7687402a343d09bc7

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

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

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

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

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