diff --git a/.travis.yml b/.travis.yml index ad17693a9..ede4b132b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/CMakeLists.txt b/CMakeLists.txt index 3c7838949..c7eb1f234 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}") diff --git a/NEWS.rst b/NEWS.rst index e55ca92c5..ee685f8f9 100644 --- a/NEWS.rst +++ b/NEWS.rst @@ -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 ================= diff --git a/dfhack.init-example b/dfhack.init-example index 3542956ce..82210a2a6 100644 --- a/dfhack.init-example +++ b/dfhack.init-example @@ -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 diff --git a/docs/Authors.rst b/docs/Authors.rst index a693646bf..aecfc519a 100644 --- a/docs/Authors.rst +++ b/docs/Authors.rst @@ -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 ======================= ======================= =========================== diff --git a/docs/Core.rst b/docs/Core.rst index 15adcc533..b15305fa9 100644 --- a/docs/Core.rst +++ b/docs/Core.rst @@ -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. diff --git a/docs/Lua API.rst b/docs/Lua API.rst index 66c2a5a07..d79924cc1 100644 --- a/docs/Lua API.rst +++ b/docs/Lua API.rst @@ -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 ============================== diff --git a/docs/NEWS-dev.rst b/docs/NEWS-dev.rst index 2be774672..834282608 100644 --- a/docs/NEWS-dev.rst +++ b/docs/NEWS-dev.rst @@ -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 ==================== diff --git a/docs/Plugins.rst b/docs/Plugins.rst index 0ed65c4a3..da1231184 100644 --- a/docs/Plugins.rst +++ b/docs/Plugins.rst @@ -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 diff --git a/docs/Scripts-removed.rst b/docs/Scripts-removed.rst new file mode 100644 index 000000000..4490110ba --- /dev/null +++ b/docs/Scripts-removed.rst @@ -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. diff --git a/index.rst b/index.rst index 1f647907b..5cba99e2c 100644 --- a/index.rst +++ b/index.rst @@ -50,6 +50,7 @@ Other Contents /docs/Authors /LICENSE /NEWS + /docs/Scripts-removed For Developers ============== diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 40c122de9..4d4a4e9f1 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -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) diff --git a/library/Console-windows.cpp b/library/Console-windows.cpp index 28d98309b..3caacd924 100644 --- a/library/Console-windows.cpp +++ b/library/Console-windows.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); diff --git a/library/Core.cpp b/library/Core.cpp index 62d605fe6..58b3f85f5 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -81,6 +81,10 @@ using namespace DFHack; #include "SDL_events.h" +#ifdef LINUX_BUILD +#include +#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 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 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 diff --git a/library/DataDefs.cpp b/library/DataDefs.cpp index c9586fcc0..ab50eb942 100644 --- a/library/DataDefs.cpp +++ b/library/DataDefs.cpp @@ -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) diff --git a/library/DataStaticsFields.cpp b/library/DataStaticsFields.cpp index d6a622a9e..d6f0414bb 100644 --- a/library/DataStaticsFields.cpp +++ b/library/DataStaticsFields.cpp @@ -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 diff --git a/library/Error.cpp b/library/Error.cpp new file mode 100644 index 000000000..ba1482918 --- /dev/null +++ b/library/Error.cpp @@ -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)) +{} diff --git a/library/Hooks-linux.cpp b/library/Hooks-linux.cpp index b0bf5a781..8291bf899 100644 --- a/library/Hooks-linux.cpp +++ b/library/Hooks-linux.cpp @@ -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) { diff --git a/library/LuaApi.cpp b/library/LuaApi.cpp index 39b4b41cb..0bf19e93e 100644 --- a/library/LuaApi.cpp +++ b/library/LuaApi.cpp @@ -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); } diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 6e3c5d5cf..f744f3eba 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -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()); diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index a6bb4b412..e350cb9ec 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -25,7 +25,6 @@ distribution. #include "Internal.h" #include "Export.h" #include "MiscUtils.h" -#include "Error.h" #ifndef LINUX_BUILD #include @@ -41,14 +40,6 @@ distribution. #include #include -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); diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index 763049a04..d0bdee00f 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -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)) diff --git a/library/VersionInfoFactory.cpp b/library/VersionInfoFactory.cpp index cdfdbf774..7033fd598 100644 --- a/library/VersionInfoFactory.cpp +++ b/library/VersionInfoFactory.cpp @@ -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)); diff --git a/library/include/DataFuncs.h b/library/include/DataFuncs.h index bf8e6b244..6541ae900 100644 --- a/library/include/DataFuncs.h +++ b/library/include/DataFuncs.h @@ -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 diff --git a/library/include/Error.h b/library/include/Error.h index 6b4781028..d3d208b1f 100644 --- a/library/include/Error.h +++ b/library/include/Error.h @@ -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(); - } }; } } diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 0048f5bd9..36625719b 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -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(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); diff --git a/library/include/modules/Maps.h b/library/include/modules/Maps.h index fb5fc9860..6b6e62f0a 100644 --- a/library/include/modules/Maps.h +++ b/library/include/modules/Maps.h @@ -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* grass = 0, std::vector* constructions = 0, std::vector* spoors = 0, - std::vector* items = 0 + std::vector* items = 0, + std::vector* priorities = 0 ); /// remove a block event from the block by address diff --git a/library/lua/binpatch.lua b/library/lua/binpatch.lua index d8e95b29f..0cae97e5b 100644 --- a/library/lua/binpatch.lua +++ b/library/lua/binpatch.lua @@ -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 diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 340b6ce64..6acbe27e8 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -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 diff --git a/library/lua/gui/dwarfmode.lua b/library/lua/gui/dwarfmode.lua index 3764c4df7..e98ba2080 100644 --- a/library/lua/gui/dwarfmode.lua +++ b/library/lua/gui/dwarfmode.lua @@ -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 diff --git a/library/lua/json.lua b/library/lua/json.lua index ac7aa6327..2b4b3a66a 100644 --- a/library/lua/json.lua +++ b/library/lua/json.lua @@ -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() diff --git a/library/modules/Gui.cpp b/library/modules/Gui.cpp index 7de9a5e11..e3cebd45e 100644 --- a/library/modules/Gui.cpp +++ b/library/modules/Gui.cpp @@ -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; } diff --git a/library/modules/MapCache.cpp b/library/modules/MapCache.cpp index fb4aef935..ffb9fc769 100644 --- a/library/modules/MapCache.cpp +++ b/library/modules/MapCache.cpp @@ -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 events; + Maps::SortBlockEvents(block, 0, 0, 0, 0, 0, 0, 0, &events); + if (events.empty()) + { + if (!write) + return NULL; + + auto event = df::allocate(); + 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; diff --git a/library/modules/Maps.cpp b/library/modules/Maps.cpp index 2b2311a7c..20255dbe3 100644 --- a/library/modules/Maps.cpp +++ b/library/modules/Maps.cpp @@ -402,7 +402,8 @@ bool Maps::SortBlockEvents(df::map_block *block, vector *grasses, vector *constructions, vector *spoors, - vector *items) + vector *items, + vector *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; diff --git a/library/modules/Screen.cpp b/library/modules/Screen.cpp index 06faf610f..96f385d8d 100644 --- a/library/modules/Screen.cpp +++ b/library/modules/Screen.cpp @@ -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 > *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((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 "?"; } diff --git a/onLoad.init-example b/onLoad.init-example index 08dd536a7..1d32f07c9 100644 --- a/onLoad.init-example +++ b/onLoad.init-example @@ -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 ] diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index b6e05b749..e85e0aaf4 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -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) diff --git a/plugins/autochop.cpp b/plugins/autochop.cpp index dbc79af42..a2a863540 100644 --- a/plugins/autochop.cpp +++ b/plugins/autochop.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; diff --git a/plugins/autohauler.cpp b/plugins/autohauler.cpp index 7a3f8a3d8..62914c784 100644 --- a/plugins/autohauler.cpp +++ b/plugins/autohauler.cpp @@ -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. diff --git a/plugins/autolabor.cpp b/plugins/autolabor.cpp index 95863ffb5..a894a2891 100644 --- a/plugins/autolabor.cpp +++ b/plugins/autolabor.cpp @@ -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 diff --git a/plugins/createitem.cpp b/plugins/createitem.cpp index e65d19793..1f2e8a5c4 100644 --- a/plugins/createitem.cpp +++ b/plugins/createitem.cpp @@ -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) diff --git a/plugins/devel/memview.cpp b/plugins/devel/memview.cpp index b93319a07..d84a3b578 100644 --- a/plugins/devel/memview.cpp +++ b/plugins/devel/memview.cpp @@ -53,7 +53,14 @@ size_t convert(const std::string& p,bool ishex=false) conv>>ret; return ret; } -bool isAddr(uintptr_t *trg,vector & ranges) +bool isAddr(void *trg, vector &ranges) +{ + for (auto &r : ranges) + if (r.isInRange(trg)) + return true; + return false; +} +bool isAddrAt(uintptr_t *trg, vector &ranges) { if(trg[0]%4==0) for(size_t i=0;i & 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; diff --git a/plugins/dig.cpp b/plugins/dig.cpp index 5404f2f2d..148da5482 100644 --- a/plugins/dig.cpp +++ b/plugins/dig.cpp @@ -1,16 +1,23 @@ +#include +#include +#include +#include +#include +#include + #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 -#include -#include -#include -#include + +#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 & parameters); command_result digtype (color_ostream &out, vector & parameters); DFHACK_PLUGIN("dig"); +REQUIRE_GLOBAL(ui_sidebar_menus); REQUIRE_GLOBAL(world); DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &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 ¶meters) +{ + 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 ¶meters) +{ + return string("-p") + int_to_string(parse_priority(out, parameters) / 1000); +} + command_result digcircle (color_ostream &out, vector & parameters) { static bool filled = false; @@ -215,6 +263,8 @@ command_result digcircle (color_ostream &out, vector & 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 & 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 & 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 & 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 & 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 & 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 & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector 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 & 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 & 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 & 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 & 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 & parameters) // HOTKEY COMMAND: CORE ALREADY SUSPENDED vector 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 & 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 & 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 & 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 & 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 & parameters) command_result digtype (color_ostream &out, vector & 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 & parameters) df::tile_designation designation = mCache->designationAt(current); designation.bits.dig = baseDes.bits.dig; - mCache->setDesignationAt(current, designation); + mCache->setDesignationAt(current, designation,priority); } } } diff --git a/plugins/dwarfmonitor.cpp b/plugins/dwarfmonitor.cpp index 2ea811f38..bf7c02efc 100644 --- a/plugins/dwarfmonitor.cpp +++ b/plugins/dwarfmonitor.cpp @@ -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 +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(pref.poetic_form_id); + break; + + case (T_type::LikeMusicalForm): + label += "Music :" + getFormName(pref.musical_form_id); + break; + + case (T_type::LikeDanceForm): + label += "Dance :" + getFormName(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 *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(); + 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 *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(); diff --git a/plugins/embark-assistant/CMakeLists.txt b/plugins/embark-assistant/CMakeLists.txt new file mode 100644 index 000000000..e57561ec1 --- /dev/null +++ b/plugins/embark-assistant/CMakeLists.txt @@ -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}) diff --git a/plugins/embark-assistant/biome_type.cpp b/plugins/embark-assistant/biome_type.cpp new file mode 100644 index 000000000..01bc0044a --- /dev/null +++ b/plugins/embark-assistant/biome_type.cpp @@ -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 + +#include "DataDefs.h" +#include +#include +#include +#include + +#include "biome_type.h" + +/***************************************************************************** +Local functions forward declaration +*****************************************************************************/ +std::pair 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 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 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(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair 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(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair 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(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair 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(is_possible_tropical_area_by_latitude, + is_tropical_area_by_latitude + ); +} + +//----------------------------------------------------------------------------// +// Utility function +// +//----------------------------------------------------------------------------// +std::pair 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(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 + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/biome_type.h b/plugins/embark-assistant/biome_type.h new file mode 100644 index 000000000..9ea562749 --- /dev/null +++ b/plugins/embark-assistant/biome_type.h @@ -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); diff --git a/plugins/embark-assistant/defs.h b/plugins/embark-assistant/defs.h new file mode 100644 index 000000000..79ad5dd5b --- /dev/null +++ b/plugins/embark-assistant/defs.h @@ -0,0 +1,256 @@ +#pragma once + +#include +#include +#include + +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 metals; + std::vector economics; + std::vector minerals; + }; + + typedef std::array, 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 metals; + std::vector economics; + std::vector 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 possible_metals; + std::vector possible_economics; + std::vector possible_minerals; + }; + + typedef std::vector 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 metals; + std::vector economics; + std::vector minerals; + // Could add savagery, evilness, and biomes, but DF provides those easily. + }; + + typedef std::vector site_lists; + + typedef std::vector> 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> 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(evil_savagery_ranges::High) + 1]; + evil_savagery_values evilness[static_cast(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); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/embark-assistant.cpp b/plugins/embark-assistant/embark-assistant.cpp new file mode 100644 index 000000000..7e0ea21cc --- /dev/null +++ b/plugins/embark-assistant/embark-assistant.cpp @@ -0,0 +1,296 @@ +#include "Core.h" +#include +#include +#include + +#include +#include +#include + +#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(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(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 & parameters); + +//======================================================================================= + +DFhackCExport command_result plugin_init (color_ostream &out, std::vector &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 & parameters) +{ + if (!parameters.empty()) + return CR_WRONG_USAGE; + + CoreSuspender suspend; + + auto screen = Gui::getViewscreenByType(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; +} diff --git a/plugins/embark-assistant/embark-assistant.h b/plugins/embark-assistant/embark-assistant.h new file mode 100644 index 000000000..6f70f09be --- /dev/null +++ b/plugins/embark-assistant/embark-assistant.h @@ -0,0 +1 @@ +#pragma once diff --git a/plugins/embark-assistant/finder_ui.cpp b/plugins/embark-assistant/finder_ui.cpp new file mode 100644 index 000000000..feea89199 --- /dev/null +++ b/plugins/embark-assistant/finder_ui.cpp @@ -0,0 +1,1145 @@ +#include "Core.h" +#include + +#include + +#include "Types.h" + +#include "df/biome_type.h" +#include "df/inorganic_raw.h" +#include "df/material_flags.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_region_type.h" +#include "df/world_raws.h" + +#include "embark-assistant.h" +#include "finder_ui.h" +#include "screen.h" + +using df::global::world; + +namespace embark_assist { + namespace finder_ui { + + enum class fields : int8_t { + x_dim, + y_dim, + savagery_calm, + savagery_medium, + savagery_savage, + good, + neutral, + evil, + aquifer, + min_river, + max_river, + waterfall, + flat, + clay, + sand, + flux, + soil_min, + soil_min_everywhere, + soil_max, + evil_weather, + reanimation, + thralling, + biome_count_min, + biome_count_max, + region_type_1, + region_type_2, + region_type_3, + biome_1, + biome_2, + biome_3, + metal_1, + metal_2, + metal_3, + economic_1, + economic_2, + economic_3, + mineral_1, + mineral_2, + mineral_3 + }; + fields first_fields = fields::x_dim; + fields last_fields = fields::mineral_3; + + struct display_map_elements { + std::string text; + int16_t key; + }; + + typedef std::vector display_maps; + typedef std::vector name_lists; + typedef std::list< display_map_elements> sort_lists; + + struct ui_lists { + uint16_t current_display_value; // Not the value itself, but a reference to its index. + int16_t current_value; // The integer representation of the value (if an enum). + uint16_t current_index; // What's selected + uint16_t focus; // The value under the (possibly inactive) cursor + display_maps list; // The strings to be displayed together with keys + // to allow location of the actual elements (e.g. a raws.inorganics mat_index + // or underlying enum value). + }; + + typedef std::vector uis; + + const DFHack::Screen::Pen active_pen(' ', COLOR_YELLOW); + const DFHack::Screen::Pen passive_pen(' ', COLOR_DARKGREY); + const DFHack::Screen::Pen normal_pen(' ', COLOR_GREY); + const DFHack::Screen::Pen white_pen(' ', COLOR_WHITE); + const DFHack::Screen::Pen lr_pen(' ', COLOR_LIGHTRED); + + //========================================================================================================== + + struct states { + embark_assist::defs::find_callbacks find_callback; + uis ui; + display_maps finder_list; // Don't need the element key, but it's easier to use the same type. + uint16_t finder_list_focus; + bool finder_list_active; + uint16_t max_inorganic; + }; + + static states *state = 0; + + //========================================================================================================== + + bool compare(const display_map_elements& first, const display_map_elements& second) { + uint16_t i = 0; + while (i < first.text.length() && i < second.text.length()) { + if (first.text[i] < second.text[i]) { + return true; + } + else if (first.text[i] > second.text[i]) { + return false; + } + ++i; + } + return first.text.length() < second.text.length(); + } + + //========================================================================================================== + + void append(sort_lists *sort_list, display_map_elements element) { + sort_lists::iterator iterator; + for (iterator = sort_list->begin(); iterator != sort_list->end(); ++iterator) { + if (iterator->key == element.key) { + return; + } + } + sort_list->push_back(element); + } + + //========================================================================================================== + + void ui_setup(embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + if (!embark_assist::finder_ui::state) { + state = new(states); + state->finder_list_focus = 0; + state->finder_list_active = true; + state->find_callback = find_callback; + state->max_inorganic = max_inorganic; + } + + fields i = first_fields; + ui_lists *element; + + while (true) { + element = new ui_lists; + element->current_display_value = 0; + element->current_index = 0; + element->focus = 0; + + switch (i) { + case fields::x_dim: + for (int16_t k = 1; k < 16; k++) { + element->list.push_back({ std::to_string(k), k }); + } + + break; + + case fields::y_dim: + for (int16_t k = 1; k < 16; k++) { + element->list.push_back({ std::to_string(k), k }); + } + + break; + + case fields::savagery_calm: + case fields::savagery_medium: + case fields::savagery_savage: + case fields::good: + case fields::neutral: + case fields::evil: + { + embark_assist::defs::evil_savagery_values k = embark_assist::defs::evil_savagery_values::NA; + while (true) { + switch (k) { + case embark_assist::defs::evil_savagery_values::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::evil_savagery_values::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::evil_savagery_values::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::aquifer: + { + embark_assist::defs::aquifer_ranges k = embark_assist::defs::aquifer_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::aquifer_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Partial: + element->list.push_back({ "Partial", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + element->list.push_back({ "Not All", static_cast(k) }); + break; + + case embark_assist::defs::aquifer_ranges::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::aquifer_ranges::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::min_river: + case fields::max_river: + { + embark_assist::defs::river_ranges k = embark_assist::defs::river_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::river_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::None: + element->list.push_back({ "None", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Brook: + element->list.push_back({ "Brook", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Stream: + element->list.push_back({ "Stream", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Minor: + element->list.push_back({ "Minor", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Medium: + element->list.push_back({ "Medium", static_cast(k) }); + break; + + case embark_assist::defs::river_ranges::Major: + element->list.push_back({ "Major", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::river_ranges::Major) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::waterfall: + case fields::flat: + case fields::evil_weather: + case fields::reanimation: + case fields::thralling: + { + embark_assist::defs::yes_no_ranges k = embark_assist::defs::yes_no_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::yes_no_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::yes_no_ranges::Yes: + element->list.push_back({ "Yes", static_cast(k) }); + break; + + case embark_assist::defs::yes_no_ranges::No: + element->list.push_back({ "No", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::yes_no_ranges::No) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::soil_min_everywhere: + { + embark_assist::defs::all_present_ranges k = embark_assist::defs::all_present_ranges::All; + while (true) { + switch (k) { + case embark_assist::defs::all_present_ranges::All: + element->list.push_back({ "All", static_cast(k) }); + break; + + case embark_assist::defs::all_present_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::all_present_ranges::Present) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::clay: + case fields::sand: + case fields::flux: + { + embark_assist::defs::present_absent_ranges k = embark_assist::defs::present_absent_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::present_absent_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::present_absent_ranges::Present: + element->list.push_back({ "Present", static_cast(k) }); + break; + + case embark_assist::defs::present_absent_ranges::Absent: + element->list.push_back({ "Absent", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::present_absent_ranges::Absent) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::soil_min: + case fields::soil_max: + { + embark_assist::defs::soil_ranges k = embark_assist::defs::soil_ranges::NA; + while (true) { + switch (k) { + case embark_assist::defs::soil_ranges::NA: + element->list.push_back({ "N/A", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::None: + element->list.push_back({ "None", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Very_Shallow: + element->list.push_back({ "Very Shallow", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Shallow: + element->list.push_back({ "Shallow", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Deep: + element->list.push_back({ "Deep", static_cast(k) }); + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + element->list.push_back({ "Very Deep", static_cast(k) }); + break; + } + + if (k == embark_assist::defs::soil_ranges::Very_Deep) { + break; + } + + k = static_cast (static_cast(k) + 1); + } + } + + break; + + case fields::biome_count_min: + case fields::biome_count_max: + for (int16_t k = 0; k < 10; k++) { + if (k == 0) { + element->list.push_back({ "N/A", -1 }); + } + else { + element->list.push_back({ std::to_string(k), k }); + } + } + + break; + + case fields::region_type_1: + case fields::region_type_2: + case fields::region_type_3: + { + std::list name; + std::list::iterator iterator; + + FOR_ENUM_ITEMS(world_region_type, iter) { + name.push_back({ ENUM_KEY_STR(world_region_type, iter), static_cast(iter) }); + } + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::biome_1: + case fields::biome_2: + case fields::biome_3: + { + sort_lists name; + sort_lists::iterator iterator; + + FOR_ENUM_ITEMS(biome_type, iter) { + std::string s = ENUM_KEY_STR(biome_type, iter); + + if (s.substr(0, 4) != "POOL" && + s.substr(0, 5) != "RIVER" && + s.substr(0, 3) != "SUB") { + name.push_back({ s, static_cast(iter) }); + } + } + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::metal_1: + case fields::metal_2: + case fields::metal_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (uint16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + for (uint16_t l = 0; l < world->raws.inorganics[k]->metal_ore.mat_index.size(); l++) { + append(&name, { world->raws.inorganics[world->raws.inorganics[k]->metal_ore.mat_index[l]]->id, + world->raws.inorganics[k]->metal_ore.mat_index[l] }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::economic_1: + case fields::economic_2: + case fields::economic_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (int16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + if (world->raws.inorganics[k]->economic_uses.size() != 0 && + !world->raws.inorganics[k]->material.flags.is_set(df::material_flags::IS_METAL)) { + append(&name, { world->raws.inorganics[k]->id, k }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + + case fields::mineral_1: + case fields::mineral_2: + case fields::mineral_3: + { + sort_lists name; + sort_lists::iterator iterator; + + for (int16_t k = 0; k < embark_assist::finder_ui::state->max_inorganic; k++) { + if (world->raws.inorganics[k]->environment.location.size() != 0 || + world->raws.inorganics[k]->environment_spec.mat_index.size() != 0 || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::SEDIMENTARY) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::IGNEOUS_EXTRUSIVE) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::IGNEOUS_INTRUSIVE) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::METAMORPHIC) || + world->raws.inorganics[k]->flags.is_set(df::inorganic_flags::SOIL)) { + append(&name, { world->raws.inorganics[k]->id, k }); + } + } + + name.sort(compare); + + element->list.push_back({ "N/A", -1 }); + + for (iterator = name.begin(); iterator != name.end(); ++iterator) { + element->list.push_back({ iterator->text, iterator->key }); + } + + name.clear(); + } + break; + } + + element->current_value = element->list[0].key; + + state->ui.push_back(element); + + switch (i) { + case fields::x_dim: + state->finder_list.push_back({ "X Dimension", static_cast(i) }); + break; + + case fields::y_dim: + state->finder_list.push_back({ "Y Dimension", static_cast(i) }); + break; + + case fields::savagery_calm: + state->finder_list.push_back({ "Low Savagery", static_cast(i) }); + break; + + case fields::savagery_medium: + state->finder_list.push_back({ "Medium Savagery", static_cast(i) }); + break; + + case fields::savagery_savage: + state->finder_list.push_back({ "High Savagery", static_cast(i) }); + break; + + case fields::good: + state->finder_list.push_back({ "Good", static_cast(i) }); + break; + + case fields::neutral: + state->finder_list.push_back({ "Neutral", static_cast(i) }); + break; + + case fields::evil: + state->finder_list.push_back({ "Evil", static_cast(i) }); + break; + + case fields::aquifer: + state->finder_list.push_back({ "Aquifer", static_cast(i) }); + break; + + case fields::min_river: + state->finder_list.push_back({ "Min River", static_cast(i) }); + break; + + case fields::max_river: + state->finder_list.push_back({ "Max River", static_cast(i) }); + break; + + case fields::waterfall: + state->finder_list.push_back({ "Waterfall", static_cast(i) }); + break; + + case fields::flat: + state->finder_list.push_back({ "Flat", static_cast(i) }); + break; + + case fields::soil_min_everywhere: + state->finder_list.push_back({ "Min Soil Everywhere", static_cast(i) }); + break; + + case fields::evil_weather: + state->finder_list.push_back({ "Evil Weather", static_cast(i) }); + break; + + case fields::reanimation: + state->finder_list.push_back({ "Reanimation", static_cast(i) }); + break; + + case fields::thralling: + state->finder_list.push_back({ "Thralling", static_cast(i) }); + break; + + case fields::clay: + state->finder_list.push_back({ "Clay", static_cast(i) }); + break; + + case fields::sand: + state->finder_list.push_back({ "Sand", static_cast(i) }); + break; + + case fields::flux: + state->finder_list.push_back({ "Flux", static_cast(i) }); + break; + + case fields::soil_min: + state->finder_list.push_back({ "Min Soil", static_cast(i) }); + break; + + case fields::soil_max: + state->finder_list.push_back({ "Max Soil", static_cast(i) }); + break; + + case fields::biome_count_min: + state->finder_list.push_back({ "Min Biome Count", static_cast(i) }); + break; + + case fields::biome_count_max: + state->finder_list.push_back({ "Max Biome Count", static_cast(i) }); + break; + + case fields::region_type_1: + state->finder_list.push_back({ "Region Type 1", static_cast(i) }); + break; + + case fields::region_type_2: + state->finder_list.push_back({ "Region Type 2", static_cast(i) }); + break; + + case fields::region_type_3: + state->finder_list.push_back({ "Region Type 3", static_cast(i) }); + break; + + case fields::biome_1: + state->finder_list.push_back({ "Biome 1", static_cast(i) }); + break; + + case fields::biome_2: + state->finder_list.push_back({ "Biome 2", static_cast(i) }); + break; + + case fields::biome_3: + state->finder_list.push_back({ "Biome 3", static_cast(i) }); + break; + + case fields::metal_1: + state->finder_list.push_back({ "Metal 1", static_cast(i) }); + break; + + case fields::metal_2: + state->finder_list.push_back({ "Metal 2", static_cast(i) }); + break; + + case fields::metal_3: + state->finder_list.push_back({ "Metal 3", static_cast(i) }); + break; + + case fields::economic_1: + state->finder_list.push_back({ "Economic 1", static_cast(i) }); + break; + + case fields::economic_2: + state->finder_list.push_back({ "Economic 2", static_cast(i) }); + break; + + case fields::economic_3: + state->finder_list.push_back({ "Economic 3", static_cast(i) }); + break; + + case fields::mineral_1: + state->finder_list.push_back({ "Mineral 1", static_cast(i) }); + break; + + case fields::mineral_2: + state->finder_list.push_back({ "Mineral 2", static_cast(i) }); + break; + + case fields::mineral_3: + state->finder_list.push_back({ "Mineral 3", static_cast(i) }); + break; + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + // Default embark area size to that of the current selection. The "size" calculation is actually one + // off to compensate for the list starting with 1 at index 0. + // + auto screen = Gui::getViewscreenByType(0); + int16_t x = screen->location.region_pos.x; + int16_t y = screen->location.region_pos.y; + state->ui[static_cast(fields::x_dim)]->current_display_value = + Gui::getViewscreenByType(0)->location.embark_pos_max.x - + Gui::getViewscreenByType(0)->location.embark_pos_min.x; + state->ui[static_cast(fields::x_dim)]->current_index = + state->ui[static_cast(fields::x_dim)]->current_display_value; + state->ui[static_cast(fields::x_dim)]->current_value = + state->ui[static_cast(fields::x_dim)]->current_display_value + 1; + + state->ui[static_cast(fields::y_dim)]->current_display_value = + Gui::getViewscreenByType(0)->location.embark_pos_max.y - + Gui::getViewscreenByType(0)->location.embark_pos_min.y; + state->ui[static_cast(fields::y_dim)]->current_index = + state->ui[static_cast(fields::y_dim)]->current_display_value; + state->ui[static_cast(fields::y_dim)]->current_value = + state->ui[static_cast(fields::y_dim)]->current_display_value + 1; + } + + + //========================================================================================================== + + void find() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + embark_assist::defs::finders finder; + fields i = first_fields; + + while (true) { + switch (i) { + case fields::x_dim: + finder.x_dim = state->ui[static_cast(i)]->current_value; + break; + + case fields::y_dim: + finder.y_dim = state->ui[static_cast(i)]->current_value; + break; + + case fields::savagery_calm: + finder.savagery[0] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::savagery_medium: + finder.savagery[1] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + case fields::savagery_savage: + finder.savagery[2] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::good: + finder.evilness[0] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::neutral: + finder.evilness[1] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::evil: + finder.evilness[2] = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::aquifer: + finder.aquifer = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::min_river: + finder.min_river = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::max_river: + finder.max_river = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::waterfall: + finder.waterfall = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::flat: + finder.flat = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_min_everywhere: + finder.soil_min_everywhere = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::evil_weather: + finder.evil_weather = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::reanimation: + finder.reanimation = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::thralling: + finder.thralling = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::clay: + finder.clay = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::sand: + finder.sand = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::flux: + finder.flux = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_min: + finder.soil_min = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::soil_max: + finder.soil_max = + static_cast(state->ui[static_cast(i)]->current_value); + break; + + case fields::biome_count_min: + finder.biome_count_min = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_count_max: + finder.biome_count_max = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_1: + finder.region_type_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_2: + finder.region_type_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::region_type_3: + finder.region_type_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_1: + finder.biome_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_2: + finder.biome_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::biome_3: + finder.biome_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_1: + finder.metal_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_2: + finder.metal_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::metal_3: + finder.metal_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_1: + finder.economic_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_2: + finder.economic_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::economic_3: + finder.economic_3 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_1: + finder.mineral_1 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_2: + finder.mineral_2 = state->ui[static_cast(i)]->current_value; + break; + + case fields::mineral_3: + finder.mineral_3 = state->ui[static_cast(i)]->current_value; + break; + } + + if (i == last_fields) { + break; // done + } + + i = static_cast (static_cast(i) + 1); + } + + state->find_callback(finder); + } + + //========================================================================================================== + + class ViewscreenFindUi : public dfhack_viewscreen + { + public: + ViewscreenFindUi(); + + void feed(std::set *input); + + void render(); + + std::string getFocusString() { return "Finder UI"; } + + private: + }; + + //=============================================================================== + + void ViewscreenFindUi::feed(std::set *input) { + if (input->count(df::interface_key::LEAVESCREEN)) + { + input->clear(); + Screen::dismiss(this); + return; + + } else if (input->count(df::interface_key::CURSOR_LEFT) || + input->count(df::interface_key::CURSOR_RIGHT)) { + state->finder_list_active = !state->finder_list_active; + + } else if (input->count(df::interface_key::CURSOR_UP)) { + if (state->finder_list_active) { + if (state->finder_list_focus > 0) { + state->finder_list_focus--; + } + else { + state->finder_list_focus = static_cast(last_fields); + } + } + else { + if (state->ui[state->finder_list_focus]->current_index > 0) { + state->ui[state->finder_list_focus]->current_index--; + } else { + state->ui[state->finder_list_focus]->current_index = static_cast(state->ui[state->finder_list_focus]->list.size()) - 1; + } + } + + } else if (input->count(df::interface_key::CURSOR_DOWN)) { + if (state->finder_list_active) { + if (state->finder_list_focus < static_cast(last_fields)) { + state->finder_list_focus++; + } else { + state->finder_list_focus = 0; + } + } + else { + if (state->ui[state->finder_list_focus]->current_index < state->ui[state->finder_list_focus]->list.size() - 1) { + state->ui[state->finder_list_focus]->current_index++; + } else { + state->ui[state->finder_list_focus]->current_index = 0; + } + } + } else if (input->count(df::interface_key::SELECT)) { + if (!state->finder_list_active) { + state->ui[state->finder_list_focus]->current_display_value = state->ui[state->finder_list_focus]->current_index; + state->ui[state->finder_list_focus]->current_value = state->ui[state->finder_list_focus]->list[state->ui[state->finder_list_focus]->current_index].key; + state->finder_list_active = true; + } + } else if (input->count(df::interface_key::CUSTOM_F)) { + input->clear(); + Screen::dismiss(this); + find(); + return; + } + } + + //=============================================================================== + + void ViewscreenFindUi::render() { +// color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen_size = DFHack::Screen::getWindowSize(); + const int list_column = 53; + uint16_t offset = 0; + + Screen::clear(); + Screen::drawBorder("Embark Assistant Site Finder"); + + embark_assist::screen::paintString(lr_pen, 1, 1, "4/6"); + embark_assist::screen::paintString(white_pen, 4, 1, ":Shift list"); + embark_assist::screen::paintString(lr_pen, 16, 1, "8/2"); + embark_assist::screen::paintString(white_pen, 19, 1, ":Up/down"); + embark_assist::screen::paintString(lr_pen, 28, 1, "ENTER"); + embark_assist::screen::paintString(white_pen, 33, 1, ":Select item"); + embark_assist::screen::paintString(lr_pen, 46, 1, "f"); + embark_assist::screen::paintString(white_pen, 47, 1, ":Find"); + embark_assist::screen::paintString(lr_pen, 53, 1, "ESC"); + embark_assist::screen::paintString(white_pen, 56, 1, ":Abort"); + + for (uint16_t i = 0; i < state->finder_list.size(); i++) { + if (i == state->finder_list_focus) { + if (state->finder_list_active) { + embark_assist::screen::paintString(active_pen, 1, 2 + i, state->finder_list[i].text); + } + else { + embark_assist::screen::paintString(passive_pen, 1, 2 + i, state->finder_list[i].text); + } + + embark_assist::screen::paintString(active_pen, + 21, + 2 + i, + state->ui[i]->list[state->ui[i]->current_display_value].text); + } + else { + embark_assist::screen::paintString(normal_pen, 1, 2 + i, state->finder_list[i].text); + + embark_assist::screen::paintString(white_pen, + 21, + 2 + i, + state->ui[i]->list[state->ui[i]->current_display_value].text); + } + + } + + // Implement scrolling lists if they don't fit on the screen. + if (state->ui[state->finder_list_focus]->list.size() > screen_size.y - 3) { + offset = (screen_size.y - 3) / 2; + if (state->ui[state->finder_list_focus]->current_index < offset) { + offset = 0; + } + else { + offset = state->ui[state->finder_list_focus]->current_index - offset; + } + + if (state->ui[state->finder_list_focus]->list.size() - offset < screen_size.y - 3) { + offset = static_cast(state->ui[state->finder_list_focus]->list.size()) - (screen_size.y - 3); + } + } + + for (uint16_t i = 0; i < state->ui[state->finder_list_focus]->list.size(); i++) { + if (i == state->ui[state->finder_list_focus]->current_index) { + if (!state->finder_list_active) { // Negated expression to get the display lines in the same order as above. + embark_assist::screen::paintString(active_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + else { + embark_assist::screen::paintString(passive_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + } + else { + embark_assist::screen::paintString(normal_pen, list_column, 2 + i - offset, state->ui[state->finder_list_focus]->list[i].text); + } + } + + dfhack_viewscreen::render(); + } + + //=============================================================================== + + ViewscreenFindUi::ViewscreenFindUi() { + } + } +} + +//=============================================================================== +// Exported operations +//=============================================================================== + +void embark_assist::finder_ui::init(DFHack::Plugin *plugin_self, embark_assist::defs::find_callbacks find_callback, uint16_t max_inorganic) { + if (!embark_assist::finder_ui::state) { // First call. Have to do the setup + embark_assist::finder_ui::ui_setup(find_callback, max_inorganic); + } + Screen::show(new ViewscreenFindUi(), plugin_self); +} + +//=============================================================================== + +void embark_assist::finder_ui::activate() { +} + +//=============================================================================== + +void embark_assist::finder_ui::shutdown() { + if (embark_assist::finder_ui::state) { + for (uint16_t i = 0; i < embark_assist::finder_ui::state->ui.size(); i++) { + delete embark_assist::finder_ui::state->ui[i]; + } + + delete embark_assist::finder_ui::state; + embark_assist::finder_ui::state = nullptr; + } +} diff --git a/plugins/embark-assistant/finder_ui.h b/plugins/embark-assistant/finder_ui.h new file mode 100644 index 000000000..70bf4ce42 --- /dev/null +++ b/plugins/embark-assistant/finder_ui.h @@ -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(); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/help_ui.cpp b/plugins/embark-assistant/help_ui.cpp new file mode 100644 index 000000000..d75d00ce2 --- /dev/null +++ b/plugins/embark-assistant/help_ui.cpp @@ -0,0 +1,314 @@ +#include "Core.h" +#include + +#include +#include +#include + +#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 *input); + + void render(); + + std::string getFocusString() { return "Help UI"; } + + private: + pages current_page = pages::Intro; + }; + + //=============================================================================== + + void ViewscreenHelpUi::feed(std::set *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 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 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); +} diff --git a/plugins/embark-assistant/help_ui.h b/plugins/embark-assistant/help_ui.h new file mode 100644 index 000000000..65a2e4f1d --- /dev/null +++ b/plugins/embark-assistant/help_ui.h @@ -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); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/matcher.cpp b/plugins/embark-assistant/matcher.cpp new file mode 100644 index 000000000..484ee3cf8 --- /dev/null +++ b/plugins/embark-assistant/matcher.cpp @@ -0,0 +1,1445 @@ +#include + +#include + +#include "DataDefs.h" +#include "df/biome_type.h" +#include "df/inorganic_raw.h" +#include "df/region_map_entry.h" +#include "df/viewscreen.h" +#include "df/viewscreen_choose_start_sitest.h" +#include "df/world.h" +#include "df/world_data.h" +#include "df/world_raws.h" +#include "df/world_region.h" +#include "df/world_region_type.h" + +#include "matcher.h" +#include "survey.h" + +using df::global::world; + +namespace embark_assist { + namespace matcher { + + //======================================================================================= + + //======================================================================================= + + bool embark_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + uint16_t x, + uint16_t y, + uint16_t start_x, + uint16_t start_y, + embark_assist::defs::finders *finder) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + bool savagery_found[3] = { false, false, false }; + bool evilness_found[3] = { false, false, false }; + uint16_t aquifer_count = 0; + bool river_found = false; + bool waterfall_found = false; + uint16_t river_elevation; + uint16_t elevation = mlt->at(start_x).at(start_y).elevation; + bool clay_found = false; + bool sand_found = false; + bool flux_found = false; + uint8_t max_soil = 0; + bool uneven = false; + bool evil_weather_found = false; + bool reanimation_found = false; + bool thralling_found = false; + bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; + bool region_types[ENUM_LAST_ITEM(world_region_type) + 1]; + uint8_t biome_count; + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + const uint16_t embark_size = finder->x_dim * finder->y_dim; + + if (finder->biome_count_min != -1 || + finder->biome_count_max != -1 || + finder->biome_1 != -1 || + finder->biome_2 != -1 || + finder->biome_3 != -1) { + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) biomes[i] = false; + } + + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(world_region_type); i++) region_types[i] = false; + + for (uint16_t i = start_x; i < start_x + finder->x_dim; i++) { + for (uint16_t k = start_y; k < start_y + finder->y_dim; k++) { + + // Savagery & Evilness + { + savagery_found[mlt->at(i).at(k).savagery_level] = true; + evilness_found[mlt->at(i).at(k).evilness_level] = true; + + embark_assist::defs::evil_savagery_ranges l = embark_assist::defs::evil_savagery_ranges::Low; + while (true) { + if (mlt->at(i).at(k).savagery_level == static_cast(l)) { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Absent) return false; + } + else { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::All) return false; + } + + if (mlt->at(i).at(k).evilness_level == static_cast(l)) { + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Absent) return false; + } + else { + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::All) return false; + } + + if (l == embark_assist::defs::evil_savagery_ranges::High) break; + l = static_cast (static_cast(l) + 1); + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; + + case embark_assist::defs::aquifer_ranges::All: + if (!mlt->at(i).at(k).aquifer) return false; + aquifer_count++; + break; + + case embark_assist::defs::aquifer_ranges::Present: + case embark_assist::defs::aquifer_ranges::Partial: + case embark_assist::defs::aquifer_ranges::Not_All: + if (mlt->at(i).at(k).aquifer) aquifer_count++; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (mlt->at(i).at(k).aquifer) return false; + break; + } + + // River & Waterfall + if (mlt->at(i).at(k).river_present) { + // Actual size values were checked on the world tile level for min rivers + if (finder->max_river != embark_assist::defs::river_ranges::NA && + finder->max_river < static_cast(survey_results->at(x).at(y).river_size)) return false; + + if (river_found && river_elevation != mlt->at(i).at(k).river_elevation) { + if (finder->waterfall == embark_assist::defs::yes_no_ranges::No) return false; + waterfall_found = true; + } + river_found = true; + river_elevation = mlt->at(i).at(k).river_elevation; + } + + // Flat + if (finder->flat == embark_assist::defs::yes_no_ranges::Yes && + elevation != mlt->at(i).at(k).elevation) return false; + + if (elevation != mlt->at(i).at(k).elevation) uneven = true; + + // Clay + if (mlt->at(i).at(k).clay) { + if (finder->clay == embark_assist::defs::present_absent_ranges::Absent) return false; + clay_found = true; + } + + // Sand + if (mlt->at(i).at(k).sand) { + if (finder->sand == embark_assist::defs::present_absent_ranges::Absent) return false; + sand_found = true; + } + + // Flux + if (mlt->at(i).at(k).flux) { + if (finder->flux == embark_assist::defs::present_absent_ranges::Absent) return false; + flux_found = true; + } + + // Min Soil + if (finder->soil_min != embark_assist::defs::soil_ranges::NA && + mlt->at(i).at(k).soil_depth < static_cast(finder->soil_min) && + finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::All) return false; + + if (max_soil < mlt->at(i).at(k).soil_depth) { + max_soil = mlt->at(i).at(k).soil_depth; + } + + // Max Soil + if (finder->soil_max != embark_assist::defs::soil_ranges::NA && + mlt->at(i).at(k).soil_depth > static_cast(finder->soil_max)) return false; + + // Evil Weather + if (survey_results->at(x).at(y).evil_weather[mlt->at(i).at(k).biome_offset]) { + if (finder->evil_weather == embark_assist::defs::yes_no_ranges::No) return false; + evil_weather_found = true; + } + + // Reanmation + if (survey_results->at(x).at(y).reanimating[mlt->at(i).at(k).biome_offset]) { + if (finder->reanimation == embark_assist::defs::yes_no_ranges::No) return false; + reanimation_found = true; + } + + // Thralling + if (survey_results->at(x).at(y).thralling[mlt->at(i).at(k).biome_offset]) { + if (finder->thralling == embark_assist::defs::yes_no_ranges::No) return false; + thralling_found = true; + } + + // Biomes + biomes[survey_results->at(x).at(y).biome[mlt->at(i).at(k).biome_offset]] = true; + + // Region Type + region_types[world_data->regions[survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset]]->type] = true; + + // Metals + metal_1 = metal_1 || mlt->at(i).at(k).metals[finder->metal_1]; + metal_2 = metal_2 || mlt->at(i).at(k).metals[finder->metal_2]; + metal_3 = metal_3 || mlt->at(i).at(k).metals[finder->metal_3]; + + // Economics + economic_1 = economic_1 || mlt->at(i).at(k).economics[finder->economic_1]; + economic_2 = economic_2 || mlt->at(i).at(k).economics[finder->economic_2]; + economic_3 = economic_3 || mlt->at(i).at(k).economics[finder->economic_3]; + + // Minerals + mineral_1 = mineral_1 || mlt->at(i).at(k).minerals[finder->mineral_1]; + mineral_2 = mineral_2 || mlt->at(i).at(k).minerals[finder->mineral_2]; + mineral_3 = mineral_3 || mlt->at(i).at(k).minerals[finder->mineral_3]; + } + } + + // Summary section, for all the stuff that require the complete picture + // + // Savagery & Evilness + { + embark_assist::defs::evil_savagery_ranges l = embark_assist::defs::evil_savagery_ranges::Low; + + while (true) { + if (finder->savagery[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Present && + !savagery_found[static_cast(l)]) return false; + + if (finder->evilness[static_cast (l)] == + embark_assist::defs::evil_savagery_values::Present && + !evilness_found[static_cast(l)]) return false; + + if (l == embark_assist::defs::evil_savagery_ranges::High) break; + l = static_cast (static_cast(l) + 1); + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + case embark_assist::defs::aquifer_ranges::All: // Checked above + case embark_assist::defs::aquifer_ranges::Absent: // Ditto + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (aquifer_count == 0 || aquifer_count == embark_size) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (aquifer_count == embark_size) return false; + break; + } + + // River & Waterfall + if (!river_found && finder->min_river > embark_assist::defs::river_ranges::None) return false; + if (finder->waterfall == embark_assist::defs::yes_no_ranges::Yes && !waterfall_found) return false; + + // Flat + if (!uneven && finder->flat == embark_assist::defs::yes_no_ranges::No) return false; + + // Clay + if (finder->clay == embark_assist::defs::present_absent_ranges::Present && !clay_found) return false; + + // Sand + if (finder->sand == embark_assist::defs::present_absent_ranges::Present && !sand_found) return false; + + // Flux + if (finder->flux == embark_assist::defs::present_absent_ranges::Present && !flux_found) return false; + + // Min Soil + if (finder->soil_min != embark_assist::defs::soil_ranges::NA && + finder->soil_min_everywhere == embark_assist::defs::all_present_ranges::Present && + max_soil < static_cast(finder->soil_min)) return false; + + // Evil Weather + if (finder->evil_weather == embark_assist::defs::yes_no_ranges::Yes && !evil_weather_found) return false; + + // Reanimation + if (finder->reanimation == embark_assist::defs::yes_no_ranges::Yes && !reanimation_found) return false; + + // Thralling + if (finder->thralling == embark_assist::defs::yes_no_ranges::Yes && !thralling_found) return false; + + // Biomes + if (finder->biome_count_min != -1 || + finder->biome_count_max != -1) { + biome_count = 0; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + if (biomes[i]) biome_count++; + } + + if (biome_count < finder->biome_count_min || + (finder->biome_count_max != -1 && + finder->biome_count_max < biome_count)) return false; + } + + if (finder->biome_1 != -1 && !biomes[finder->biome_1]) return false; + if (finder->biome_2 != -1 && !biomes[finder->biome_2]) return false; + if (finder->biome_3 != -1 && !biomes[finder->biome_3]) return false; + + // Region Type + if (finder->region_type_1 != -1 && !region_types[finder->region_type_1]) return false; + if (finder->region_type_2 != -1 && !region_types[finder->region_type_2]) return false; + if (finder->region_type_3 != -1 && !region_types[finder->region_type_3]) return false; + + // Metals, Economics, and Minerals + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + + return true; + } + + //======================================================================================= + + void mid_level_tile_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt, + uint16_t x, + uint16_t y, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + bool match = false; + + for (uint16_t i = 0; i < 16; i++) { + for (uint16_t k = 0; k < 16; k++) { + if (i < 16 - finder->x_dim + 1 && k < 16 - finder->y_dim + 1) { + match_results->at(x).at(y).mlt_match[i][k] = embark_match(survey_results, mlt, x, y, i, k, finder); + match = match || match_results->at(x).at(y).mlt_match[i][k]; + } + else { + match_results->at(x).at(y).mlt_match[i][k] = false; + } + } + } + match_results->at(x).at(y).contains_match = match; + match_results->at(x).at(y).preliminary_match = false; + } + + //======================================================================================= + + bool world_tile_match(embark_assist::defs::world_tile_data *survey_results, + uint16_t x, + uint16_t y, + embark_assist::defs::finders *finder) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); + const uint16_t embark_size = finder->x_dim * finder->y_dim; + uint16_t count; + bool found; + + if (tile->surveyed) { + // Savagery + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->savagery[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->savagery_count[i] < embark_size) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->savagery_count[i] > 256 - embark_size) return false; + break; + } + } + + // Evilness + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->evilness[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->evilness_count[i] < embark_size) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->evilness_count[i] > 256 - embark_size) return false; + break; + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; // No restriction + + case embark_assist::defs::aquifer_ranges::All: + if (tile->aquifer_count < 256 - embark_size) return false; + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (tile->aquifer_count == 0 || + tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (tile->aquifer_count > 256 - embark_size) return false; + break; + } + + // River size. Every tile has riverless tiles, so max rivers has to be checked on the detailed level. + switch (tile->river_size) { + case embark_assist::defs::river_sizes::None: + if (finder->min_river > embark_assist::defs::river_ranges::None) return false; + break; + + case embark_assist::defs::river_sizes::Brook: + if (finder->min_river > embark_assist::defs::river_ranges::Brook) return false; + break; + + case embark_assist::defs::river_sizes::Stream: + if (finder->min_river > embark_assist::defs::river_ranges::Stream) return false; + break; + + case embark_assist::defs::river_sizes::Minor: + if (finder->min_river > embark_assist::defs::river_ranges::Minor) return false; + break; + + case embark_assist::defs::river_sizes::Medium: + if (finder->min_river > embark_assist::defs::river_ranges::Medium) return false; + break; + + case embark_assist::defs::river_sizes::Major: + if (finder->max_river != embark_assist::defs::river_ranges::NA) return false; + break; + } + + // Waterfall + switch (finder->waterfall) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->waterfall) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->waterfall && + embark_size == 256) return false; + break; + } + + // Flat. No world tile checks. Need to look at the details + + // Clay + switch (finder->clay) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->clay_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->clay_count > 256 - embark_size) return false; + break; + } + + // Sand + switch (finder->sand) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->sand_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->sand_count > 256 - embark_size) return false; + break; + } + + // Flux + switch (finder->flux) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->flux_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->flux_count > 256 - embark_size) return false; + break; + } + + // Soil Min + switch (finder->soil_min) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::None: + break; // No restriction + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->max_region_soil < 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->max_region_soil < 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->max_region_soil < 3) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + if (tile->max_region_soil < 4) return false; + break; + } + + // soil_min_everywhere only applies on the detailed level + + // Soil Max + switch (finder->soil_max) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::Very_Deep: + break; // No restriction + + case embark_assist::defs::soil_ranges::None: + if (tile->min_region_soil > 0) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->min_region_soil > 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->min_region_soil > 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->min_region_soil > 3) return false; + break; + } + + // Evil Weather + switch (finder->evil_weather) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->evil_weather_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->evil_weather_full) return false; + break; + } + + // Reanimating + switch (finder->reanimation) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->reanimating_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->reanimating_full) return false; + break; + } + + // Thralling + switch (finder->thralling) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->thralling_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->thralling_full) return false; + break; + } + + // Biome Count Min (Can't do anything with Max at this level) + if (finder->biome_count_min > tile->biome_count) return false; + + // Region Type 1 + if (finder->region_type_1 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_1) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 2 + if (finder->region_type_2 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_2) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 3 + if (finder->region_type_3 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_3) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Biome 1 + if (finder->biome_1 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_1) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 2 + if (finder->biome_2 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_2) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 3 + if (finder->biome_3 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_3) { + found = true; + break; + } + } + + if (!found) return false; + } + + if (finder->metal_1 != -1 || + finder->metal_2 != -1 || + finder->metal_3 != -1 || + finder->economic_1 != -1 || + finder->economic_2 != -1 || + finder->economic_3 != -1 || + finder->mineral_1 != -1 || + finder->mineral_2 != -1 || + finder->mineral_3 != -1) { + count = 0; + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + metal_1 = metal_1 || tile->metals[finder->metal_1]; + metal_2 = metal_2 || tile->metals[finder->metal_2]; + metal_3 = metal_3 || tile->metals[finder->metal_3]; + economic_1 = economic_1 || tile->economics[finder->economic_1]; + economic_2 = economic_2 || tile->economics[finder->economic_2]; + economic_3 = economic_3 || tile->economics[finder->economic_3]; + mineral_1 = mineral_1 || tile->minerals[finder->mineral_1]; + mineral_2 = mineral_2 || tile->minerals[finder->mineral_2]; + mineral_3 = mineral_3 || tile->minerals[finder->mineral_3]; + + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + } + } + else { // Not surveyed + // Savagery + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->savagery[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->savagery_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->savagery_count[i] == 256) return false; + break; + } + } + + // Evilness + for (uint8_t i = 0; i < 3; i++) + { + switch (finder->evilness[i]) { + case embark_assist::defs::evil_savagery_values::NA: + break; // No restriction + + case embark_assist::defs::evil_savagery_values::All: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Present: + if (tile->evilness_count[i] == 0) return false; + break; + + case embark_assist::defs::evil_savagery_values::Absent: + if (tile->evilness_count[i] == 256) return false; + break; + } + } + + // Aquifer + switch (finder->aquifer) { + case embark_assist::defs::aquifer_ranges::NA: + break; // No restriction + + case embark_assist::defs::aquifer_ranges::All: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Present: + if (tile->aquifer_count == 0) return false; + break; + + case embark_assist::defs::aquifer_ranges::Partial: + if (tile->aquifer_count == 0 || + tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Not_All: + if (tile->aquifer_count == 256) return false; + break; + + case embark_assist::defs::aquifer_ranges::Absent: + if (tile->aquifer_count == 256) return false; + break; + } + + // River size + switch (tile->river_size) { + case embark_assist::defs::river_sizes::None: + if (finder->min_river > embark_assist::defs::river_ranges::None) return false; + break; + + case embark_assist::defs::river_sizes::Brook: + if (finder->min_river > embark_assist::defs::river_ranges::Brook) return false; + break; + + case embark_assist::defs::river_sizes::Stream: + if (finder->min_river > embark_assist::defs::river_ranges::Stream) return false; + break; + + case embark_assist::defs::river_sizes::Minor: + if (finder->min_river > embark_assist::defs::river_ranges::Minor) return false; + break; + + case embark_assist::defs::river_sizes::Medium: + if (finder->min_river > embark_assist::defs::river_ranges::Medium) return false; + break; + + case embark_assist::defs::river_sizes::Major: + if (finder->max_river != embark_assist::defs::river_ranges::NA) return false; + break; + } + + // Waterfall + if (finder->waterfall == embark_assist::defs::yes_no_ranges::Yes && + tile->river_size == embark_assist::defs::river_sizes::None) return false; + + // Flat. No world tile checks. Need to look at the details + + // Clay + switch (finder->clay) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->clay_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->clay_count == 256) return false; + break; + } + + // Sand + switch (finder->sand) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->sand_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->sand_count == 256) return false; + break; + } + + // Flux + switch (finder->flux) { + case embark_assist::defs::present_absent_ranges::NA: + break; // No restriction + + case embark_assist::defs::present_absent_ranges::Present: + if (tile->flux_count == 0) return false; + break; + case embark_assist::defs::present_absent_ranges::Absent: + if (tile->flux_count == 256) return false; + break; + } + + // Soil Min + switch (finder->soil_min) { + case embark_assist::defs::soil_ranges::NA: + case embark_assist::defs::soil_ranges::None: + break; // No restriction + + case embark_assist::defs::soil_ranges::Very_Shallow: + if (tile->max_region_soil < 1) return false; + break; + + case embark_assist::defs::soil_ranges::Shallow: + if (tile->max_region_soil < 2) return false; + break; + + case embark_assist::defs::soil_ranges::Deep: + if (tile->max_region_soil < 3) return false; + break; + + case embark_assist::defs::soil_ranges::Very_Deep: + if (tile->max_region_soil < 4) return false; + break; + } + + // soil_min_everywhere only applies on the detailed level + + // Soil Max + // Can't say anything as the preliminary data isn't reliable + + // Evil Weather + switch (finder->evil_weather) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->evil_weather_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->evil_weather_full) return false; + break; + } + + // Reanimating + switch (finder->reanimation) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->reanimating_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->reanimating_full) return false; + break; + } + + // Thralling + switch (finder->thralling) { + case embark_assist::defs::yes_no_ranges::NA: + break; // No restriction + + case embark_assist::defs::yes_no_ranges::Yes: + if (!tile->thralling_possible) return false; + break; + + case embark_assist::defs::yes_no_ranges::No: + if (tile->thralling_full) return false; + break; + } + + // Biome Count Min (Can't do anything with Max at this level) + if (finder->biome_count_min > tile->biome_count) return false; + + // Region Type 1 + if (finder->region_type_1 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_1) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 2 + if (finder->region_type_2 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_2) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Region Type 3 + if (finder->region_type_3 != -1) { + found = false; + + for (uint8_t k = 1; k < 10; k++) { + if (tile->biome_index[k] != -1) { + if (world_data->regions[tile->biome_index[k]]->type == finder->region_type_3) { + found = true; + break; + } + } + + if (found) break; + } + + if (!found) return false; + } + + // Biome 1 + if (finder->biome_1 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_1) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 2 + if (finder->biome_2 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_2) { + found = true; + break; + } + } + + if (!found) return false; + } + + // Biome 3 + if (finder->biome_3 != -1) { + found = false; + + for (uint8_t i = 1; i < 10; i++) { + if (tile->biome[i] == finder->biome_3) { + found = true; + break; + } + } + + if (!found) return false; + } + + if (finder->metal_1 != -1 || + finder->metal_2 != -1 || + finder->metal_3 != -1 || + finder->economic_1 != -1 || + finder->economic_2 != -1 || + finder->economic_3 != -1 || + finder->mineral_1 != -1 || + finder->mineral_2 != -1 || + finder->mineral_3 != -1) { + count = 0; + bool metal_1 = finder->metal_1 == -1; + bool metal_2 = finder->metal_2 == -1; + bool metal_3 = finder->metal_3 == -1; + bool economic_1 = finder->economic_1 == -1; + bool economic_2 = finder->economic_2 == -1; + bool economic_3 = finder->economic_3 == -1; + bool mineral_1 = finder->mineral_1 == -1; + bool mineral_2 = finder->mineral_2 == -1; + bool mineral_3 = finder->mineral_3 == -1; + + metal_1 = metal_1 || tile->metals[finder->metal_1]; + metal_2 = metal_2 || tile->metals[finder->metal_2]; + metal_3 = metal_3 || tile->metals[finder->metal_3]; + economic_1 = economic_1 || tile->economics[finder->economic_1]; + economic_2 = economic_2 || tile->economics[finder->economic_2]; + economic_3 = economic_3 || tile->economics[finder->economic_3]; + mineral_1 = mineral_1 || tile->minerals[finder->mineral_1]; + mineral_2 = mineral_2 || tile->minerals[finder->mineral_2]; + mineral_3 = mineral_3 || tile->minerals[finder->mineral_3]; + + if (!metal_1 || + !metal_2 || + !metal_3 || + !economic_1 || + !economic_2 || + !economic_3 || + !mineral_1 || + !mineral_2 || + !mineral_3) return false; + } + } + return true; + } + + //======================================================================================= + + uint32_t preliminary_world_match(embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + uint32_t count = 0; + 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++) { + match_results->at(i).at(k).preliminary_match = + world_tile_match(survey_results, i, k, finder); + if (match_results->at(i).at(k).preliminary_match) count++; + match_results->at(i).at(k).contains_match = false; + } + } + + return count; + } + + //======================================================================================= + + void match_world_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::finders *finder, + embark_assist::defs::match_results *match_results, + uint16_t x, + uint16_t y) { + +// color_ostream_proxy out(Core::getInstance().getConsole()); + embark_assist::defs::mid_level_tiles mlt; + + embark_assist::survey::survey_mid_level_tile(geo_summary, + survey_results, + &mlt); + + mid_level_tile_match(survey_results, + &mlt, + x, + y, + finder, + match_results); + } + } +} + +//======================================================================================= +// Visible operations +//======================================================================================= + +void embark_assist::matcher::move_cursor(uint16_t x, uint16_t y) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + uint16_t original_x = screen->location.region_pos.x; + uint16_t original_y = screen->location.region_pos.y; + + uint16_t large_x = std::abs(original_x - x) / 10; + uint16_t small_x = std::abs(original_x - x) % 10; + uint16_t large_y = std::abs(original_y - y) / 10; + uint16_t small_y = std::abs(original_y - y) % 10; + + while (large_x > 0 || large_y > 0) { + if (large_x > 0 && large_y > 0) { + if (original_x - x > 0 && original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + } + else if (original_x - x > 0 && original_y - y < 0) { + screen->feed_key(df::interface_key::CURSOR_DOWNLEFT_FAST); + } + else if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPRIGHT_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT_FAST); + } + large_x--; + large_y--; + } + else if (large_x > 0) { + if (original_x - x > 0) { + screen->feed_key(df::interface_key::CURSOR_LEFT_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_RIGHT_FAST); + } + large_x--; + } + else { + if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UP_FAST); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWN_FAST); + } + large_y--; + } + } + + while (small_x > 0 || small_y > 0) { + if (small_x > 0 && small_y > 0) { + if (original_x - x > 0 && original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT); + } + else if (original_x - x > 0 && original_y - y < 0) { + screen->feed_key(df::interface_key::CURSOR_DOWNLEFT); + } + else if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UPRIGHT); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWNRIGHT); + } + small_x--; + small_y--; + } + else if (small_x > 0) { + if (original_x - x > 0) { + screen->feed_key(df::interface_key::CURSOR_LEFT); + } + else { + screen->feed_key(df::interface_key::CURSOR_RIGHT); + } + small_x--; + } + else { + if (original_y - y > 0) { + screen->feed_key(df::interface_key::CURSOR_UP); + } + else { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + small_y--; + } + } +} + +//======================================================================================= + +uint16_t embark_assist::matcher::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) { + + color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + uint16_t x_end; + uint16_t y_end; + bool turn; + uint16_t count; + uint16_t preliminary_matches; + + if (!iterator->active) { + embark_assist::survey::clear_results(match_results); + + // Static check for impossible requirements + // + count = 0; + for (uint8_t i = 0; i < 3; i++) { + if (iterator->finder.evilness[i] == embark_assist::defs::evil_savagery_values::All) { + count++; + } + } + + if (count > 1) { + out.printerr("matcher::find: Will never find any due to multiple All evilness requirements\n"); + return 0; + } + + count = 0; + for (uint8_t i = 0; i < 3; i++) { + if (iterator->finder.savagery[i] == embark_assist::defs::evil_savagery_values::All) { + count++; + } + } + + if (count > 1) { + out.printerr("matcher::find: Will never find any due to multiple All savagery requirements\n"); + return 0; + } + + if (iterator->finder.max_river < iterator->finder.min_river && + iterator->finder.max_river != embark_assist::defs::river_ranges::NA) { + out.printerr("matcher::find: Will never find any due to max river < min river\n"); + return 0; + } + + if (iterator->finder.waterfall == embark_assist::defs::yes_no_ranges::Yes && + iterator->finder.max_river == embark_assist::defs::river_ranges::None) { + out.printerr("matcher::find: Will never find any waterfalls with None as max river\n"); + return 0; + } + + if (iterator->finder.soil_max < iterator->finder.soil_min && + iterator->finder.soil_max != embark_assist::defs::soil_ranges::NA) { + out.printerr("matcher::find: Will never find any matches with max soil < min soil\n"); + return 0; + } + + if (iterator->finder.biome_count_max < iterator->finder.biome_count_min && + iterator->finder.biome_count_max != -1) { + out.printerr("matcher::find: Will never find any matches with max biomes < min biomes\n"); + return 0; + } + + preliminary_matches = preliminary_world_match(survey_results, &iterator->finder, match_results); + + if (preliminary_matches == 0) { + out.printerr("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + return 0; + } + else { + out.print("matcher::find: Preliminarily matching world tiles: %i\n", preliminary_matches); + } + + while (screen->location.region_pos.x != 0 || screen->location.region_pos.y != 0) { + screen->feed_key(df::interface_key::CURSOR_UPLEFT_FAST); + } + iterator->active = true; + iterator->i = 0; + iterator->k = 0; + iterator->x_right = true; + iterator->y_down = true; + iterator->inhibit_x_turn = false; + iterator->inhibit_y_turn = false; + iterator->count = 0; + } + + if ((iterator->k == world->worldgen.worldgen_parms.dim_x / 16 && iterator->x_right) || + (iterator->k == 0 && !iterator->x_right)) { + x_end = 0; + } + else { + x_end = 15; + } + + if (iterator->i == world->worldgen.worldgen_parms.dim_y / 16) { + y_end = 0; + } + else { + y_end = 15; + } + + for (uint16_t l = 0; l <= x_end; l++) { + for (uint16_t m = 0; m <= y_end; m++) { + // This is where the payload goes + if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).preliminary_match) { + match_world_tile(geo_summary, + survey_results, + &iterator->finder, + match_results, + screen->location.region_pos.x, + screen->location.region_pos.y); + if (match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).contains_match) { + iterator->count++; + } + } + else { + for (uint16_t n = 0; n < 16; n++) { + for (uint16_t p = 0; p < 16; p++) { + match_results->at(screen->location.region_pos.x).at(screen->location.region_pos.y).mlt_match[n][p] = false; + } + } + } + // End of payload section + + if (m != y_end) { + if (iterator->y_down) { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + else { + screen->feed_key(df::interface_key::CURSOR_UP); + } + } + else { + if (screen->location.region_pos.x != 0 && + screen->location.region_pos.x != world->worldgen.worldgen_parms.dim_x - 1) { + turn = true; + } + else { + iterator->inhibit_y_turn = !iterator->inhibit_y_turn; + turn = iterator->inhibit_y_turn; + } + + if (turn) { + iterator->y_down = !iterator->y_down; + } + else { + if (iterator->y_down) { + screen->feed_key(df::interface_key::CURSOR_DOWN); + } + else { + screen->feed_key(df::interface_key::CURSOR_UP); + } + } + } + } + + if (iterator->x_right) { // Won't do anything at the edge, so we don't bother filter those cases. + screen->feed_key(df::interface_key::CURSOR_RIGHT); + } + else { + screen->feed_key(df::interface_key::CURSOR_LEFT); + } + + if (!iterator->x_right && + screen->location.region_pos.x == 0) { + turn = !turn; + + if (turn) { + iterator->x_right = true; + } + } + else if (iterator->x_right && + screen->location.region_pos.x == world->worldgen.worldgen_parms.dim_x - 1) { + turn = !turn; + + if (turn) { + iterator->x_right = false; + } + } + } + // } + + iterator->k++; + if (iterator->k > world->worldgen.worldgen_parms.dim_x / 16) + { + iterator->k = 0; + iterator->i++; + iterator->active = !(iterator->i > world->worldgen.worldgen_parms.dim_y / 16); + + if (!iterator->active) { + embark_assist::matcher::move_cursor(iterator->x, iterator->y); + } + } + + return iterator->count; +} diff --git a/plugins/embark-assistant/matcher.h b/plugins/embark-assistant/matcher.h new file mode 100644 index 000000000..9887ffbd0 --- /dev/null +++ b/plugins/embark-assistant/matcher.h @@ -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); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/overlay.cpp b/plugins/embark-assistant/overlay.cpp new file mode 100644 index 000000000..6b63fa70d --- /dev/null +++ b/plugins/embark-assistant/overlay.cpp @@ -0,0 +1,439 @@ +#include + +#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 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 *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(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; + } +} diff --git a/plugins/embark-assistant/overlay.h b/plugins/embark-assistant/overlay.h new file mode 100644 index 000000000..d66d6d7fd --- /dev/null +++ b/plugins/embark-assistant/overlay.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#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(); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/screen.cpp b/plugins/embark-assistant/screen.cpp new file mode 100644 index 000000000..477e5c63c --- /dev/null +++ b/plugins/embark-assistant/screen.cpp @@ -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; +} diff --git a/plugins/embark-assistant/screen.h b/plugins/embark-assistant/screen.h new file mode 100644 index 000000000..06b723f24 --- /dev/null +++ b/plugins/embark-assistant/screen.h @@ -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); + } +} \ No newline at end of file diff --git a/plugins/embark-assistant/survey.cpp b/plugins/embark-assistant/survey.cpp new file mode 100644 index 000000000..6583d2479 --- /dev/null +++ b/plugins/embark-assistant/survey.cpp @@ -0,0 +1,1081 @@ +#include + +#include "Core.h" +#include +#include +#include + +#include +#include "modules/Materials.h" + +#include "DataDefs.h" +#include "df/coord2d.h" +#include "df/creature_interaction_effect.h" +#include "df/creature_interaction_effect_display_symbolst.h" +#include "df/creature_interaction_effect_type.h" +#include "df/feature_init.h" +#include "df/inorganic_flags.h" +#include "df/inorganic_raw.h" +#include "df/interaction.h" +#include "df/interaction_instance.h" +#include "df/interaction_source.h" +#include "df/interaction_source_regionst.h" +#include "df/interaction_source_type.h" +#include "df/interaction_target.h" +#include "df/interaction_target_corpsest.h" +#include "df/interaction_target_materialst.h" +#include "df/material_common.h" +#include "df/reaction.h" +#include "df/region_map_entry.h" +#include "df/syndrome.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_geo_layer.h" +#include "df/world_raws.h" +#include "df/world_region.h" +#include "df/world_region_details.h" +#include "df/world_region_feature.h" +#include "df/world_river.h" +#include "df/world_site.h" +#include "df/world_site_type.h" +#include "df/world_underground_region.h" + +#include "biome_type.h" +#include "defs.h" +#include "survey.h" + +using namespace DFHack; +using namespace df::enums; +using namespace Gui; + +using df::global::world; + +namespace embark_assist { + namespace survey { + struct states { + uint16_t clay_reaction = -1; + uint16_t flux_reaction = -1; + uint16_t x; + uint16_t y; + uint8_t local_min_x; + uint8_t local_min_y; + uint8_t local_max_x; + uint8_t local_max_y; + uint16_t max_inorganic; + }; + + static states *state; + + //======================================================================================= + + bool geo_survey(embark_assist::defs::geo_data *geo_summary) { + color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + auto reactions = world->raws.reactions; + bool non_soil_found; + uint16_t size; + + for (uint16_t i = 0; i < reactions.size(); i++) { + if (reactions[i]->code == "MAKE_CLAY_BRICKS") { + state->clay_reaction = i; + } + + if (reactions[i]->code == "PIG_IRON_MAKING") { + state->flux_reaction = i; + } + } + + if (state->clay_reaction == -1) { + out.printerr("The reaction 'MAKE_CLAY_BRICKS' was not found, so clay can't be identified.\n"); + } + + if (state->flux_reaction == -1) { + out.printerr("The reaction 'PIG_IRON_MAKING' was not found, so flux can't be identified.\n"); + } + + for (uint16_t i = 0; i < world_data->geo_biomes.size(); i++) { + geo_summary->at(i).possible_metals.resize(state->max_inorganic); + geo_summary->at(i).possible_economics.resize(state->max_inorganic); + geo_summary->at(i).possible_minerals.resize(state->max_inorganic); + + non_soil_found = true; + df::world_geo_biome *geo = world_data->geo_biomes[i]; + + for (uint16_t k = 0; k < geo->layers.size() && k < 16; k++) { + df::world_geo_layer *layer = geo->layers[k]; + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + geo_summary->at(i).soil_size += layer->top_height - layer->bottom_height + 1; + + if (world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::SOIL_SAND)) { + geo_summary->at(i).sand_absent = false; + } + + if (non_soil_found) { + geo_summary->at(i).top_soil_only = false; + } + } + else { + non_soil_found = true; + } + + geo_summary->at(i).possible_minerals[layer->mat_index] = true; + + size = (uint16_t)world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size(); + + for (uint16_t l = 0; l < size; l++) { + geo_summary->at(i).possible_metals.at(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index[l]) = true; + } + + size = (uint16_t)world->raws.inorganics[layer->mat_index]->economic_uses.size(); + if (size != 0) { + geo_summary->at(i).possible_economics[layer->mat_index] = true; + + for (uint16_t l = 0; l < size; l++) { + if (world->raws.inorganics[layer->mat_index]->economic_uses[l] == state->clay_reaction) { + geo_summary->at(i).clay_absent = false; + } + + if (world->raws.inorganics[layer->mat_index]->economic_uses[l] == state->flux_reaction) { + geo_summary->at(i).flux_absent = false; + } + } + } + + size = (uint16_t)layer->vein_mat.size(); + + for (uint16_t l = 0; l < size; l++) { + auto vein = layer->vein_mat[l]; + geo_summary->at(i).possible_minerals[vein] = true; + + for (uint16_t m = 0; m < world->raws.inorganics[vein]->metal_ore.mat_index.size(); m++) { + geo_summary->at(i).possible_metals.at(world->raws.inorganics[vein]->metal_ore.mat_index[m]) = true; + } + + if (world->raws.inorganics[vein]->economic_uses.size() != 0) { + geo_summary->at(i).possible_economics[vein] = true; + + for (uint16_t m = 0; m < world->raws.inorganics[vein]->economic_uses.size(); m++) { + if (world->raws.inorganics[vein]->economic_uses[m] == state->clay_reaction) { + geo_summary->at(i).clay_absent = false; + } + + if (world->raws.inorganics[vein]->economic_uses[m] == state->flux_reaction) { + geo_summary->at(i).flux_absent = false; + } + } + } + } + + if (layer->bottom_height <= -3 && + world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) { + geo_summary->at(i).aquifer_absent = false; + } + + if (non_soil_found == true) { + geo_summary->at(i).top_soil_aquifer_only = false; + } + } + } + return true; + } + + + //================================================================================= + + void survey_rivers(embark_assist::defs::world_tile_data *survey_results) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + int16_t x; + int16_t y; + + for (uint16_t i = 0; i < world_data->rivers.size(); i++) { + for (uint16_t k = 0; k < world_data->rivers[i]->path.x.size(); k++) { + x = world_data->rivers[i]->path.x[k]; + y = world_data->rivers[i]->path.y[k]; + + if (world_data->rivers[i]->flow[k] < 5000) { + if (world_data->region_map[x][y].flags.is_set(df::region_map_entry_flags::is_brook)) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Brook; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Stream; + } + } + else if (world_data->rivers[i]->flow[k] < 10000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Minor; + } + else if (world_data->rivers[i]->flow[k] < 20000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Medium; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Major; + } + } + + x = world_data->rivers[i]->end_pos.x; + y = world_data->rivers[i]->end_pos.y; + + // Make the guess the river size for the end is the same as the tile next to the end. Note that DF + // doesn't actually recognize this tile as part of the river in the pre embark river name display. + // We also assume the is_river/is_brook flags are actually set properly for the end tile. + // + if (x >= 0 && y >= 0 && x < world->worldgen.worldgen_parms.dim_x && y < world->worldgen.worldgen_parms.dim_y) { + if (survey_results->at(x).at(y).river_size == embark_assist::defs::river_sizes::None) { + if (world_data->rivers[i]->path.x.size() && + world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 5000) { + if (world_data->region_map[x][y].flags.is_set(df::region_map_entry_flags::is_brook)) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Brook; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Stream; + } + } + else if (world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 10000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Minor; + } + else if (world_data->rivers[i]->flow[world_data->rivers[i]->path.x.size() - 1] < 20000) { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Medium; + } + else { + survey_results->at(x).at(y).river_size = embark_assist::defs::river_sizes::Major; + } + } + } + } + } + + //================================================================================= + + void survey_evil_weather(embark_assist::defs::world_tile_data *survey_results) { +// color_ostream_proxy out(Core::getInstance().getConsole()); + df::world_data *world_data = world->world_data; + + for (uint16_t i = 0; i < world->interaction_instances.all.size(); i++) { + auto interaction = world->raws.interactions[world->interaction_instances.all[i]->interaction_id]; + uint16_t region_index = world->interaction_instances.all[i]->region_index; + bool thralling = false; + bool reanimating = false; + + if (interaction->sources.size() && + interaction->sources[0]->getType() == df::interaction_source_type::REGION) { + for (uint16_t k = 0; k < interaction->targets.size(); k++) { + if (interaction->targets[k]->getType() == 0) { // Returns wrong type. Should be df::interaction_target_type::CORPSE + reanimating = true; + } + else if (interaction->targets[k]->getType() == 2) {// Returns wrong type.. Should be df::interaction_target_type::MATERIAL + df::interaction_target_materialst* material = virtual_cast(interaction->targets[k]); + if (material && DFHack::MaterialInfo(material->mat_type, material->mat_index).isInorganic()) { + for (uint16_t l = 0; l < world->raws.inorganics[material->mat_index]->material.syndrome.size(); l++) { + for (uint16_t m = 0; m < world->raws.inorganics[material->mat_index]->material.syndrome[l]->ce.size(); m++) { + if (world->raws.inorganics[material->mat_index]->material.syndrome[l]->ce[m]->getType() == df::creature_interaction_effect_type::FLASH_TILE) { + // Using this as a proxy. There seems to be a group of 4 effects for thralls: + // display symbol, flash symbol, phys att change and one more. + thralling = true; + } + } + } + } + } + } + } + + for (uint16_t k = 0; k < world_data->regions[region_index]->region_coords.size(); k++) { + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).evil_weather[5] = true; + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).reanimating[5] = reanimating; + survey_results->at(world_data->regions[region_index]->region_coords[k].x).at(world_data->regions[region_index]->region_coords[k].y).thralling[5] = thralling; + } + } + + 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++) { + survey_results->at(i).at(k).evil_weather_possible = false; + survey_results->at(i).at(k).reanimating_possible = false; + survey_results->at(i).at(k).thralling_possible = false; + survey_results->at(i).at(k).evil_weather_full = true; + survey_results->at(i).at(k).reanimating_full = true; + survey_results->at(i).at(k).thralling_full = true; + + for (uint8_t l = 1; l < 10; l++) { + if (survey_results->at(i).at(k).biome_index[l] != -1) { + df::coord2d adjusted = apply_offset(i, k, l); + survey_results->at(i).at(k).evil_weather[l] = survey_results->at(adjusted.x).at(adjusted.y).evil_weather[5]; + survey_results->at(i).at(k).reanimating[l] = survey_results->at(adjusted.x).at(adjusted.y).reanimating[5]; + survey_results->at(i).at(k).thralling[l] = survey_results->at(adjusted.x).at(adjusted.y).thralling[5]; + + if (survey_results->at(i).at(k).evil_weather[l]) { + survey_results->at(i).at(k).evil_weather_possible = true; + } + else { + survey_results->at(i).at(k).evil_weather_full = false; + } + + if (survey_results->at(i).at(k).reanimating[l]) { + survey_results->at(i).at(k).reanimating_possible = true; + } + else { + survey_results->at(i).at(k).reanimating_full = false; + } + + if (survey_results->at(i).at(k).thralling[l]) { + survey_results->at(i).at(k).thralling_possible = true; + } + else { + survey_results->at(i).at(k).thralling_full = false; + } + } + } + } + } + } + } +} + +//================================================================================= +// Exported operations +//================================================================================= + +void embark_assist::survey::setup(uint16_t max_inorganic) { + embark_assist::survey::state = new(embark_assist::survey::states); + embark_assist::survey::state->max_inorganic = max_inorganic; +} + +//================================================================================= + +df::coord2d embark_assist::survey::get_last_pos() { + return{ embark_assist::survey::state->x, embark_assist::survey::state->y }; +} + +//================================================================================= + +void embark_assist::survey::initiate(embark_assist::defs::mid_level_tiles *mlt) { + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + mlt->at(i).at(k).metals.resize(state->max_inorganic); + mlt->at(i).at(k).economics.resize(state->max_inorganic); + mlt->at(i).at(k).minerals.resize(state->max_inorganic); + } + } +} + +//================================================================================= + +void embark_assist::survey::clear_results(embark_assist::defs::match_results *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++) { + match_results->at(i).at(k).preliminary_match = false; + match_results->at(i).at(k).contains_match = false; + + for (uint16_t l = 0; l < 16; l++) { + for (uint16_t m = 0; m < 16; m++) { + match_results->at(i).at(k).mlt_match[l][m] = false; + } + } + } + } +} + +//================================================================================= + +void embark_assist::survey::high_level_world_survey(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + + embark_assist::survey::geo_survey(geo_summary); + 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++) { + df::coord2d adjusted; + df::world_data *world_data = world->world_data; + uint16_t geo_index; + uint16_t sav_ev; + uint8_t offset_count = 0; + survey_results->at(i).at(k).surveyed = false; + survey_results->at(i).at(k).aquifer_count = 0; + survey_results->at(i).at(k).clay_count = 0; + survey_results->at(i).at(k).sand_count = 0; + survey_results->at(i).at(k).flux_count = 0; + survey_results->at(i).at(k).min_region_soil = 10; + survey_results->at(i).at(k).max_region_soil = 0; + survey_results->at(i).at(k).waterfall = false; + survey_results->at(i).at(k).savagery_count[0] = 0; + survey_results->at(i).at(k).savagery_count[1] = 0; + survey_results->at(i).at(k).savagery_count[2] = 0; + survey_results->at(i).at(k).evilness_count[0] = 0; + survey_results->at(i).at(k).evilness_count[1] = 0; + survey_results->at(i).at(k).evilness_count[2] = 0; + survey_results->at(i).at(k).metals.resize(state->max_inorganic); + survey_results->at(i).at(k).economics.resize(state->max_inorganic); + survey_results->at(i).at(k).minerals.resize(state->max_inorganic); + // Evil weather and rivers are handled in later operations. Should probably be merged into one. + + for (uint8_t l = 1; l < 10; l++) + { + adjusted = apply_offset(i, k, l); + if (adjusted.x != i || adjusted.y != k || l == 5) { + offset_count++; + + survey_results->at(i).at(k).biome_index[l] = world_data->region_map[adjusted.x][adjusted.y].region_id; + survey_results->at(i).at(k).biome[l] = get_biome_type(adjusted.x, adjusted.y, k); + geo_index = world_data->region_map[adjusted.x][adjusted.y].geo_index; + + if (!geo_summary->at(geo_index).aquifer_absent) survey_results->at(i).at(k).aquifer_count++; + if (!geo_summary->at(geo_index).clay_absent) survey_results->at(i).at(k).clay_count++; + if (!geo_summary->at(geo_index).sand_absent) survey_results->at(i).at(k).sand_count++; + if (!geo_summary->at(geo_index).flux_absent) survey_results->at(i).at(k).flux_count++; + + if (geo_summary->at(geo_index).soil_size < survey_results->at(i).at(k).min_region_soil) + survey_results->at(i).at(k).min_region_soil = geo_summary->at(geo_index).soil_size; + + if (geo_summary->at(geo_index).soil_size > survey_results->at(i).at(k).max_region_soil) + survey_results->at(i).at(k).max_region_soil = geo_summary->at(geo_index).soil_size; + + sav_ev = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; + if (sav_ev == 3) sav_ev = 2; + survey_results->at(i).at(k).savagery_count[sav_ev]++; + + sav_ev = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; + if (sav_ev == 3) sav_ev = 2; + survey_results->at(i).at(k).evilness_count[sav_ev]++; + + for (uint16_t m = 0; m < state->max_inorganic; m++) { + if (geo_summary->at(geo_index).possible_metals[m]) survey_results->at(i).at(k).metals[m] = true; + if (geo_summary->at(geo_index).possible_economics[m]) survey_results->at(i).at(k).economics[m] = true; + if (geo_summary->at(geo_index).possible_minerals[m]) survey_results->at(i).at(k).minerals[m] = true; + } + } + else { + survey_results->at(i).at(k).biome_index[l] = -1; + survey_results->at(i).at(k).biome[l] = -1; + } + } + + survey_results->at(i).at(k).biome_count = 0; + for (uint8_t l = 1; l < 10; l++) { + if (survey_results->at(i).at(k).biome[l] != -1) survey_results->at(i).at(k).biome_count++; + } + + if (survey_results->at(i).at(k).aquifer_count == offset_count) survey_results->at(i).at(k).aquifer_count = 256; + if (survey_results->at(i).at(k).clay_count == offset_count) survey_results->at(i).at(k).clay_count = 256; + if (survey_results->at(i).at(k).sand_count == offset_count) survey_results->at(i).at(k).sand_count = 256; + if (survey_results->at(i).at(k).flux_count == offset_count) survey_results->at(i).at(k).flux_count = 256; + for (uint8_t l = 0; l < 3; l++) { + if (survey_results->at(i).at(k).savagery_count[l] == offset_count) survey_results->at(i).at(k).savagery_count[l] = 256; + if (survey_results->at(i).at(k).evilness_count[l] == offset_count) survey_results->at(i).at(k).evilness_count[l] = 256; + } + } + } + + embark_assist::survey::survey_rivers(survey_results); + embark_assist::survey::survey_evil_weather(survey_results); +} + +//================================================================================= + +void embark_assist::survey::survey_mid_level_tile(embark_assist::defs::geo_data *geo_summary, + embark_assist::defs::world_tile_data *survey_results, + embark_assist::defs::mid_level_tiles *mlt) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + int16_t x = screen->location.region_pos.x; + int16_t y = screen->location.region_pos.y; + embark_assist::defs::region_tile_datum *tile = &survey_results->at(x).at(y); + int8_t max_soil_depth; + int8_t offset; + int16_t elevation; + int16_t last_bottom; + int16_t top_z; + int16_t base_z; + int16_t min_z = 0; // Initialized to silence warning about potential usage of uninitialized data. + int16_t bottom_z; + df::coord2d adjusted; + df::world_data *world_data = world->world_data; + df::world_region_details *details = world_data->region_details[0]; + df::region_map_entry *world_tile = &world_data->region_map[x][y]; + std::vector features; + uint8_t soil_erosion; + uint16_t end_check_l; + uint16_t end_check_m; + uint16_t end_check_n; + + for (uint16_t i = 0; i < state->max_inorganic; i++) { + tile->metals[i] = 0; + tile->economics[i] = 0; + tile->minerals[i] = 0; + } + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + mlt->at(i).at(k).metals.resize(state->max_inorganic); + mlt->at(i).at(k).economics.resize(state->max_inorganic); + mlt->at(i).at(k).minerals.resize(state->max_inorganic); + } + } + + for (uint8_t i = 1; i < 10; i++) survey_results->at(x).at(y).biome_index[i] = -1; + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + max_soil_depth = -1; + + offset = details->biome[i][k]; + adjusted = apply_offset(x, y, offset); + + if (adjusted.x != x || adjusted.y != y) + { + mlt->at(i).at(k).biome_offset = offset; + } + else + { + mlt->at(i).at(k).biome_offset = 5; + }; + + survey_results->at(x).at(y).biome_index[mlt->at(i).at(k).biome_offset] = + world_data->region_map[adjusted.x][adjusted.y].region_id; + + mlt->at(i).at(k).savagery_level = world_data->region_map[adjusted.x][adjusted.y].savagery / 33; + if (mlt->at(i).at(k).savagery_level == 3) { + mlt->at(i).at(k).savagery_level = 2; + } + mlt->at(i).at(k).evilness_level = world_data->region_map[adjusted.x][adjusted.y].evilness / 33; + if (mlt->at(i).at(k).evilness_level == 3) { + mlt->at(i).at(k).evilness_level = 2; + } + + elevation = details->elevation[i][k]; + + // Special biome adjustments + if (!world_data->region_map[adjusted.x][adjusted.y].flags.is_set(region_map_entry_flags::is_lake)) { + if (world_data->region_map[adjusted.x][adjusted.y].elevation >= 150) { // Mountain + max_soil_depth = 0; + + } + else if (world_data->region_map[adjusted.x][adjusted.y].elevation < 100) { // Ocean + if (elevation == 99) { + elevation = 98; + } + + if ((world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 4 || + world_data->geo_biomes[world_data->region_map[x][y].geo_index]->unk1 == 5) && + details->unk12e8 < 500) { + max_soil_depth = 0; + } + } + } + + base_z = elevation - 1; + features = details->features[i][k]; + std::map layer_bottom, layer_top; + + end_check_l = static_cast(features.size()); + for (size_t l = 0; l < end_check_l; l++) { + auto feature = features[l]; + + if (feature->layer != -1 && + feature->min_z != -30000) { + auto layer = world_data->underground_regions[feature->layer]; + + layer_bottom[layer->layer_depth] = feature->min_z; + layer_top[layer->layer_depth] = feature->max_z; + base_z = std::min((int)base_z, (int)feature->min_z); + + if (layer->type == df::world_underground_region::MagmaSea) { + min_z = feature->min_z; // The features are individual per region tile + break; + } + } + } + + // Compute shifts for layers in the stack. + + if (max_soil_depth == -1) { // Not set to zero by the biome + max_soil_depth = std::max((154 - elevation) / 5, 1); + } + + soil_erosion = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - + std::min((int)geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size, (int)max_soil_depth); + int16_t layer_shift[16]; + int16_t cur_shift = elevation + soil_erosion - 1; + + mlt->at(i).at(k).aquifer = false; + mlt->at(i).at(k).clay = false; + mlt->at(i).at(k).sand = false; + mlt->at(i).at(k).flux = false; + if (max_soil_depth == 0) { + mlt->at(i).at(k).soil_depth = 0; + } + else { + mlt->at(i).at(k).soil_depth = geo_summary->at(world_data->region_map[adjusted.x][adjusted.y].geo_index).soil_size - soil_erosion; + } + mlt->at(i).at(k).offset = offset; + mlt->at(i).at(k).elevation = details->elevation[i][k]; + mlt->at(i).at(k).river_present = false; + mlt->at(i).at(k).river_elevation = 100; + + if (details->rivers_vertical.active[i][k] == 1) { + mlt->at(i).at(k).river_present = true; + mlt->at(i).at(k).river_elevation = details->rivers_vertical.elevation[i][k]; + } + else if (details->rivers_horizontal.active[i][k] == 1) { + mlt->at(i).at(k).river_present = true; + mlt->at(i).at(k).river_elevation = details->rivers_horizontal.elevation[i][k]; + } + + if (tile->min_region_soil > mlt->at(i).at(k).soil_depth) { + tile->min_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (tile->max_region_soil < mlt->at(i).at(k).soil_depth) { + tile->max_region_soil = mlt->at(i).at(k).soil_depth; + } + + end_check_l = static_cast(world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers.size()); + if (end_check_l > 16) end_check_l = 16; + + for (uint16_t l = 0; l < end_check_l; l++) { + auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; + layer_shift[l] = cur_shift; + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + int16_t size = layer->top_height - layer->bottom_height - 1; + // Comment copied from prospector.cpp(like the logic)... + // This is to replicate the behavior of a probable bug in the + // map generation code : if a layer is partially eroded, the + // removed levels are in fact transferred to the layer below, + // because unlike the case of removing the whole layer, the code + // does not execute a loop to shift the lower part of the stack up. + if (size > soil_erosion) { + cur_shift = cur_shift - soil_erosion; + } + + soil_erosion -= std::min((int)soil_erosion, (int)size); + } + } + + last_bottom = elevation; + // Don't have to set up the end_check as we can reuse the one above. + + for (uint16_t l = 0; l < end_check_l; l++) { + auto layer = world_data->geo_biomes[world_data->region_map[adjusted.x][adjusted.y].geo_index]->layers[l]; + top_z = last_bottom - 1; + bottom_z = std::max((int)layer->bottom_height + layer_shift[l], (int)min_z); + + if (l == 15) { + bottom_z = min_z; // stretch layer if needed + } + + if (top_z >= bottom_z) { + mlt->at(i).at(k).minerals[layer->mat_index] = true; + + end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->metal_ore.mat_index.size()); + + for (uint16_t m = 0; m < end_check_m; m++) { + mlt->at(i).at(k).metals[world->raws.inorganics[layer->mat_index]->metal_ore.mat_index[m]] = true; + } + + if (layer->type == df::geo_layer_type::SOIL || + layer->type == df::geo_layer_type::SOIL_SAND) { + if (world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::SOIL_SAND)) { + mlt->at(i).at(k).sand = true; + } + } + + if (world->raws.inorganics[layer->mat_index]->economic_uses.size() > 0) { + mlt->at(i).at(k).economics[layer->mat_index] = true; + + end_check_m = static_cast(world->raws.inorganics[layer->mat_index]->economic_uses.size()); + for (uint16_t m = 0; m < end_check_m; m++) { + if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->clay_reaction) { + mlt->at(i).at(k).clay = true; + } + + else if (world->raws.inorganics[layer->mat_index]->economic_uses[m] == state->flux_reaction) { + mlt->at(i).at(k).flux = true; + } + } + } + + end_check_m = static_cast(layer->vein_mat.size()); + + for (uint16_t m = 0; m < end_check_m; m++) { + mlt->at(i).at(k).minerals[layer->vein_mat[m]] = true; + + end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index.size()); + + for (uint16_t n = 0; n < end_check_n; n++) { + mlt->at(i).at(k).metals[world->raws.inorganics[layer->vein_mat[m]]->metal_ore.mat_index[n]] = true; + } + + if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size() > 0) { + mlt->at(i).at(k).economics[layer->vein_mat[m]] = true; + + end_check_n = static_cast(world->raws.inorganics[layer->vein_mat[m]]->economic_uses.size()); + for (uint16_t n = 0; n < end_check_n; n++) { + if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->clay_reaction) { + mlt->at(i).at(k).clay = true; + } + + else if (world->raws.inorganics[layer->vein_mat[m]]->economic_uses[n] == state->flux_reaction) { + mlt->at(i).at(k).flux = true; + } + } + } + } + + if (bottom_z <= elevation - 3 && + world->raws.inorganics[layer->mat_index]->flags.is_set(df::inorganic_flags::AQUIFER)) { + mlt->at(i).at(k).aquifer = true; + } + } + } + } + } + + survey_results->at(x).at(y).aquifer_count = 0; + survey_results->at(x).at(y).clay_count = 0; + survey_results->at(x).at(y).sand_count = 0; + survey_results->at(x).at(y).flux_count = 0; + survey_results->at(x).at(y).min_region_soil = 10; + survey_results->at(x).at(y).max_region_soil = 0; + survey_results->at(x).at(y).savagery_count[0] = 0; + survey_results->at(x).at(y).savagery_count[1] = 0; + survey_results->at(x).at(y).savagery_count[2] = 0; + survey_results->at(x).at(y).evilness_count[0] = 0; + survey_results->at(x).at(y).evilness_count[1] = 0; + survey_results->at(x).at(y).evilness_count[2] = 0; + + bool river_elevation_found = false; + int16_t river_elevation; + + for (uint8_t i = 0; i < 16; i++) { + for (uint8_t k = 0; k < 16; k++) { + if (mlt->at(i).at(k).aquifer) { survey_results->at(x).at(y).aquifer_count++; } + if (mlt->at(i).at(k).clay) { survey_results->at(x).at(y).clay_count++; } + if (mlt->at(i).at(k).sand) { survey_results->at(x).at(y).sand_count++; } + if (mlt->at(i).at(k).flux) { survey_results->at(x).at(y).flux_count++; } + + if (mlt->at(i).at(k).soil_depth < survey_results->at(x).at(y).min_region_soil) { + survey_results->at(x).at(y).min_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).soil_depth > survey_results->at(x).at(y).max_region_soil) { + survey_results->at(x).at(y).max_region_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).river_present) { + if (river_elevation_found) { + if (mlt->at(i).at(k).river_elevation != river_elevation) + { + survey_results->at(x).at(y).waterfall = true; + } + } + else { + river_elevation_found = true; + river_elevation = mlt->at(i).at(k).river_elevation; + } + } + + // River size surveyed separately + // biome_index handled above + // biome handled below + // evil weather handled separately + // reanimating handled separately + // thralling handled separately + + survey_results->at(x).at(y).savagery_count[mlt->at(i).at(k).savagery_level]++; + survey_results->at(x).at(y).evilness_count[mlt->at(i).at(k).evilness_level]++; + + for (uint16_t l = 0; l < state->max_inorganic; l++) { + if (mlt->at(i).at(k).metals[l]) { survey_results->at(x).at(y).metals[l] = true; } + if (mlt->at(i).at(k).economics[l]) { survey_results->at(x).at(y).economics[l] = true; } + if (mlt->at(i).at(k).minerals[l]) { survey_results->at(x).at(y).minerals[l] = true; } + } + } + } + + for (uint8_t i = 1; i < 10; i++) { + if (survey_results->at(x).at(y).biome_index[i] == -1) { + survey_results->at(x).at(y).biome[i] = -1; + } + } + + bool biomes[ENUM_LAST_ITEM(biome_type) + 1]; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + biomes[i] = false; + } + + for (uint8_t i = 1; i < 10; i++) + { + if (survey_results->at(x).at(y).biome[i] != -1) { + biomes[survey_results->at(x).at(y).biome[i]] = true; + } + } + int count = 0; + for (uint8_t i = 0; i <= ENUM_LAST_ITEM(biome_type); i++) { + if (biomes[i]) count++; + } + + tile->biome_count = count; + tile->surveyed = true; +} +//================================================================================= + +df::coord2d embark_assist::survey::apply_offset(uint16_t x, uint16_t y, int8_t offset) { + df::coord2d result; + result.x = x; + result.y = y; + + switch (offset) { + case 1: + result.x--; + result.y++; + break; + + case 2: + result.y++; + break; + + case 3: + result.x++; + result.y++; + break; + + case 4: + result.x--; + break; + + case 5: + break; // Center. No change + + case 6: + result.x++; + break; + + case 7: + result.x--; + result.y--; + break; + + case 8: + result.y--; + break; + + case 9: + result.x++; + result.y--; + break; + + default: + // Bug. Just act as if it's the center... + break; + } + + if (result.x < 0) { + result.x = 0; + } + else if (result.x >= world->worldgen.worldgen_parms.dim_x) { + result.x = world->worldgen.worldgen_parms.dim_x - 1; + } + + if (result.y < 0) { + result.y = 0; + } + else if (result.y >= world->worldgen.worldgen_parms.dim_y) { + result.y = world->worldgen.worldgen_parms.dim_y - 1; + } + + return result; +} + +//================================================================================= + +void embark_assist::survey::survey_region_sites(embark_assist::defs::site_lists *site_list) { + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + df::world_data *world_data = world->world_data; + int8_t index = 0; + + site_list->clear(); + + for (uint32_t i = 0; i < world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites.size(); i++) { + auto site = world_data->region_map[screen->location.region_pos.x][screen->location.region_pos.y].sites[i]; + switch (site->type) { + case df::world_site_type::PlayerFortress: + case df::world_site_type::DarkFortress: + case df::world_site_type::MountainHalls: + case df::world_site_type::ForestRetreat: + case df::world_site_type::Town: + case df::world_site_type::Fortress: + break; // Already visible + + case df::world_site_type::Cave: + if (!world->worldgen.worldgen_parms.all_caves_visible) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'c' }); // Cave + } + break; + + case df::world_site_type::Monument: + if (site->subtype_info->lair_type != -1 || + site->subtype_info->is_monument == 0) { // Not Tomb, which is visible already + } + else if (site->subtype_info->lair_type == -1) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'V' }); // Vault + } + else { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'M' }); // Any other Monument type. Pyramid? + } + break; + + case df::world_site_type::ImportantLocation: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'i' }); // Don't really know what that is... + break; + + case df::world_site_type::LairShrine: + if (site->subtype_info->lair_type == 0 || + site->subtype_info->lair_type == 1 || + site->subtype_info->lair_type == 4) { // Only Rocs seen. Mountain lair? + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'l' }); // Lair + } + else if (site->subtype_info->lair_type == 2) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'L' }); // Labyrinth + } + else if (site->subtype_info->lair_type == 3) { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'S' }); // Shrine + } + else { + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '?' }); // Can these exist? + } + break; + + case df::world_site_type::Camp: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, 'C' }); // Camp + break; + + default: + site_list->push_back({ (uint8_t)site->rgn_min_x , (uint8_t)site->rgn_min_y, '!' }); // Not even in the enum... + break; + } + } +} + +//================================================================================= + +void embark_assist::survey::survey_embark(embark_assist::defs::mid_level_tiles *mlt, + embark_assist::defs::site_infos *site_info, + bool use_cache) { + + // color_ostream_proxy out(Core::getInstance().getConsole()); + auto screen = Gui::getViewscreenByType(0); + int16_t elevation; + uint16_t x = screen->location.region_pos.x; + uint16_t y = screen->location.region_pos.y; + bool river_found = false; + int16_t river_elevation; + std::vector metals(state->max_inorganic); + std::vector economics(state->max_inorganic); + std::vector minerals(state->max_inorganic); + + if (!use_cache) { // For some reason DF scrambles these values on world tile movements (at least in Lua...). + state->local_min_x = screen->location.embark_pos_min.x; + state->local_min_y = screen->location.embark_pos_min.y; + state->local_max_x = screen->location.embark_pos_max.x; + state->local_max_y = screen->location.embark_pos_max.y; + } + + state->x = x; + state->y = y; + + site_info->aquifer = false; + site_info->aquifer_full = true; + site_info->min_soil = 10; + site_info->max_soil = 0; + site_info->flat = true; + site_info->waterfall = false; + site_info->clay = false; + site_info->sand = false; + site_info->flux = false; + site_info->metals.clear(); + site_info->economics.clear(); + site_info->metals.clear(); + + for (uint8_t i = state->local_min_x; i <= state->local_max_x; i++) { + for (uint8_t k = state->local_min_y; k <= state->local_max_y; k++) { + if (mlt->at(i).at(k).aquifer) { + site_info->aquifer = true; + } + else { + site_info->aquifer_full = false; + } + + if (mlt->at(i).at(k).soil_depth < site_info->min_soil) { + site_info->min_soil = mlt->at(i).at(k).soil_depth; + } + + if (mlt->at(i).at(k).soil_depth > site_info->max_soil) { + site_info->max_soil = mlt->at(i).at(k).soil_depth; + } + + if (i == state->local_min_x && k == state->local_min_y) { + elevation = mlt->at(i).at(k).elevation; + + } + else if (elevation != mlt->at(i).at(k).elevation) { + site_info->flat = false; + } + + if (mlt->at(i).at(k).river_present) { + if (river_found) { + if (river_elevation != mlt->at(i).at(k).river_elevation) { + site_info->waterfall = true; + } + } + else { + river_elevation = mlt->at(i).at(k).river_elevation; + river_found = true; + } + } + + if (mlt->at(i).at(k).clay) { + site_info->clay = true; + } + + if (mlt->at(i).at(k).sand) { + site_info->sand = true; + } + + if (mlt->at(i).at(k).flux) { + site_info->flux = true; + } + + for (uint16_t l = 0; l < state->max_inorganic; l++) { + metals[l] = metals[l] || mlt->at(i).at(k).metals[l]; + economics[l] = economics[l] || mlt->at(i).at(k).economics[l]; + minerals[l] = minerals[l] || mlt->at(i).at(k).minerals[l]; + } + } + } + for (uint16_t l = 0; l < state->max_inorganic; l++) { + if (metals[l]) { + site_info->metals.push_back(l); + } + + if (economics[l]) { + site_info->economics.push_back(l); + } + + if (minerals[l]) { + site_info->minerals.push_back(l); + } + } +} + +//================================================================================= + +void embark_assist::survey::shutdown() { + delete state; +} + diff --git a/plugins/embark-assistant/survey.h b/plugins/embark-assistant/survey.h new file mode 100644 index 000000000..03d2d9a03 --- /dev/null +++ b/plugins/embark-assistant/survey.h @@ -0,0 +1,38 @@ +#pragma once +#include + +#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(); + } +} \ No newline at end of file diff --git a/plugins/fix-armory.cpp b/plugins/fix-armory.cpp index 8b062290b..0f63b4d93 100644 --- a/plugins/fix-armory.cpp +++ b/plugins/fix-armory.cpp @@ -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; diff --git a/plugins/labormanager/joblabormapper.cpp b/plugins/labormanager/joblabormapper.cpp index 051331137..d16bfafcf 100644 --- a/plugins/labormanager/joblabormapper.cpp +++ b/plugins/labormanager/joblabormapper.cpp @@ -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 }; @@ -917,4 +920,4 @@ df::unit_labor JobLaborMapper::find_job_labor(df::job* j) return labor; } -/* End of labor deducer */ \ No newline at end of file +/* End of labor deducer */ diff --git a/plugins/labormanager/labormanager.cpp b/plugins/labormanager/labormanager.cpp index 3a026d1ea..42e718488 100644 --- a/plugins/labormanager/labormanager.cpp +++ b/plugins/labormanager/labormanager.cpp @@ -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 diff --git a/plugins/listcolumn.h b/plugins/listcolumn.h index 4e4815b69..f7747d96d 100644 --- a/plugins/listcolumn.h +++ b/plugins/listcolumn.h @@ -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; diff --git a/plugins/proto/AdventureControl.proto b/plugins/proto/AdventureControl.proto new file mode 100644 index 000000000..7d3d128d6 --- /dev/null +++ b/plugins/proto/AdventureControl.proto @@ -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; +} \ No newline at end of file diff --git a/plugins/proto/RemoteFortressReader.proto b/plugins/proto/RemoteFortressReader.proto index 62ea5ee54..35f5b0abe 100644 --- a/plugins/proto/RemoteFortressReader.proto +++ b/plugins/proto/RemoteFortressReader.proto @@ -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; +} diff --git a/plugins/remotefortressreader/CMakeLists.txt b/plugins/remotefortressreader/CMakeLists.txt index ee6383a3c..10cc21047 100644 --- a/plugins/remotefortressreader/CMakeLists.txt +++ b/plugins/remotefortressreader/CMakeLists.txt @@ -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) diff --git a/plugins/remotefortressreader/adventure_control.cpp b/plugins/remotefortressreader/adventure_control.cpp new file mode 100644 index 000000000..6fb592d62 --- /dev/null +++ b/plugins/remotefortressreader/adventure_control.cpp @@ -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 + +using namespace AdventureControl; +using namespace df::enums; +using namespace DFHack; +using namespace Gui; + +std::queue 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; +} diff --git a/plugins/remotefortressreader/adventure_control.h b/plugins/remotefortressreader/adventure_control.h new file mode 100644 index 000000000..bc674e427 --- /dev/null +++ b/plugins/remotefortressreader/adventure_control.h @@ -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 diff --git a/plugins/remotefortressreader/df_version_int.h b/plugins/remotefortressreader/df_version_int.h index d6d0bd750..35c9ed851 100644 --- a/plugins/remotefortressreader/df_version_int.h +++ b/plugins/remotefortressreader/df_version_int.h @@ -1,2 +1,2 @@ #pragma once -#define DF_VERSION_INT 43005 +#define DF_VERSION_INT 44002 diff --git a/plugins/remotefortressreader/item_reader.cpp b/plugins/remotefortressreader/item_reader.cpp new file mode 100644 index 000000000..f96397409 --- /dev/null +++ b/plugins/remotefortressreader/item_reader.cpp @@ -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(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; +} diff --git a/plugins/remotefortressreader/item_reader.h b/plugins/remotefortressreader/item_reader.h new file mode 100644 index 000000000..045c1be51 --- /dev/null +++ b/plugins/remotefortressreader/item_reader.h @@ -0,0 +1,32 @@ +#ifndef ITEM_READER_H +#define ITEM_READER_H + +#include +#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 *, int); + +void CopyImage(const df::art_image * image, RemoteFortressReader::ArtImage * netImage); + +#endif // !ITEM_READER_H diff --git a/plugins/remotefortressreader/remotefortressreader.cpp b/plugins/remotefortressreader/remotefortressreader.cpp index 6858d10c8..5b84bf745 100644 --- a/plugins/remotefortressreader/remotefortressreader.cpp +++ b/plugins/remotefortressreader/remotefortressreader.cpp @@ -1,5 +1,5 @@ #include "df_version_int.h" -#define RFR_VERSION "0.18.0" +#define RFR_VERSION "0.19.0" #include #include @@ -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 & 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(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 & parameters) { remove("bp_appearance_mods.csv"); @@ -233,20 +258,28 @@ command_result RemoteFortressReader_version(color_ostream &out, vector & 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 &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 * items) return result; } +map 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(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(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(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(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; } diff --git a/plugins/rendermax/renderer_light.cpp b/plugins/rendermax/renderer_light.cpp index f0f3c92cf..199e174af 100644 --- a/plugins/rendermax/renderer_light.cpp +++ b/plugins/rendermax/renderer_light.cpp @@ -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; diff --git a/plugins/rendermax/rendermax.cpp b/plugins/rendermax/rendermax.cpp index 2140743ca..be8be07ba 100644 --- a/plugins/rendermax/rendermax.cpp +++ b/plugins/rendermax/rendermax.cpp @@ -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); diff --git a/plugins/ruby/ui.rb b/plugins/ruby/ui.rb index a9dd05437..9268dcb9e 100644 --- a/plugins/ruby/ui.rb +++ b/plugins/ruby/ui.rb @@ -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 diff --git a/plugins/tweak/tweak.cpp b/plugins/tweak/tweak.cpp index bf044dd58..6b5b1f687 100644 --- a/plugins/tweak/tweak.cpp +++ b/plugins/tweak/tweak.cpp @@ -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 list_entries; - std::vector list_unk1; - std::vector list_unk2; + std::vector mat_types; + std::vector mat_indices; std::vector list_unk3; std::vector 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)); diff --git a/plugins/tweak/tweaks/kitchen-keys.h b/plugins/tweak/tweaks/kitchen-keys.h deleted file mode 100644 index ca48a1c89..000000000 --- a/plugins/tweak/tweaks/kitchen-keys.h +++ /dev/null @@ -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(world->selected_building); - if (!ws) - return false; - if (ws->type != workshop_type::Kitchen) - return false; - return true; - } - - DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set *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); diff --git a/plugins/tweak/tweaks/pausing-fps-counter.h b/plugins/tweak/tweaks/pausing-fps-counter.h new file mode 100644 index 000000000..ee76aab15 --- /dev/null +++ b/plugins/tweak/tweaks/pausing-fps-counter.h @@ -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); diff --git a/plugins/zone.cpp b/plugins/zone.cpp index e41704044..608216092 100644 --- a/plugins/zone.cpp +++ b/plugins/zone.cpp @@ -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; diff --git a/scripts b/scripts index 3baa24fec..8d079a591 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 3baa24fec93461218b5b658de94884ebff0a0b23 +Subproject commit 8d079a59122d9ba72ce9c0f7687402a343d09bc7 diff --git a/test/main.lua b/test/main.lua new file mode 100644 index 000000000..0085a9711 --- /dev/null +++ b/test/main.lua @@ -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') diff --git a/travis/dfhack_travis.init b/travis/dfhack_travis.init new file mode 100644 index 000000000..d9bc3e5ba --- /dev/null +++ b/travis/dfhack_travis.init @@ -0,0 +1,2 @@ +:lua dfhack.internal.addScriptPath(os.getenv('TRAVIS_BUILD_DIR')) +test/main diff --git a/travis/download-df.sh b/travis/download-df.sh new file mode 100755 index 000000000..20dc3dbd4 --- /dev/null +++ b/travis/download-df.sh @@ -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 diff --git a/travis/get-df-version.sh b/travis/get-df-version.sh new file mode 100755 index 000000000..13d317d2e --- /dev/null +++ b/travis/get-df-version.sh @@ -0,0 +1,4 @@ +#!/bin/sh +cd "$(dirname "$0")" +cd .. +grep DF_VERSION CMakeLists.txt | perl -ne 'print "$&\n" if /[\d\.]+/' diff --git a/travis/run-tests.py b/travis/run-tests.py new file mode 100644 index 000000000..a8265088c --- /dev/null +++ b/travis/run-tests.py @@ -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)