Merge branch 'develop' of https://github.com/DFHack/dfhack into develop

develop
Japa 2016-01-10 12:47:03 +05:30
commit ed62f0f554
54 changed files with 1667 additions and 464 deletions

@ -58,6 +58,11 @@ if(MSVC)
add_definitions( "/wd4819" )
endif()
IF(CMAKE_CROSSCOMPILING)
SET(DFHACK_NATIVE_BUILD_DIR "DFHACK_NATIVE_BUILD_DIR-NOTFOUND" CACHE FILEPATH "Path to a native build directory")
INCLUDE("${DFHACK_NATIVE_BUILD_DIR}/ImportExecutables.cmake")
ENDIF()
# set up folder structures for IDE solutions
# MSVC Express won't load solutions that use this. It also doesn't include MFC supported
# Check for MFC!
@ -97,8 +102,8 @@ if (NOT EXISTS ${dfhack_SOURCE_DIR}/library/xml/codegen.pl OR NOT EXISTS ${dfhac
endif()
# set up versioning.
set(DF_VERSION "0.42.03")
SET(DFHACK_RELEASE "alpha1")
set(DF_VERSION "0.42.04")
SET(DFHACK_RELEASE "alpha2")
SET(DFHACK_PRERELEASE TRUE)
set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}")
@ -171,6 +176,7 @@ endif()
if(NOT UNIX)
SET(ZLIB_ROOT depends/zlib/)
endif()
set(ZLIB_ROOT /usr/lib/i386-linux-gnu)
find_package(ZLIB REQUIRED)
include_directories(depends/protobuf)
include_directories(depends/lua/include)

@ -9,7 +9,7 @@
Internals
Lua
New [Internal Commands | Plugins | Scripts | Tweaks]
New [Internal Commands | Plugins | Scripts | Features]
Fixes
Misc Improvements
Removed
@ -32,9 +32,49 @@ Changelog
DFHack future
=============
Internals
---------
- Commands to run on startup can be specified on the command line with ``+``
Example::
./dfhack +devel/print-args example
"Dwarf Fortress.exe" +devel/print-args example
- Prevented plugins with active viewscreens from being unloaded and causing a crash
New Plugins
-----------
- `autogems`: Creates a new Workshop Order setting, automatically cutting rough gems
New Scripts
-----------
- `devel/save-version`: Displays DF version information about the current save
New Features
------------
- `search-plugin`: Support for the location occupation assignment menu
- `confirm`: Added a confirmation for retiring locations
- `search-plugin`: Support for new screens:
- location occupation assignment
- civilization animal training knowledge
- animal trainer assignment
- `tweak`:
- ``tweak block-labors``: Prevents labors that can't be used from being toggled
- ``tweak hide-priority``: Adds an option to hide designation priority indicators
- ``tweak title-start-rename``: Adds a safe rename option to the title screen "Start Playing" menu
Fixes
-----
- `exportlegends`: Handles entities without specific races, and a few other fixes for things new to v0.42
- `showmood`: Fixed name display on OS X/Linux
Misc Improvements
-----------------
- `weather`: now implemented by a script
DFHack 0.40.24-r5
=================

@ -21,8 +21,13 @@ IF(CMAKE_COMPILER_IS_GNUCC)
FOREACH(header tr1/unordered_map unordered_map)
FOREACH(namespace std::tr1 std )
IF(HAVE_HASH_MAP EQUAL 0 AND NOT STL_HASH_OLD_GCC)
CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp")
TRY_RUN(HASH_MAP_RUN_RESULT HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp")
CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
IF(CMAKE_CROSSCOMPILING)
TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
SET(HASH_MAP_RUN_RESULT ${HASH_MAP_COMPILE_RESULT})
ELSE()
TRY_RUN(HASH_MAP_RUN_RESULT HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
ENDIF()
IF (HASH_MAP_COMPILE_RESULT AND HASH_MAP_RUN_RESULT EQUAL 1)
SET(HASH_MAP_H <${header}>)
STRING(REPLACE "map" "set" HASH_SET_H ${HASH_MAP_H})
@ -40,8 +45,8 @@ IF(CMAKE_COMPILER_IS_GNUCC)
FOREACH(header ext/hash_map hash_map)
FOREACH(namespace __gnu_cxx "" std stdext)
IF (HAVE_HASH_MAP EQUAL 0)
CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp")
TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp")
CONFIGURE_FILE("${CMAKE_CURRENT_SOURCE_DIR}/testHashMap.cpp.in" "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
TRY_COMPILE(HASH_MAP_COMPILE_RESULT ${PROJECT_BINARY_DIR}/CMakeTmp "${CMAKE_CURRENT_BINARY_DIR}/testHashMap.cpp")
IF (HASH_MAP_COMPILE_RESULT)
SET(HASH_MAP_H <${header}>)
STRING(REPLACE "map" "set" HASH_SET_H ${HASH_MAP_H})
@ -237,8 +242,12 @@ TARGET_LINK_LIBRARIES(protoc protobuf)
# Protobuf compiler executable
ADD_EXECUTABLE(protoc-bin google/protobuf/compiler/main.cc google/protobuf/compiler/command_line_interface.h google/protobuf/compiler/cpp/cpp_generator.h)
IDE_FOLDER(protoc-bin "Depends")
IF(NOT CMAKE_CROSSCOMPILING)
ADD_EXECUTABLE(protoc-bin google/protobuf/compiler/main.cc google/protobuf/compiler/command_line_interface.h google/protobuf/compiler/cpp/cpp_generator.h)
IDE_FOLDER(protoc-bin "Depends")
SET_TARGET_PROPERTIES(protoc-bin PROPERTIES OUTPUT_NAME protoc)
TARGET_LINK_LIBRARIES(protoc-bin protoc)
SET_TARGET_PROPERTIES(protoc-bin PROPERTIES OUTPUT_NAME protoc)
TARGET_LINK_LIBRARIES(protoc-bin protoc)
EXPORT(TARGETS protoc-bin FILE ${CMAKE_BINARY_DIR}/ImportExecutables.cmake )
ENDIF()

@ -179,12 +179,15 @@ tweak farm-plot-select
tweak import-priority-category
# Misc. UI tweaks
tweak block-labors # Prevents labors that can't be used from being toggled
tweak civ-view-agreement
tweak fps-min
tweak hide-priority
tweak kitchen-keys
tweak kitchen-prefs-empty
tweak max-wheelbarrow
tweak shift-8-scroll
tweak title-start-rename
tweak tradereq-pet-gender
###########################
@ -208,6 +211,7 @@ enable \
confirm \
dwarfmonitor \
mousequery \
autogems \
automelt \
autotrade \
buildingplan \

@ -37,6 +37,7 @@ IndigoFenix
James Logsdon jlogsdon
Japa JapaMala
Jared Adams
Jim Lisi stonetoad
jj jjyg jj``
John Beisley huin
John Shade gsvslto
@ -58,6 +59,7 @@ Mike Stewart thewonderidiot
Mikko Juola Noeda Adeon
MithrilTuxedo MithrilTuxedo
mizipzor mizipzor
moversti moversti
Neil Little nmlittle
Nick Rart nickrart comestible
Omniclasm
@ -91,7 +93,9 @@ Simon Jackson sizeak
Tacomagic
Tim Walberg twalberg
Timothy Collett danaris
Tom Jobbins TheBloke
Tom Prince
txtsd txtsd
U-glouglou\\simon
Valentin Ochs Cat-Ion
Vjek

@ -363,7 +363,7 @@ To install Chocolatey and the required dependencies:
* Close this ``cmd.exe`` window and open another Admin ``cmd.exe`` in the same way
* Run the following command::
choco install git cmake strawberryperl -y
choco install git cmake.portable strawberryperl -y
* Close the Admin ``cmd.exe`` window; you're done!

@ -1908,6 +1908,14 @@ and are only documented here for completeness:
The table used by ``dfhack.run_script()`` to give every script its own
global environment, persistent between calls to the script.
* ``dfhack.internal.getPE()``
Returns the PE timestamp of the DF executable (only on Windows)
* ``dfhack.internal.getMD5()``
Returns the MD5 of the DF executable (only on OS X and Linux)
* ``dfhack.internal.getAddress(name)``
Returns the global address ``name``, or *nil*.

@ -280,6 +280,7 @@ Subcommands that persist until disabled or DF quits:
in advmode. The issue is that the screen tries to force you to select
the contents separately from the container. This forcefully skips child
reagents.
:block-labors: Prevents labors that can't be used from being toggled
:civ-view-agreement: Fixes overlapping text on the "view agreement" screen
:craft-age-wear: Fixes the behavior of crafted items wearing out over time (:bug:`6003`).
With this tweak, items made from cloth and leather will gain a level of
@ -294,6 +295,7 @@ Subcommands that persist until disabled or DF quits:
:fast-trade: Makes Shift-Down in the Move Goods to Depot and Trade screens select
the current item (fully, in case of a stack), and scroll down one line.
:fps-min: Fixes the in-game minimum FPS setting
:hide-priority: Adds an option to hide designation priority indicators
:import-priority-category:
Allows changing the priority of all goods in a
category when discussing an import agreement with the liaison
@ -315,6 +317,7 @@ Subcommands that persist until disabled or DF quits:
:nestbox-color: Fixes the color of built nestboxes
:shift-8-scroll: Gives Shift-8 (or :kbd:`*`) priority when scrolling menus, instead of scrolling the map
:stable-cursor: Saves the exact cursor position between t/q/k/d/b/etc menus of fortress mode.
:title-start-rename: Adds a safe rename option to the title screen "Start Playing" menu
:tradereq-pet-gender: Displays pet genders on the trade request screen
.. _fix-armory:
@ -872,6 +875,13 @@ job-duplicate
In :kbd:`q` mode, when a job is highlighted within a workshop or furnace
building, calling ``job-duplicate`` instantly duplicates the job.
.. _autogems:
autogems
========
Creates a new Workshop Order setting, automatically cutting rough gems
when `enabled <enable>`.
.. _stockflow:
stockflow
@ -2104,11 +2114,6 @@ Options:
Beware that filling in hollow veins will trigger a demon invasion on top of
your miner when you dig into the region that used to be hollow.
weather
=======
Prints the current weather, and lets you change the weather to 'clear', 'rain'
or 'snow', with those words as commands (eg ``weather rain``).
=================

@ -62,6 +62,7 @@ using namespace std;
using namespace DFHack;
#include "df/ui.h"
#include "df/ui_sidebar_menus.h"
#include "df/world.h"
#include "df/world_data.h"
#include "df/interfacest.h"
@ -1255,7 +1256,7 @@ static void run_dfhack_init(color_ostream &out, Core *core)
std::vector<std::string> prefixes(1, "dfhack");
size_t count = loadScriptFiles(core, out, prefixes, ".");
if (!count)
if (!count || !Filesystem::isfile("dfhack.init"))
{
core->runCommand(out, "gui/no-dfhack-init");
core->loadScriptFile(out, "dfhack.init-example", true);
@ -1549,6 +1550,67 @@ bool Core::Init()
if (!server->listen(RemoteClient::GetDefaultPort()))
cerr << "TCP listen failed.\n";
if (df::global::ui_sidebar_menus)
{
vector<string> args;
const string & raw = df::global::ui_sidebar_menus->command_line.raw;
size_t offset = 0;
while (offset < raw.size())
{
if (raw[offset] == '"')
{
offset++;
size_t next = raw.find("\"", offset);
args.push_back(raw.substr(offset, next - offset));
offset = next + 2;
}
else
{
size_t next = raw.find(" ", offset);
if (next == string::npos)
{
args.push_back(raw.substr(offset));
offset = raw.size();
}
else
{
args.push_back(raw.substr(offset, next - offset));
offset = next + 1;
}
}
}
for (auto it = args.begin(); it != args.end(); )
{
const string & first = *it;
if (first.length() > 0 && first[0] == '+')
{
vector<string> cmd;
for (it++; it != args.end(); it++) {
const string & arg = *it;
if (arg.length() > 0 && arg[0] == '+')
{
break;
}
cmd.push_back(arg);
}
if (runCommand(con, first.substr(1), cmd) != CR_OK)
{
cerr << "Error running command: " << first.substr(1);
for (auto it2 = cmd.begin(); it2 != cmd.end(); it2++)
{
cerr << " \"" << *it2 << "\"";
}
cerr << "\n";
}
}
else
{
it++;
}
}
}
cerr << "DFHack is running.\n";
return true;
}

@ -2304,6 +2304,24 @@ static const LuaWrapper::FunctionReg dfhack_internal_module[] = {
{ NULL, NULL }
};
static int internal_getmd5(lua_State *L)
{
auto p = Core::getInstance().p;
if (p->getDescriptor()->getOS() == OS_WINDOWS)
luaL_error(L, "process MD5 not available on Windows");
lua_pushstring(L, p->getMD5().c_str());
return 1;
}
static int internal_getPE(lua_State *L)
{
auto p = Core::getInstance().p;
if (p->getDescriptor()->getOS() != OS_WINDOWS)
luaL_error(L, "process PE timestamp not available on non-Windows");
lua_pushinteger(L, p->getPE());
return 1;
}
static int internal_getAddress(lua_State *L)
{
const char *name = luaL_checkstring(L, 1);
@ -2683,6 +2701,8 @@ static int internal_findScript(lua_State *L)
}
static const luaL_Reg dfhack_internal_funcs[] = {
{ "getPE", internal_getPE },
{ "getMD5", internal_getmd5 },
{ "getAddress", internal_getAddress },
{ "setAddress", internal_setAddress },
{ "getVTable", internal_getVTable },

@ -24,6 +24,7 @@ distribution.
#include "modules/EventManager.h"
#include "modules/Filesystem.h"
#include "modules/Screen.h"
#include "Internal.h"
#include "Core.h"
#include "MemAccess.h"
@ -272,23 +273,13 @@ bool Plugin::load(color_ostream &con)
plugin_abort_load; \
return false; \
}
#define plugin_check_symbols(sym1,sym2) \
if (!LookupPlugin(plug, sym1) && !LookupPlugin(plug, sym2)) \
{ \
con.printerr("Plugin %s: missing symbols: %s & %s\n", name.c_str(), sym1, sym2); \
plugin_abort_load; \
return false; \
}
plugin_check_symbols("plugin_name", "name") // allow r3 plugins
plugin_check_symbols("plugin_version", "version") // allow r3 plugins
plugin_check_symbol("plugin_name")
plugin_check_symbol("plugin_version")
plugin_check_symbol("plugin_self")
plugin_check_symbol("plugin_init")
plugin_check_symbol("plugin_globals")
const char ** plug_name =(const char ** ) LookupPlugin(plug, "plugin_name");
if (!plug_name) // allow r3 plugin naming
plug_name = (const char ** )LookupPlugin(plug, "name");
if (name != *plug_name)
{
con.printerr("Plugin %s: name mismatch, claims to be %s\n", name.c_str(), *plug_name);
@ -296,9 +287,6 @@ bool Plugin::load(color_ostream &con)
return false;
}
const char ** plug_version =(const char ** ) LookupPlugin(plug, "plugin_version");
if (!plug_version) // allow r3 plugin version
plug_version =(const char ** ) LookupPlugin(plug, "version");
const char ** plug_git_desc_ptr = (const char**) LookupPlugin(plug, "plugin_git_description");
Plugin **plug_self = (Plugin**)LookupPlugin(plug, "plugin_self");
const char *dfhack_version = Version::dfhack_version();
@ -385,6 +373,12 @@ bool Plugin::unload(color_ostream &con)
// if we are actually loaded
if(state == PS_LOADED)
{
if (Screen::hasActiveScreens(this))
{
con.printerr("Cannot unload plugin %s: has active viewscreens\n", name.c_str());
access->unlock();
return false;
}
EventManager::unregisterAll(this);
// notify the plugin about an attempt to shutdown
if (plugin_onstatechange &&

@ -60,15 +60,16 @@ Process::Process(VersionInfoFactory * known_versions)
identified = false;
my_descriptor = 0;
my_pe = 0;
md5wrapper md5;
uint32_t length;
uint8_t first_kb [1024];
memset(first_kb, 0, sizeof(first_kb));
// get hash of the running DF process
string hash = md5.getHashFromFile(real_path, length, (char *) first_kb);
my_md5 = md5.getHashFromFile(real_path, length, (char *) first_kb);
// create linux process, add it to the vector
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash);
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5);
if(vinfo)
{
my_descriptor = new VersionInfo(*vinfo);
@ -79,7 +80,7 @@ Process::Process(VersionInfoFactory * known_versions)
char * wd = getcwd(NULL, 0);
cerr << "Unable to retrieve version information.\n";
cerr << "File: " << real_path << endl;
cerr << "MD5: " << hash << endl;
cerr << "MD5: " << my_md5 << endl;
cerr << "working dir: " << wd << endl;
cerr << "length:" << length << endl;
cerr << "1KB hexdump follows:" << endl;

@ -55,15 +55,16 @@ Process::Process(VersionInfoFactory * known_versions)
identified = false;
my_descriptor = 0;
my_pe = 0;
md5wrapper md5;
uint32_t length;
uint8_t first_kb [1024];
memset(first_kb, 0, sizeof(first_kb));
// get hash of the running DF process
string hash = md5.getHashFromFile(exe_link_name, length, (char *) first_kb);
my_md5 = md5.getHashFromFile(exe_link_name, length, (char *) first_kb);
// create linux process, add it to the vector
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(hash);
VersionInfo * vinfo = known_versions->getVersionInfoByMD5(my_md5);
if(vinfo)
{
my_descriptor = new VersionInfo(*vinfo);
@ -74,7 +75,7 @@ Process::Process(VersionInfoFactory * known_versions)
char * wd = getcwd(NULL, 0);
cerr << "Unable to retrieve version information.\n";
cerr << "File: " << exe_link_name << endl;
cerr << "MD5: " << hash << endl;
cerr << "MD5: " << my_md5 << endl;
cerr << "working dir: " << wd << endl;
cerr << "length:" << length << endl;
cerr << "1KB hexdump follows:" << endl;

@ -95,7 +95,8 @@ Process::Process(VersionInfoFactory * factory)
{
return;
}
VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(d->pe_header.FileHeader.TimeDateStamp);
my_pe = d->pe_header.FileHeader.TimeDateStamp;
VersionInfo* vinfo = factory->getVersionInfoByPETimestamp(my_pe);
if(vinfo)
{
identified = true;
@ -105,8 +106,7 @@ Process::Process(VersionInfoFactory * factory)
}
else
{
fprintf(stderr, "Unable to retrieve version information.\nPE timestamp: 0x%x\n",
d->pe_header.FileHeader.TimeDateStamp);
fprintf(stderr, "Unable to retrieve version information.\nPE timestamp: 0x%x\n", my_pe);
fflush(stderr);
}
}

@ -287,6 +287,9 @@ namespace DFHack
EXEC = 4
};
uint32_t getPE() { return my_pe; }
std::string getMD5() { return my_md5; }
private:
VersionInfo * my_descriptor;
PlatformSpecific *d;
@ -294,6 +297,8 @@ namespace DFHack
uint32_t my_pid;
uint32_t base;
std::map<void *, std::string> classNameCache;
uint32_t my_pe;
std::string my_md5;
};
class DFHACK_EXPORT ClassNameCheck

@ -54,6 +54,7 @@ namespace df
namespace DFHack
{
class Core;
class Plugin;
typedef std::set<df::interface_key> interface_key_set;
@ -208,9 +209,12 @@ namespace DFHack
DFHACK_EXPORT bool findGraphicsTile(const std::string &page, int x, int y, int *ptile, int *pgs = NULL);
// Push and remove viewscreens
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL);
DFHACK_EXPORT bool show(df::viewscreen *screen, df::viewscreen *before = NULL, Plugin *p = NULL);
inline bool show(df::viewscreen *screen, Plugin *p)
{ return show(screen, NULL, p); }
DFHACK_EXPORT void dismiss(df::viewscreen *screen, bool to_first = false);
DFHACK_EXPORT bool isDismissed(df::viewscreen *screen);
DFHACK_EXPORT bool hasActiveScreens(Plugin *p);
/// Retrieve the string representation of the bound key.
DFHACK_EXPORT std::string getKeyDisplay(df::interface_key key);

@ -62,6 +62,7 @@ using namespace DFHack;
#include "df/viewscreen_layer_assigntradest.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_layer_stockpilest.h"
#include "df/viewscreen_locationsst.h"
#include "df/viewscreen_petst.h"
#include "df/viewscreen_tradegoodsst.h"
#include "df/viewscreen_storesst.h"
@ -89,6 +90,7 @@ using namespace DFHack;
#include "df/route_stockpile_link.h"
#include "df/game_mode.h"
#include "df/unit.h"
#include "df/occupation.h"
using namespace df::enums;
using df::global::gview;
@ -542,6 +544,11 @@ DEFINE_GET_FOCUS_STRING_HANDLER(layer_stockpile)
}
}
DEFINE_GET_FOCUS_STRING_HANDLER(locations)
{
focus += "/" + enum_item_key(screen->menu);
}
std::string Gui::getFocusString(df::viewscreen *top)
{
if (!top)
@ -835,6 +842,24 @@ df::unit *Gui::getAnyUnit(df::viewscreen *top)
}
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_locationsst, top))
{
switch (screen->menu)
{
case df::viewscreen_locationsst::AssignOccupation:
return vector_get(screen->units, screen->unit_idx);
case df::viewscreen_locationsst::Occupations:
{
auto occ = vector_get(screen->occupations, screen->occupation_idx);
if (occ)
return df::unit::find(occ->unit_id);
return NULL;
}
default:
return NULL;
}
}
if (VIRTUAL_CAST_VAR(screen, df::viewscreen_petst, top))
{
switch (screen->mode)

@ -282,7 +282,9 @@ bool Screen::findGraphicsTile(const std::string &pagename, int x, int y, int *pt
return false;
}
bool Screen::show(df::viewscreen *screen, df::viewscreen *before)
static std::map<df::viewscreen*, Plugin*> plugin_screens;
bool Screen::show(df::viewscreen *screen, df::viewscreen *before, Plugin *plugin)
{
CHECK_NULL_POINTER(screen);
CHECK_INVALID_ARGUMENT(!screen->parent && !screen->child);
@ -306,6 +308,9 @@ bool Screen::show(df::viewscreen *screen, df::viewscreen *before)
if (dfhack_viewscreen::is_instance(screen))
static_cast<dfhack_viewscreen*>(screen)->onShow();
if (plugin)
plugin_screens[screen] = plugin;
return true;
}
@ -313,6 +318,10 @@ void Screen::dismiss(df::viewscreen *screen, bool to_first)
{
CHECK_NULL_POINTER(screen);
auto it = plugin_screens.find(screen);
if (it != plugin_screens.end())
plugin_screens.erase(it);
if (screen->breakdown_level != interface_breakdown_types::NONE)
return;
@ -332,6 +341,21 @@ bool Screen::isDismissed(df::viewscreen *screen)
return screen->breakdown_level != interface_breakdown_types::NONE;
}
bool Screen::hasActiveScreens(Plugin *plugin)
{
if (plugin_screens.empty())
return false;
df::viewscreen *screen = &gview->view;
while (screen)
{
auto it = plugin_screens.find(screen);
if (it != plugin_screens.end() && it->second == plugin)
return true;
screen = screen->child;
}
return false;
}
#ifdef _LINUX
// Link to the libgraphics class directly:
class DFHACK_EXPORT enabler_inputst {

@ -78,7 +78,9 @@ uint32_t World::ReadCurrentYear()
uint32_t World::ReadCurrentTick()
{
return DF_GLOBAL_VALUE(cur_year_tick, 0);
// prevent this from returning anything less than 0,
// to avoid day/month calculations with 0xffffffff
return std::max(0, DF_GLOBAL_VALUE(cur_year_tick, 0));
}
bool World::ReadGameMode(t_gamemodes& rd)

@ -1 +1 @@
Subproject commit 636f7787552cc663f01561c7dc84f758cea027f1
Subproject commit d952e79b684f518b04eae5f973fa39679a2a4fa0

@ -90,6 +90,7 @@ if (BUILD_SUPPORTED)
# DFHACK_PLUGIN(advtools advtools.cpp)
DFHACK_PLUGIN(autochop autochop.cpp)
DFHACK_PLUGIN(autodump autodump.cpp)
DFHACK_PLUGIN(autogems autogems.cpp)
DFHACK_PLUGIN(autohauler autohauler.cpp)
DFHACK_PLUGIN(autolabor autolabor.cpp)
DFHACK_PLUGIN(automaterial automaterial.cpp)
@ -167,7 +168,6 @@ if (BUILD_SUPPORTED)
# DFHACK_PLUGIN(treefarm treefarm.cpp)
DFHACK_PLUGIN(tubefill tubefill.cpp)
add_subdirectory(tweak)
DFHACK_PLUGIN(weather weather.cpp)
DFHACK_PLUGIN(workflow workflow.cpp LINK_LIBRARIES lua)
DFHACK_PLUGIN(workNow workNow.cpp)
DFHACK_PLUGIN(zone zone.cpp LINK_LIBRARIES lua)

@ -600,7 +600,7 @@ struct autochop_hook : public df::viewscreen_dwarfmodest
if (isInDesignationMenu() && input->count(interface_key::CUSTOM_C))
{
sendKey(interface_key::LEAVESCREEN);
Screen::show(new ViewscreenAutochop());
Screen::show(new ViewscreenAutochop(), plugin_self);
}
else
{
@ -643,7 +643,7 @@ command_result df_autochop (color_ostream &out, vector <string> & parameters)
return CR_WRONG_USAGE;
}
if (Maps::IsValid())
Screen::show(new ViewscreenAutochop());
Screen::show(new ViewscreenAutochop(), plugin_self);
return CR_OK;
}

@ -0,0 +1,301 @@
/*
* Autogems plugin.
* Creates a new Workshop Order setting, automatically cutting rough gems.
* For best effect, include "enable autogems" in your dfhack.init configuration.
*/
#include "uicommon.h"
#include "modules/Buildings.h"
#include "modules/Gui.h"
#include "modules/Job.h"
#include "modules/World.h"
#include "df/building_workshopst.h"
#include "df/buildings_other_id.h"
#include "df/builtin_mats.h"
#include "df/general_ref_building_holderst.h"
#include "df/job.h"
#include "df/job_item.h"
#include "df/viewscreen_dwarfmodest.h"
#define CONFIG_KEY "autogems/config"
#define DELTA_TICKS 1200
#define MAX_WORKSHOP_JOBS 10
using namespace DFHack;
DFHACK_PLUGIN("autogems");
DFHACK_PLUGIN_IS_ENABLED(enabled);
REQUIRE_GLOBAL(ui);
REQUIRE_GLOBAL(world);
typedef decltype(df::item::id) item_id;
typedef decltype(df::job_item::mat_index) mat_index;
typedef std::map<mat_index, int> gem_map;
bool running = false;
decltype(world->frame_counter) last_frame_count = 0;
const char *tagline = "Creates a new Workshop Order setting, automatically cutting rough gems.";
const char *usage = (
" enable autogems\n"
" Enable the plugin.\n"
" disable autogems\n"
" Disable the plugin.\n"
"\n"
"While enabled, the Current Workshop Orders screen (o-W) have a new option:\n"
" g: Auto Cut Gems\n"
"\n"
"While this option is enabled, jobs will be created in Jeweler's Workshops\n"
"to cut any accessible rough gems.\n"
);
void add_task(mat_index gem_type, df::building_workshopst *workshop) {
// Create a single task in the specified workshop.
// Partly copied from Buildings::linkForConstruct(); perhaps a refactor is in order.
auto ref = df::allocate<df::general_ref_building_holderst>();
if (!ref) {
//Core::printerr("Could not allocate general_ref_building_holderst\n");
return;
}
ref->building_id = workshop->id;
auto item = new df::job_item();
if (!item) {
//Core::printerr("Could not allocate job_item\n");
return;
}
item->item_type = df::item_type::ROUGH;
item->mat_type = df::builtin_mats::INORGANIC;
item->mat_index = gem_type;
item->quantity = 1;
item->vector_id = df::job_item_vector_id::ROUGH;
auto job = new df::job();
if (!job) {
//Core::printerr("Could not allocate job\n");
return;
}
job->job_type = df::job_type::CutGems;
job->pos = df::coord(workshop->centerx, workshop->centery, workshop->z);
job->mat_type = df::builtin_mats::INORGANIC;
job->mat_index = gem_type;
job->general_refs.push_back(ref);
job->job_items.push_back(item);
workshop->jobs.push_back(job);
Job::linkIntoWorld(job);
}
void add_tasks(gem_map &gem_types, df::building_workshopst *workshop) {
int slots = MAX_WORKSHOP_JOBS - workshop->jobs.size();
if (slots <= 0) {
return;
}
for (auto g = gem_types.begin(); g != gem_types.end() && slots > 0; ++g) {
while (g->second > 0 && slots > 0) {
add_task(g->first, workshop);
g->second -= 1;
slots -= 1;
}
}
}
void create_jobs() {
// Creates jobs in Jeweler's Workshops as necessary.
// Todo: Consider path availability?
std::set<item_id> stockpiled;
std::set<df::building_workshopst*> unlinked;
gem_map available;
auto workshops = &world->buildings.other[df::buildings_other_id::WORKSHOP_JEWELER];
for (auto w = workshops->begin(); w != workshops->end(); ++w) {
auto workshop = virtual_cast<df::building_workshopst>(*w);
auto links = workshop->links.take_from_pile;
if (workshop->construction_stage < 3) {
// Construction in progress.
continue;
}
if (workshop->jobs.size() == 1 && workshop->jobs[0]->job_type == df::job_type::DestroyBuilding) {
// Queued for destruction.
continue;
}
if (links.size() > 0) {
for (auto l = links.begin(); l != links.end() && workshop->jobs.size() <= MAX_WORKSHOP_JOBS; ++l) {
auto stockpile = virtual_cast<df::building_stockpilest>(*l);
gem_map piled;
Buildings::StockpileIterator stored;
for (stored.begin(stockpile); !stored.done(); ++stored) {
auto item = *stored;
if (item->getType() == item_type::ROUGH && item->getMaterial() == builtin_mats::INORGANIC) {
stockpiled.insert(item->id);
piled[item->getMaterialIndex()] += 1;
}
}
// Decrement current jobs from all linked workshops, not just this one.
auto outbound = stockpile->links.give_to_workshop;
for (auto ws = outbound.begin(); ws != outbound.end(); ++ws) {
auto shop = virtual_cast<df::building_workshopst>(*ws);
for (auto j = shop->jobs.begin(); j != shop->jobs.end(); ++j) {
auto job = *j;
if (job->job_type == df::job_type::CutGems) {
if (job->flags.bits.repeat) {
piled[job->mat_index] = 0;
} else {
piled[job->mat_index] -= 1;
}
}
}
}
add_tasks(piled, workshop);
}
} else {
// Note which gem types have already been ordered to be cut.
for (auto j = workshop->jobs.begin(); j != workshop->jobs.end(); ++j) {
auto job = *j;
if (job->job_type == df::job_type::CutGems) {
available[job->mat_index] -= job->flags.bits.repeat? 100: 1;
}
}
if (workshop->jobs.size() <= MAX_WORKSHOP_JOBS) {
unlinked.insert(workshop);
}
}
}
if (unlinked.size() > 0) {
// Count how many gems of each type are available to be cut.
// Gems in stockpiles linked to specific workshops don't count.
auto gems = world->items.other[items_other_id::ROUGH];
for (auto g = gems.begin(); g != gems.end(); ++g) {
auto item = *g;
// ROUGH also includes raw glass; the INORGANIC check filters that out.
if (item->getMaterial() == builtin_mats::INORGANIC && !stockpiled.count(item->id)) {
available[item->getMaterialIndex()] += 1;
}
}
for (auto w = unlinked.begin(); w != unlinked.end(); ++w) {
add_tasks(available, *w);
}
}
}
DFhackCExport command_result plugin_onupdate(color_ostream &out) {
if (running && (world->frame_counter - last_frame_count >= DELTA_TICKS)) {
last_frame_count = world->frame_counter;
create_jobs();
}
return CR_OK;
}
/*
* Interface hooks
*/
struct autogem_hook : public df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
bool in_menu() {
// Determines whether we're looking at the Workshop Orders screen.
return ui->main.mode == ui_sidebar_mode::OrdersWorkshop;
}
bool handleInput(std::set<df::interface_key> *input) {
if (!in_menu()) {
return false;
}
if (input->count(interface_key::CUSTOM_G)) {
// Toggle whether gems are auto-cut for this fort.
auto config = World::GetPersistentData(CONFIG_KEY, NULL);
if (config.isValid()) {
config.ival(0) = running;
}
running = !running;
return true;
}
return false;
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input)) {
if (!handleInput(input)) {
INTERPOSE_NEXT(feed)(input);
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ()) {
INTERPOSE_NEXT(render)();
if (in_menu()) {
auto dims = Gui::getDwarfmodeViewDims();
int x = dims.menu_x1 + 1;
int y = dims.y1 + 12;
Screen::Pen pen = Screen::readTile(x, y);
while (pen.valid() && pen.ch != ' ') {
pen = Screen::readTile(x, ++y);
}
if (pen.valid()) {
OutputHotkeyString(x, y, (running? "Auto Cut Gems": "No Auto Cut Gems"), "g", false, x, COLOR_WHITE, COLOR_LIGHTRED);
}
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(autogem_hook, render);
DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_change_event event) {
if (event == DFHack::SC_MAP_LOADED) {
if (enabled && World::isFortressMode()) {
// Determine whether auto gem cutting has been disabled for this fort.
auto config = World::GetPersistentData(CONFIG_KEY);
running = !(config.isValid() && config.ival(0));
last_frame_count = world->frame_counter;
}
} else if (event == DFHack::SC_MAP_UNLOADED) {
running = false;
}
return CR_OK;
}
DFhackCExport command_result plugin_enable(color_ostream& out, bool enable) {
if (enable != enabled) {
if (!INTERPOSE_HOOK(autogem_hook, feed).apply(enable) || !INTERPOSE_HOOK(autogem_hook, render).apply(enable)) {
out.printerr("Could not %s autogem hooks!\n", enable? "insert": "remove");
return CR_FAILURE;
}
enabled = enable;
running = enabled && World::isFortressMode();
}
return CR_OK;
}
DFhackCExport command_result plugin_init(color_ostream &out, std::vector <PluginCommand> &commands) {
return CR_OK;
}
DFhackCExport command_result plugin_shutdown(color_ostream &out) {
return plugin_enable(out, false);
}

@ -2,6 +2,8 @@
//By cdombroski
//Translates a region of tiles specified by the cursor and arguments/prompts into a series of blueprint files suitable for digfort/buildingplan/quickfort
#include <algorithm>
#include <Console.h>
#include <PluginManager.h>

@ -161,7 +161,7 @@ struct buildingplan_hook : public df::viewscreen_dwarfmodest
}
else if (input->count(interface_key::CUSTOM_SHIFT_M))
{
Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type)));
Screen::show(new ViewscreenChooseMaterial(planner.getDefaultItemFilterForType(type)), plugin_self);
}
else if (input->count(interface_key::CUSTOM_SHIFT_Q))
{

@ -56,7 +56,7 @@ public:
df::building* getSelectedBuilding() { return Gui::getAnyBuilding(parent); }
std::string getFocusString() { return "commandprompt"; }
viewscreen_commandpromptst(std::string entry):is_response(false)
viewscreen_commandpromptst(std::string entry):is_response(false), submitted(false)
{
show_fps=gps->display_frames;
gps->display_frames=0;
@ -127,6 +127,7 @@ protected:
std::list<std::pair<color_value,std::string> > responses;
int cursor_pos;
int history_idx;
bool submitted;
bool is_response;
bool show_fps;
int frame;
@ -194,6 +195,9 @@ void viewscreen_commandpromptst::submit()
Screen::dismiss(this);
return;
}
if(submitted)
return;
submitted = true;
prompt_ostream out(this);
Core::getInstance().runCommand(out, get_entry());
if(out.empty() && responses.empty())
@ -314,7 +318,7 @@ command_result show_prompt(color_ostream &out, std::vector <std::string> & param
std::string params;
for(size_t i=0;i<parameters.size();i++)
params+=parameters[i]+" ";
Screen::show(new viewscreen_commandpromptst(params));
Screen::show(new viewscreen_commandpromptst(params), plugin_self);
return CR_OK;
}
bool hotkey_allow_all(df::viewscreen *top)
@ -332,8 +336,6 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event e)
{
if (e == SC_BEGIN_UNLOAD && Gui::getCurFocus() == "dfhack/commandprompt")
return CR_FAILURE;
return CR_OK;
}

@ -19,6 +19,7 @@
#include "df/general_ref_contained_in_itemst.h"
#include "df/viewscreen_dwarfmodest.h"
#include "df/viewscreen_layer_militaryst.h"
#include "df/viewscreen_locationsst.h"
#include "df/viewscreen_tradegoodsst.h"
using namespace DFHack;
@ -453,6 +454,7 @@ DEFINE_CONFIRMATION(squad_disband, viewscreen_layer_militaryst, 0);
DEFINE_CONFIRMATION(uniform_delete, viewscreen_layer_militaryst, 0);
DEFINE_CONFIRMATION(note_delete, viewscreen_dwarfmodest, 0);
DEFINE_CONFIRMATION(route_delete, viewscreen_dwarfmodest, 0);
DEFINE_CONFIRMATION(location_retire, viewscreen_locationsst, 0);
DFhackCExport command_result plugin_init (color_ostream &out, vector <PluginCommand> &commands)
{

@ -1057,7 +1057,7 @@ public:
{
df::unit *selected_unit = (selected_column == 1) ? dwarf_activity_column.getFirstSelectedElem() : nullptr;
Screen::dismiss(this);
Screen::show(new ViewscreenDwarfStats(selected_unit));
Screen::show(new ViewscreenDwarfStats(selected_unit), plugin_self);
}
else if (input->count(interface_key::CUSTOM_SHIFT_Z))
{
@ -1665,7 +1665,7 @@ private:
static void open_stats_srceen()
{
Screen::show(new ViewscreenFortStats());
Screen::show(new ViewscreenFortStats(), plugin_self);
}
static void add_work_history(df::unit *unit, activity_type type)
@ -1915,12 +1915,12 @@ static command_result dwarfmonitor_cmd(color_ostream &out, vector <string> & par
else if (cmd == 's' || cmd == 'S')
{
if(Maps::IsValid())
Screen::show(new ViewscreenFortStats());
Screen::show(new ViewscreenFortStats(), plugin_self);
}
else if (cmd == 'p' || cmd == 'P')
{
if(Maps::IsValid())
Screen::show(new ViewscreenPreferences());
Screen::show(new ViewscreenPreferences(), plugin_self);
}
else if (cmd == 'r' || cmd == 'R')
{

@ -21,6 +21,9 @@ using namespace DFHack;
using df::global::enabler;
using df::global::gps;
DFHACK_PLUGIN("embark-tools");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
#define FOR_ITER_TOOLS(iter) for(auto iter = tools.begin(); iter != tools.end(); iter++)
void update_embark_sidebar (df::viewscreen_choose_start_sitest * screen)
@ -684,7 +687,7 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
void display_settings()
{
Screen::show(new embark_tools_settings);
Screen::show(new embark_tools_settings, plugin_self);
}
inline bool is_valid_page()
@ -734,9 +737,6 @@ struct choose_start_site_hook : df::viewscreen_choose_start_sitest
IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(choose_start_site_hook, render);
DFHACK_PLUGIN("embark-tools");
DFHACK_PLUGIN_IS_ENABLED(is_enabled);
command_result embark_tools_cmd (color_ostream &out, std::vector <std::string> & parameters);
DFhackCExport command_result plugin_init (color_ostream &out, std::vector <PluginCommand> &commands)
@ -783,14 +783,6 @@ DFhackCExport command_result plugin_enable (color_ostream &out, bool enable)
DFhackCExport command_result plugin_onstatechange (color_ostream &out, state_change_event evt)
{
if (evt == SC_BEGIN_UNLOAD)
{
if (Gui::getCurFocus() == "dfhack/embark-tools/options")
{
out.printerr("Settings screen active.\n");
return CR_FAILURE;
}
}
return CR_OK;
}

@ -9,6 +9,7 @@
#include "modules/Translation.h"
#include "modules/World.h"
#include "df/creature_raw.h"
#include "df/map_block.h"
#include "df/unit.h"
#include "df/world.h"
@ -40,16 +41,6 @@ static std::string get_unit_description(df::unit *unit)
return desc;
}
df::unit *findUnit(int x, int y, int z)
{
for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u)
{
if ((**u).pos.x == x && (**u).pos.y == y && (**u).pos.z == z)
return *u;
}
return NULL;
}
struct uo_buf {
uint32_t dim_x, dim_y, dim_z;
size_t size;
@ -154,8 +145,21 @@ unsigned fix_unit_occupancy (color_ostream &out, uo_opts &opts)
}
}
for (auto u = world->units.active.begin(); u != world->units.active.end(); ++u)
uo_buffer.set((**u).pos.x, (**u).pos.y, (**u).pos.z, 0);
for (auto it = world->units.active.begin(); it != world->units.active.end(); ++it)
{
df::unit *u = *it;
if (!u || u->flags1.bits.caged || u->pos.x < 0)
continue;
df::creature_raw *craw = df::creature_raw::find(u->race);
int unit_extents = (craw && craw->flags.is_set(df::creature_raw_flags::EQUIPMENT_WAGON)) ? 1 : 0;
for (int16_t x = u->pos.x - unit_extents; x <= u->pos.x + unit_extents; ++x)
{
for (int16_t y = u->pos.y - unit_extents; y <= u->pos.y + unit_extents; ++y)
{
uo_buffer.set(x, y, u->pos.z, 0);
}
}
}
for (size_t i = 0; i < uo_buffer.size; i++)
{

@ -319,7 +319,7 @@ static command_result hotkeys_cmd(color_ostream &out, vector <string> & paramete
if (Gui::getFocusString(top_screen) != "dfhack/viewscreen_hotkeys")
{
find_active_keybindings(top_screen);
Screen::show(new ViewscreenHotkeys(top_screen));
Screen::show(new ViewscreenHotkeys(top_screen), plugin_self);
}
}
}

@ -35,8 +35,9 @@ public:
bool allow_search;
bool feed_mouse_set_highlight;
bool feed_changed_highlight;
T default_value;
ListColumn()
ListColumn(const T default_value_ = T())
{
bottom_margin = 3;
clear();
@ -50,6 +51,7 @@ public:
allow_search = true;
feed_mouse_set_highlight = false;
feed_changed_highlight = false;
default_value = default_value_;
}
void clear()
@ -310,7 +312,7 @@ public:
{
vector<T> results = getSelectedElems(true);
if (results.size() == 0)
return (T)nullptr;
return default_value;
else
return results[0];
}

@ -192,6 +192,17 @@ end
route_delete.title = "Delete route"
route_delete.message = "Are you sure you want to delete this route?"
location_retire = defconf('location-retire')
function location_retire.intercept_key(key)
return key == keys.LOCATION_RETIRE and
(screen.menu == df.viewscreen_locationsst.T_menu.Locations or
screen.menu == df.viewscreen_locationsst.T_menu.Occupations) and
screen.in_edit == df.viewscreen_locationsst.T_in_edit.None and
screen.locations[screen.location_idx]
end
location_retire.title = "Retire location"
location_retire.message = "Are you sure you want to retire this location?"
-- End of confirmation definitions
function check()

@ -1773,14 +1773,14 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
if (events->count(interface_key::CUSTOM_B))
{
Screen::show(new viewscreen_unitbatchopst(units, true, &do_refresh_names));
Screen::show(new viewscreen_unitbatchopst(units, true, &do_refresh_names), plugin_self);
}
if (events->count(interface_key::CUSTOM_E))
{
vector<UnitInfo*> tmp;
tmp.push_back(cur);
Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names));
Screen::show(new viewscreen_unitbatchopst(tmp, false, &do_refresh_names), plugin_self);
}
if (events->count(interface_key::CUSTOM_P))
@ -1791,11 +1791,11 @@ void viewscreen_unitlaborsst::feed(set<df::interface_key> *events)
has_selected = true;
if (has_selected) {
Screen::show(new viewscreen_unitprofessionset(units, true));
Screen::show(new viewscreen_unitprofessionset(units, true), plugin_self);
} else {
vector<UnitInfo*> tmp;
tmp.push_back(cur);
Screen::show(new viewscreen_unitprofessionset(tmp, false));
Screen::show(new viewscreen_unitprofessionset(tmp, false), plugin_self);
}
}
@ -2144,7 +2144,7 @@ struct unitlist_hook : df::viewscreen_unitlistst
{
if (units[page].size())
{
Screen::show(new viewscreen_unitlaborsst(units[page], cursor_pos[page]));
Screen::show(new viewscreen_unitlaborsst(units[page], cursor_pos[page]), plugin_self);
return;
}
}

@ -582,7 +582,7 @@ struct mousequery_hook : public df::viewscreen_dwarfmodest
{
int x = left_margin;
int y = gps->dimy - 2;
OutputToggleString(x, y, "Box Select", "Alt+M", box_designation_enabled,
OutputToggleString(x, y, "Box Select", interface_key::CUSTOM_ALT_M, box_designation_enabled,
true, left_margin, COLOR_WHITE, COLOR_LIGHTRED);
}

@ -7,6 +7,7 @@
#include "uicommon.h"
#include "df/creature_raw.h"
#include "df/ui_look_list.h"
#include "df/viewscreen_announcelistst.h"
#include "df/viewscreen_petst.h"
@ -621,7 +622,7 @@ protected:
};
// This basic match function is separated out from the generic multi column class, because the
// pets screen, which uses a union in its primary list, will cause a compile failure is this
// pets screen, which uses a union in its primary list, will cause a compile failure if this
// match function exists in the generic class
template < class S, class T, class PARENT = search_generic<S,T> >
class search_multicolumn_modifiable : public search_multicolumn_modifiable_generic<S, T, PARENT>
@ -751,7 +752,7 @@ template <class T, class V, int D> V generic_search_hook<T, V, D> ::module;
typedef generic_search_hook<screen, module> module##_hook; \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, feed, prio); \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, render, prio); \
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, key_conflict, prio)
template<> IMPLEMENT_VMETHOD_INTERPOSE_PRIO(module##_hook, key_conflict, prio);
//
// END: Generic Search functionality
@ -761,19 +762,24 @@ template <class T, class V, int D> V generic_search_hook<T, V, D> ::module;
//
// START: Animal screen search
//
typedef df::viewscreen_petst::T_animal T_animal;
typedef df::viewscreen_petst::T_mode T_mode;
class pets_search : public search_multicolumn_modifiable_generic<df::viewscreen_petst, T_animal>
typedef search_multicolumn_modifiable_generic<df::viewscreen_petst, df::viewscreen_petst::T_animal> pets_search_base;
class pets_search : public pets_search_base
{
typedef df::viewscreen_petst::T_animal T_animal;
typedef df::viewscreen_petst::T_mode T_mode;
public:
void render() const
{
if (viewscreen->mode == T_mode::List)
print_search_option(25, 4);
print_search_option(25, 4);
}
private:
bool can_init(df::viewscreen_petst *screen)
{
return pets_search_base::can_init(screen) && screen->mode == T_mode::List;
}
int32_t *get_viewscreen_cursor()
{
return &viewscreen->cursor;
@ -876,13 +882,130 @@ private:
std::vector<char > *is_adopting, is_adopting_s;
};
IMPLEMENT_HOOKS(df::viewscreen_petst, pets_search);
IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, pets_search, 1, 0);
//
// END: Animal screen search
//
//
// START: Animal knowledge screen search
//
typedef search_generic<df::viewscreen_petst, int32_t> animal_knowledge_search_base;
class animal_knowledge_search : public animal_knowledge_search_base
{
typedef df::viewscreen_petst::T_mode T_mode;
bool can_init(df::viewscreen_petst *screen)
{
return animal_knowledge_search_base::can_init(screen) && screen->mode == T_mode::TrainingKnowledge;
}
public:
void render() const
{
print_search_option(2, 4);
}
private:
int32_t *get_viewscreen_cursor()
{
return NULL;
}
vector<int32_t> *get_primary_list()
{
return &viewscreen->known;
}
string get_element_description(int32_t id) const
{
auto craw = df::creature_raw::find(id);
string out;
if (craw)
{
for (size_t i = 0; i < 3; ++i)
out += craw->name[i] + " ";
}
return out;
}
};
IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, animal_knowledge_search, 2, 0);
//
// END: Animal knowledge screen search
//
//
// START: Animal trainer search
//
typedef search_twocolumn_modifiable<df::viewscreen_petst, df::unit*, df::viewscreen_petst::T_trainer_mode> animal_trainer_search_base;
class animal_trainer_search : public animal_trainer_search_base
{
typedef df::viewscreen_petst::T_mode T_mode;
typedef df::viewscreen_petst::T_trainer_mode T_trainer_mode;
bool can_init(df::viewscreen_petst *screen)
{
return animal_trainer_search_base::can_init(screen) && screen->mode == T_mode::SelectTrainer;
}
public:
void render() const
{
Screen::paintTile(Screen::Pen(186, 8, 0), 14, 2);
Screen::paintTile(Screen::Pen(186, 8, 0), gps->dimx - 14, 2);
Screen::paintTile(Screen::Pen(201, 8, 0), 14, 1);
Screen::paintTile(Screen::Pen(187, 8, 0), gps->dimx - 14, 1);
for (int x = 15; x <= gps->dimx - 15; ++x)
{
Screen::paintTile(Screen::Pen(205, 8, 0), x, 1);
Screen::paintTile(Screen::Pen(0, 0, 0), x, 2);
}
print_search_option(16, 2);
}
private:
int32_t *get_viewscreen_cursor()
{
return &viewscreen->trainer_cursor;
}
vector<df::unit*> *get_primary_list()
{
return &viewscreen->trainer_unit;
}
string get_element_description(df::unit *u) const
{
return get_unit_description(u);
}
std::vector<T_trainer_mode> *get_secondary_list()
{
return &viewscreen->trainer_mode;
}
public:
bool process_input(set<df::interface_key> *input)
{
if (input->count(interface_key::SELECT) && viewscreen->trainer_unit.empty() && !in_entry_mode())
return true;
return animal_trainer_search_base::process_input(input);
}
};
IMPLEMENT_HOOKS_WITH_ID(df::viewscreen_petst, animal_trainer_search, 3, 0);
//
// END: Animal trainer search
//
//
// START: Stocks screen search
@ -1997,6 +2120,8 @@ IMPLEMENT_HOOKS(df::viewscreen_locationsst, location_assign_occupation_search);
HOOK_ACTION(trade_search_fort_hook) \
HOOK_ACTION(stocks_search_hook) \
HOOK_ACTION(pets_search_hook) \
HOOK_ACTION(animal_knowledge_search_hook) \
HOOK_ACTION(animal_trainer_search_hook) \
HOOK_ACTION(military_search_hook) \
HOOK_ACTION(nobles_search_hook) \
HOOK_ACTION(profiles_search_hook) \

@ -69,7 +69,7 @@ command_result df_showmood (color_ostream &out, vector <string> & parameters)
out.printerr("Dwarf with strange mood does not have a mood type!\n");
continue;
}
out.print("%s is currently ", Translation::TranslateName(&unit->name, false).c_str());
out.print("%s is currently ", DF2CONSOLE(Translation::TranslateName(&unit->name, false)).c_str());
switch (unit->mood)
{
case mood_type::Macabre:

@ -771,7 +771,7 @@ public:
}
else if (input->count(interface_key::HELP))
{
Screen::show(new search_help);
Screen::show(new search_help, plugin_self);
}
bool key_processed = false;
@ -1425,7 +1425,7 @@ struct stocks_hook : public df::viewscreen_storesst
if (input->count(interface_key::CUSTOM_E))
{
Screen::dismiss(this);
Screen::show(new ViewscreenStocks());
Screen::show(new ViewscreenStocks(), plugin_self);
return;
}
INTERPOSE_NEXT(feed)(input);
@ -1457,7 +1457,7 @@ struct stocks_stockpile_hook : public df::viewscreen_dwarfmodest
if (input->count(interface_key::CUSTOM_I))
{
Screen::show(new ViewscreenStocks(sp));
Screen::show(new ViewscreenStocks(sp), plugin_self);
return true;
}
@ -1531,7 +1531,7 @@ static command_result stocks_cmd(color_ostream &out, vector <string> & parameter
}
else if (toLower(parameters[0])[0] == 's')
{
Screen::show(new ViewscreenStocks());
Screen::show(new ViewscreenStocks(), plugin_self);
return CR_OK;
}
}
@ -1557,10 +1557,6 @@ DFhackCExport command_result plugin_onstatechange(color_ostream &out, state_chan
case SC_MAP_LOADED:
ViewscreenStocks::reset();
break;
case SC_BEGIN_UNLOAD:
if (Gui::getCurFocus().find("dfhack/stocks") == 0)
return CR_FAILURE;
break;
default:
break;
}

@ -34,7 +34,12 @@ struct title_version_hook : df::viewscreen_titlest {
int x = 0, y = 0;
OutputString(COLOR_WHITE, x, y, string("DFHack ") + DFHACK_VERSION);
if (!DFHACK_IS_RELEASE)
{
OutputString(COLOR_WHITE, x, y, " (dev)");
x = 0; y = 1;
OutputString(COLOR_WHITE, x, y, "Git: ");
OutputString(COLOR_WHITE, x, y, DFHACK_GIT_DESCRIPTION);
}
}
};

@ -14,6 +14,7 @@
#include "modules/Materials.h"
#include "modules/MapCache.h"
#include "modules/Buildings.h"
#include "modules/Filesystem.h"
#include "MiscUtils.h"
@ -78,6 +79,7 @@
#include "tweaks/adamantine-cloth-wear.h"
#include "tweaks/advmode-contained.h"
#include "tweaks/block-labors.h"
#include "tweaks/civ-agreement-ui.h"
#include "tweaks/craft-age-wear.h"
#include "tweaks/eggs-fertile.h"
@ -86,6 +88,7 @@
#include "tweaks/fast-heat.h"
#include "tweaks/fast-trade.h"
#include "tweaks/fps-min.h"
#include "tweaks/hide-priority.h"
#include "tweaks/import-priority-category.h"
#include "tweaks/kitchen-keys.h"
#include "tweaks/kitchen-prefs-color.h"
@ -96,6 +99,7 @@
#include "tweaks/nestbox-color.h"
#include "tweaks/shift-8-scroll.h"
#include "tweaks/stable-cursor.h"
#include "tweaks/title-start-rename.h"
#include "tweaks/tradereq-pet-gender.h"
using std::set;
@ -114,7 +118,9 @@ REQUIRE_GLOBAL(ui_area_map_width);
REQUIRE_GLOBAL(ui_build_selector);
REQUIRE_GLOBAL(ui_building_item_cursor);
REQUIRE_GLOBAL(ui_menu_width);
REQUIRE_GLOBAL(ui_look_cursor);
REQUIRE_GLOBAL(ui_sidebar_menus);
REQUIRE_GLOBAL(ui_unit_view_mode);
REQUIRE_GLOBAL(ui_workshop_in_add);
REQUIRE_GLOBAL(world);
@ -174,6 +180,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" Fixes custom reactions with container inputs in advmode. The issue is\n"
" that the screen tries to force you to select the contents separately\n"
" from the container. This forcefully skips child reagents.\n"
" tweak block-labors [disable]\n"
" Prevents labors that can't be used from being toggled.\n"
" tweak civ-view-agreement\n"
" Fixes overlapping text on the \"view agreement\" screen\n"
" tweak craft-age-wear [disable]\n"
@ -193,6 +201,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" the current item (fully, in case of a stack), and scroll down one line.\n"
" tweak fps-min [disable]\n"
" Fixes the in-game minimum FPS setting (bug 6277)\n"
" tweak hide-priority [disable]\n"
" Adds an option to hide designation priority indicators\n"
" tweak import-priority-category [disable]\n"
" When meeting with a liaison, makes Shift+Left/Right arrow adjust\n"
" the priority of an entire category of imports.\n"
@ -218,6 +228,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" tweak shift-8-scroll [disable]\n"
" Gives Shift+8 (or *) priority when scrolling menus, instead of \n"
" scrolling the map\n"
" tweak title-start-rename [disable]\n"
" Adds a safe rename option to the title screen \"Start Playing\" menu\n"
" tweak tradereq-pet-gender [disable]\n"
" Displays the gender of pets in the trade request list\n"
// " tweak military-training [disable]\n"
@ -232,6 +244,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_HOOK("advmode-contained", advmode_contained_hook, feed);
TWEAK_HOOK("block-labors", block_labors_hook, feed);
TWEAK_HOOK("block-labors", block_labors_hook, render);
TWEAK_HOOK("civ-view-agreement", civ_agreement_view_hook, render);
TWEAK_HOOK("craft-age-wear", craft_age_wear_hook, ageItem);
@ -252,6 +267,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_ONUPDATE_HOOK("fps-min", fps_min_hook);
TWEAK_HOOK("hide-priority", hide_priority_hook, feed);
TWEAK_HOOK("hide-priority", hide_priority_hook, render);
TWEAK_HOOK("import-priority-category", takerequest_hook, feed);
TWEAK_HOOK("import-priority-category", takerequest_hook, render);
@ -277,6 +295,9 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
TWEAK_HOOK("stable-cursor", stable_cursor_hook, feed);
TWEAK_HOOK("title-start-rename", title_start_rename_hook, feed);
TWEAK_HOOK("title-start-rename", title_start_rename_hook, render);
TWEAK_HOOK("tradereq-pet-gender", pet_gender_hook, render);
return CR_OK;

@ -0,0 +1,104 @@
#include "modules/Gui.h"
#include "modules/Screen.h"
#include "modules/Units.h"
#include "df/ui_unit_view_mode.h"
#include "df/unit_labor.h"
#include "df/viewscreen_dwarfmodest.h"
using namespace DFHack;
using df::global::ui;
using df::global::ui_look_cursor;
using df::global::ui_unit_view_mode;
struct block_labors_hook : df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
inline bool valid_mode()
{
return ui->main.mode == df::ui_sidebar_mode::ViewUnits &&
ui_unit_view_mode->value == df::ui_unit_view_mode::T_value::PrefLabor;
}
inline bool forbidden_labor (df::unit *unit, df::unit_labor labor)
{
return is_valid_enum_item(labor) && !Units::isValidLabor(unit, labor);
}
inline bool all_labors_enabled (df::unit *unit, df::unit_labor_category cat)
{
FOR_ENUM_ITEMS(unit_labor, labor)
{
if (ENUM_ATTR(unit_labor, category, labor) == cat &&
!unit->status.labors[labor] &&
!forbidden_labor(unit, labor))
return false;
}
return true;
}
inline void recolor_line (int x1, int x2, int y, UIColor color)
{
for (int x = x1; x <= x2; x++)
{
auto tile = Screen::readTile(x, y);
tile.fg = color;
tile.bold = false;
Screen::paintTile(tile, x, y);
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
auto dims = Gui::getDwarfmodeViewDims();
if (valid_mode())
{
df::unit *unit = Gui::getAnyUnit(this);
for (int y = 5, i = (*ui_look_cursor/13)*13;
y <= 17 && i < unit_labors_sidemenu.size();
++y, ++i)
{
df::unit_labor labor = unit_labors_sidemenu[i];
df::unit_labor_category cat = df::unit_labor_category(labor);
if (is_valid_enum_item(cat) && all_labors_enabled(unit, cat))
recolor_line(dims.menu_x1, dims.menu_x2, y, COLOR_WHITE);
if (forbidden_labor(unit, labor))
recolor_line(dims.menu_x1, dims.menu_x2, y, COLOR_RED +
(unit->status.labors[labor] ? 8 : 0));
}
}
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
using namespace df::enums::interface_key;
if (valid_mode())
{
df::unit *unit = Gui::getAnyUnit(this);
df::unit_labor labor = unit_labors_sidemenu[*ui_look_cursor];
df::unit_labor_category cat = df::unit_labor_category(labor);
if ((input->count(SELECT) || input->count(SELECT_ALL)) && forbidden_labor(unit, labor))
{
unit->status.labors[labor] = false;
return;
}
else if (input->count(SELECT_ALL) && is_valid_enum_item(cat))
{
bool new_state = !all_labors_enabled(unit, cat);
FOR_ENUM_ITEMS(unit_labor, labor)
{
if (ENUM_ATTR(unit_labor, category, labor) == cat)
unit->status.labors[labor] = (new_state && !forbidden_labor(unit, labor));
}
return;
}
}
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(block_labors_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(block_labors_hook, render);

@ -0,0 +1,61 @@
#include "modules/Gui.h"
#include "df/viewscreen_dwarfmodest.h"
using namespace DFHack;
using df::global::gps;
using df::global::ui_sidebar_menus;
struct hide_priority_hook : df::viewscreen_dwarfmodest {
typedef df::viewscreen_dwarfmodest interpose_base;
inline bool valid_mode ()
{
switch (ui->main.mode)
{
case df::ui_sidebar_mode::DesignateMine:
case df::ui_sidebar_mode::DesignateRemoveRamps:
case df::ui_sidebar_mode::DesignateUpStair:
case df::ui_sidebar_mode::DesignateDownStair:
case df::ui_sidebar_mode::DesignateUpDownStair:
case df::ui_sidebar_mode::DesignateUpRamp:
case df::ui_sidebar_mode::DesignateChannel:
case df::ui_sidebar_mode::DesignateGatherPlants:
case df::ui_sidebar_mode::DesignateRemoveDesignation:
case df::ui_sidebar_mode::DesignateSmooth:
case df::ui_sidebar_mode::DesignateCarveTrack:
case df::ui_sidebar_mode::DesignateEngrave:
case df::ui_sidebar_mode::DesignateCarveFortification:
case df::ui_sidebar_mode::DesignateChopTrees:
case df::ui_sidebar_mode::DesignateToggleEngravings:
case df::ui_sidebar_mode::DesignateToggleMarker:
case df::ui_sidebar_mode::DesignateRemoveConstruction:
return true;
default:
return false;
}
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (valid_mode())
{
auto dims = Gui::getDwarfmodeViewDims();
if (dims.menu_on)
{
int x = dims.menu_x1 + 1, y = gps->dimy - (gps->dimy > 26 ? 8 : 7);
OutputToggleString(x, y, "Show priorities", df::interface_key::CUSTOM_ALT_P,
ui_sidebar_menus->designation.priority_set, true, 0,
COLOR_WHITE, COLOR_LIGHTRED);
}
}
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
if (valid_mode() && input->count(df::interface_key::CUSTOM_ALT_P))
ui_sidebar_menus->designation.priority_set = !ui_sidebar_menus->designation.priority_set;
else
INTERPOSE_NEXT(feed)(input);
}
};
IMPLEMENT_VMETHOD_INTERPOSE(hide_priority_hook, feed);
IMPLEMENT_VMETHOD_INTERPOSE(hide_priority_hook, render);

@ -0,0 +1,112 @@
#include "df/viewscreen_titlest.h"
using namespace DFHack;
struct title_start_rename_hook : df::viewscreen_titlest {
typedef df::viewscreen_titlest interpose_base;
typedef interpose_base::T_sel_subpage T_sel_subpage;
static T_sel_subpage last_subpage;
static bool in_rename;
static bool rename_failed;
static std::string entry;
inline df::viewscreen_titlest::T_start_savegames *get_cur_save()
{
return vector_get(start_savegames, sel_submenu_line);
}
inline std::string full_save_dir(const std::string &region_name)
{
return std::string("data/save/") + region_name;
}
bool do_rename()
{
auto save = get_cur_save();
if (!save)
return false;
if (Filesystem::isdir(full_save_dir(entry)))
return false;
if (rename(full_save_dir(save->save_dir).c_str(), full_save_dir(entry).c_str()) != 0)
return false;
save->save_dir = entry;
entry = "";
return true;
}
DEFINE_VMETHOD_INTERPOSE(void, render, ())
{
INTERPOSE_NEXT(render)();
if (sel_subpage == T_sel_subpage::StartSelectWorld || sel_subpage == T_sel_subpage::StartSelectMode)
{
auto save = get_cur_save();
if (save)
{
int x = 1, y = 7;
OutputHotkeyString(x, y,
in_rename ? entry.c_str() : "Rename",
df::interface_key::CUSTOM_R,
false, 0,
rename_failed ? COLOR_LIGHTRED : COLOR_WHITE,
in_rename ? COLOR_RED : COLOR_LIGHTRED);
if (in_rename)
OutputString(COLOR_LIGHTGREEN, x, y, "_");
}
}
}
DEFINE_VMETHOD_INTERPOSE(void, feed, (std::set<df::interface_key> *input))
{
using namespace df::enums::interface_key;
if (in_rename)
{
rename_failed = false;
auto string_key = get_string_key(input);
if (input->count(SELECT) && !entry.empty())
{
if (do_rename())
in_rename = false;
else
rename_failed = true;
}
else if (input->count(STRING_A000))
{
if (!entry.empty())
entry.erase(entry.size() - 1);
}
else if (string_key != NONE)
{
entry += Screen::keyToChar(string_key);
}
else if (input->count(LEAVESCREEN) || (input->count(SELECT) && entry.empty()) ||
input->count(STANDARDSCROLL_UP) || input->count(STANDARDSCROLL_DOWN))
{
entry = "";
in_rename = false;
std::set<df::interface_key> tmp;
if (input->count(STANDARDSCROLL_UP))
tmp.insert(STANDARDSCROLL_UP);
if (input->count(STANDARDSCROLL_DOWN))
tmp.insert(STANDARDSCROLL_DOWN);
INTERPOSE_NEXT(feed)(&tmp);
}
}
else if (input->count(CUSTOM_R))
{
in_rename = true;
}
else
{
INTERPOSE_NEXT(feed)(input);
}
}
};
IMPLEMENT_VMETHOD_INTERPOSE(title_start_rename_hook, render);
IMPLEMENT_VMETHOD_INTERPOSE(title_start_rename_hook, feed);
df::viewscreen_titlest::T_sel_subpage title_start_rename_hook::last_subpage =
df::viewscreen_titlest::T_sel_subpage::None;
bool title_start_rename_hook::in_rename = false;
bool title_start_rename_hook::rename_failed = false;
std::string title_start_rename_hook::entry;

@ -1,147 +0,0 @@
#include "Core.h"
#include "Console.h"
#include "Export.h"
#include "PluginManager.h"
#include <vector>
#include <string>
#include "modules/World.h"
#include "DataDefs.h"
#include "df/weather_type.h"
using std::vector;
using std::string;
using namespace DFHack;
using namespace df::enums;
DFHACK_PLUGIN("weather");
REQUIRE_GLOBAL(current_weather);
bool locked = false;
unsigned char locked_data[25];
command_result weather (color_ostream &out, vector <string> & parameters);
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
{
commands.push_back(PluginCommand(
"weather", "Print the weather map or change weather.",
weather, false,
" Prints the current weather map by default.\n"
"Options:\n"
" snow - make it snow everywhere.\n"
" rain - make it rain.\n"
" clear - clear the sky.\n"
));
return CR_OK;
}
DFhackCExport command_result plugin_shutdown ( color_ostream &out )
{
return CR_OK;
}
command_result weather (color_ostream &con, vector <string> & parameters)
{
int val_override = -1;
bool lock = false;
bool unlock = false;
bool snow = false;
bool rain = false;
bool clear = false;
for(size_t i = 0; i < parameters.size();i++)
{
if(parameters[i] == "rain")
rain = true;
else if(parameters[i] == "snow")
snow = true;
else if(parameters[i] == "clear")
clear = true;
else if(parameters[i] == "lock")
lock = true;
else if(parameters[i] == "unlock")
unlock = true;
else
{
val_override = atoi(parameters[i].c_str());
if(val_override == 0)
return CR_WRONG_USAGE;
}
}
if(lock && unlock)
{
con << "Lock or unlock? DECIDE!" << std::endl;
return CR_FAILURE;
}
int cnt = 0;
cnt += rain;
cnt += snow;
cnt += clear;
if(cnt > 1)
{
con << "Rain, snow or clear sky? DECIDE!" << std::endl;
return CR_FAILURE;
}
bool something = lock || unlock || rain || snow || clear || val_override != -1;
CoreSuspender suspend;
if(!current_weather)
{
con << "Weather support seems broken :(" << std::endl;
return CR_FAILURE;
}
if(!something)
{
// paint weather map
con << "Weather map (C = clear, R = rain, S = snow):" << std::endl;
for(int y = 0; y<5;y++)
{
for(int x = 0; x<5;x++)
{
switch((*current_weather)[x][y])
{
case weather_type::None:
con << "C ";
break;
case weather_type::Rain:
con << "R ";
break;
case weather_type::Snow:
con << "S ";
break;
default:
con << (int) (*current_weather)[x][y] << " ";
break;
}
}
con << std::endl;
}
}
else
{
// weather changing action!
if(rain)
{
con << "Here comes the rain." << std::endl;
World::SetCurrentWeather(weather_type::Rain);
}
if(snow)
{
con << "Snow everywhere!" << std::endl;
World::SetCurrentWeather(weather_type::Snow);
}
if(clear)
{
con << "Suddenly, sunny weather!" << std::endl;
World::SetCurrentWeather(weather_type::None);
}
if(val_override != -1)
{
con << "I have no damn idea what this is... " << val_override << std::endl;
World::SetCurrentWeather(val_override);
}
// FIXME: weather lock needs map ID to work reliably... needs to be implemented.
}
return CR_OK;
}

@ -1 +1 @@
Subproject commit 01b594027a84c6b732f7639870538f138483a78c
Subproject commit 26c60013223e02b5558e31bed8e0625495430995

@ -14,7 +14,6 @@ local artisan_labors = {
"ARCHITECT",
"ANIMALTRAIN",
"LEATHER",
"BREWER",
"WEAVER",
"CLOTHESMAKER",
"COOK",

@ -1,10 +1,8 @@
-- Exports an ini file for Dwarf Therapist.
--[[=begin
devel/export-dt-ini
===================
Exports an ini file containing memory addresses for Dwarf Therapist.
=end]]
local utils = require 'utils'
@ -66,22 +64,39 @@ end
-- List of actual values
header('addresses')
address('translation_vector',globals,'world','raws','language','translations')
address('language_vector',globals,'world','raws','language','words')
address('creature_vector',globals,'world','units','all')
address('active_creature_vector',globals,'world','units','active')
address('dwarf_race_index',globals,'ui','race_id')
address('squad_vector',globals,'world','squads','all')
address('current_year',globals,'cur_year')
address('cur_year_tick',globals,'cur_year_tick')
address('current_year',globals,'cur_year')
address('dwarf_civ_index',globals,'ui','civ_id')
address('races_vector',globals,'world','raws','creatures','all')
address('reactions_vector',globals,'world','raws','reactions')
address('events_vector',globals,'world','history','events')
address('historical_figures_vector',globals,'world','history','figures')
address('fake_identities_vector',globals,'world','identities','all')
address('dwarf_race_index',globals,'ui','race_id')
address('fortress_entity',globals,'ui','main','fortress_entity')
address('historical_entities_vector',globals,'world','entities','all')
address('creature_vector',globals,'world','units','all')
address('active_creature_vector',globals,'world','units','active')
address('weapons_vector',globals,'world','items','other','WEAPON')
address('shields_vector',globals,'world','items','other', 'SHIELD')
address('quivers_vector',globals,'world','items','other', 'QUIVER')
address('crutches_vector',globals,'world','items','other', 'CRUTCH')
address('backpacks_vector',globals,'world','items','other', 'BACKPACK')
address('ammo_vector',globals,'world','items','other', 'AMMO')
address('flasks_vector',globals,'world','items','other', 'FLASK')
address('pants_vector',globals,'world','items','other', 'PANTS')
address('armor_vector',globals,'world','items','other', 'ARMOR')
address('shoes_vector',globals,'world','items','other', 'SHOES')
address('helms_vector',globals,'world','items','other', 'HELM')
address('gloves_vector',globals,'world','items','other', 'GLOVES')
address('artifacts_vector',globals,'world','artifacts','all')
address('squad_vector',globals,'world','squads','all')
address('activities_vector',globals,'world','activities','all')
address('fake_identities_vector',globals,'world','identities','all')
address('poetic_forms_vector',globals,'world','poetic_forms','all')
address('musical_forms_vector',globals,'world','musical_forms','all')
address('dance_forms_vector',globals,'world','dance_forms','all')
address('occupations_vector',globals,'world','occupations','all')
address('world_data',globals,'world','world_data')
address('material_templates_vector',globals,'world','raws','material_templates')
address('inorganics_vector',globals,'world','raws','inorganics')
address('plants_vector',globals,'world','raws','plants','all')
address('races_vector',globals,'world','raws','creatures','all')
address('itemdef_weapons_vector',globals,'world','raws','itemdefs','weapons')
address('itemdef_trap_vector',globals,'world','raws','itemdefs','trapcomps')
address('itemdef_toy_vector',globals,'world','raws','itemdefs','toys')
@ -96,29 +111,17 @@ address('itemdef_shield_vector',globals,'world','raws','itemdefs','shields')
address('itemdef_helm_vector',globals,'world','raws','itemdefs','helms')
address('itemdef_pant_vector',globals,'world','raws','itemdefs','pants')
address('itemdef_food_vector',globals,'world','raws','itemdefs','food')
address('language_vector',globals,'world','raws','language','words')
address('translation_vector',globals,'world','raws','language','translations')
address('colors_vector',globals,'world','raws','language','colors')
address('shapes_vector',globals,'world','raws','language','shapes')
address('reactions_vector',globals,'world','raws','reactions')
address('base_materials',globals,'world','raws','mat_table','builtin')
address('inorganics_vector',globals,'world','raws','inorganics')
address('plants_vector',globals,'world','raws','plants','all')
address('material_templates_vector',globals,'world','raws','material_templates')
address('all_syndromes_vector',globals,'world','raws','syndromes','all')
address('world_data',globals,'world','world_data')
address('active_sites_vector',df.world_data,'active_site')
address('events_vector',globals,'world','history','events')
address('historical_figures_vector',globals,'world','history','figures')
address('world_site_type',df.world_site,'type')
address('weapons_vector',globals,'world','items','other','WEAPON')
address('shields_vector',globals,'world','items','other', 'SHIELD')
address('quivers_vector',globals,'world','items','other', 'QUIVER')
address('crutches_vector',globals,'world','items','other', 'CRUTCH')
address('backpacks_vector',globals,'world','items','other', 'BACKPACK')
address('ammo_vector',globals,'world','items','other', 'AMMO')
address('flasks_vector',globals,'world','items','other', 'FLASK')
address('pants_vector',globals,'world','items','other', 'PANTS')
address('armor_vector',globals,'world','items','other', 'ARMOR')
address('shoes_vector',globals,'world','items','other', 'SHOES')
address('helms_vector',globals,'world','items','other', 'HELM')
address('gloves_vector',globals,'world','items','other', 'GLOVES')
address('artifacts_vector',globals,'world','artifacts','all')
address('active_sites_vector',df.world_data,'active_site')
header('offsets')
address('word_table',df.language_translation,'words')
@ -220,6 +223,7 @@ address('stack_size',df.item_actual,'stack_size')
address('wear',df.item_actual,'wear')
address('mat_type',df.item_crafted,'mat_type')
address('mat_index',df.item_crafted,'mat_index')
address('maker_race',df.item_crafted,'maker_race')
address('quality',df.item_crafted,'quality')
header('item_subtype_offsets')
@ -400,9 +404,20 @@ address('id',df.squad,'id')
address('name',df.squad,'name')
address('alias',df.squad,'alias')
address('members',df.squad,'positions')
address('orders',df.squad,'orders')
address('schedules',df.squad,'schedule')
if os_type ~= 'windows' then --squad_schedule_entry size
value('sched_size',0x20)
else
value('sched_size',0x40)
end
address('sched_orders',df.squad_schedule_entry,'orders')
address('sched_assign',df.squad_schedule_entry,'order_assignments')
address('alert',df.squad,'cur_alert_idx')
address('carry_food',df.squad,'carry_food')
address('carry_water',df.squad,'carry_water')
address('ammunition',df.squad,'ammunition')
address('ammunition_qty',df.squad_ammo_spec,'amount')
address('quiver',df.squad_position,'quiver')
address('backpack',df.squad_position,'backpack')
address('flask',df.squad_position,'flask')
@ -416,13 +431,33 @@ address('weapon_vector',df.squad_position,'uniform','weapon')
address('uniform_item_filter',df.squad_uniform_spec,'item_filter')
address('uniform_indiv_choice',df.squad_uniform_spec,'indiv_choice')
header('activity_offsets')
address('activity_type',df.activity_entry,'id')
address('events',df.activity_entry,'events')
address('participants',df.activity_event_combat_trainingst,'participants')
address('sq_lead',df.activity_event_skill_demonstrationst,'hist_figure_id')
address('sq_skill',df.activity_event_skill_demonstrationst,'skill')
address('sq_train_rounds',df.activity_event_skill_demonstrationst,'train_rounds')
address('pray_deity',df.activity_event_prayerst,'histfig_id')
address('pray_sphere',df.activity_event_prayerst,'topic')
address('knowledge_category',df.activity_event_ponder_topicst,'knowledge_category')
address('knowledge_flag',df.activity_event_ponder_topicst,'knowledge_flag')
address('perf_type',df.activity_event_performancest,'type')
address('perf_participants',df.activity_event_performancest,'participant_actions')
address('perf_histfig',df.activity_event_performancest.T_participant_actions,'histfig_id')
-- Final creation of the file
local out = io.open('therapist.ini', 'w')
out:write('[info]\n')
-- TODO: add an api function to retrieve the checksum
out:write('checksum=<<fillme>>\n')
if dfhack.getOSType() == 'windows' and dfhack.internal.getPE then
out:write(('checksum=0x%x\n'):format(dfhack.internal.getPE()))
elseif dfhack.getOSType() ~= 'windows' and dfhack.internal.getMD5 then
out:write(('checksum=0x%s\n'):format(dfhack.internal.getMD5():sub(1, 8)))
else
out:write('checksum=<<fillme>>\n')
end
out:write('version_name='..dfhack.getDFVersion()..'\n')
out:write('complete='..(complete and 'true' or 'false')..'\n')

@ -260,17 +260,14 @@ local function dwarfmode_to_top()
end
local function feed_menu_choice(catnames,catkeys,enum,enter_seq,exit_seq,prompt)
local entered = false
return function (idx)
if idx == 0 and prompt and not utils.prompt_yes_no(' Proceed?', true) then
return false
end
idx = idx % #catnames + 1
if not entered then
entered = true
else
if idx > 0 then
dwarfmode_feed_input(table.unpack(exit_seq or {}))
end
idx = idx % #catnames + 1
dwarfmode_feed_input(table.unpack(enter_seq or {}))
dwarfmode_feed_input(catkeys[idx])
if enum then
@ -465,6 +462,22 @@ end
-- enabler
--
local function lookup_colors()
local f = io.open('data/init/colors.txt', 'r') or error('failed to open file')
local text = f:read('*all')
f:close()
local colors = {}
for _, color in pairs({'BLACK', 'BLUE', 'GREEN', 'CYAN', 'RED', 'MAGENTA',
'BROWN', 'LGRAY', 'DGRAY', 'LBLUE', 'LGREEN', 'LCYAN', 'LRED',
'LMAGENTA', 'YELLOW', 'WHITE'}) do
for _, part in pairs({'R', 'G', 'B'}) do
local opt = color .. '_' .. part
table.insert(colors, tonumber(text:match(opt .. ':(%d+)') or error('missing from colors.txt: ' .. opt)))
end
end
return colors
end
local function is_valid_enabler(e)
if not ms.is_valid_vector(e.textures.raws, 4)
or not ms.is_valid_vector(e.text_system, 4)
@ -478,7 +491,7 @@ end
local function find_enabler()
-- Data from data/init/colors.txt
local colors = {
local default_colors = {
0, 0, 0, 0, 0, 128, 0, 128, 0,
0, 128, 128, 128, 0, 0, 128, 0, 128,
128, 128, 0, 192, 192, 192, 128, 128, 128,
@ -486,10 +499,21 @@ local function find_enabler()
255, 0, 0, 255, 0, 255, 255, 255, 0,
255, 255, 255
}
local colors
local ok, ret = pcall(lookup_colors)
if not ok then
dfhack.printerr('Failed to look up colors, using defaults: \n' .. ret)
colors = default_colors
else
colors = ret
end
for i = 1,#colors do colors[i] = colors[i]/255 end
local idx, addr = data.float:find_one(colors)
if not idx then
idx, addr = data.float:find_one(default_colors)
end
if idx then
validate_offset('enabler', is_valid_enabler, addr, df.enabler, 'ccolor')
return
@ -530,6 +554,9 @@ local function find_gps()
local w,h = ms.get_screen_size()
local idx, addr = zone.area.int32_t:find_one{w, h, -1, -1}
if not idx then
idx, addr = data.int32_t.find_one{w, h, -1, -1}
end
if idx then
validate_offset('gps', is_valid_gps, addr, df.graphic, 'dimx')
return
@ -1581,6 +1608,53 @@ Searching for pause_state. Please do as instructed below:]],
ms.found_offset('pause_state', addr)
end
--
-- standing orders
--
local function find_standing_orders(gname, seq, depends)
if type(seq) ~= 'table' then seq = {seq} end
for k, v in pairs(depends) do
if not dfhack.internal.getAddress(k) then
qerror(('Cannot locate %s: %s not found'):format(gname, k))
end
df.global[k] = v
end
local addr
if dwarfmode_to_top() then
addr = searcher:find_interactive(
'Auto-searching for ' .. gname,
'uint8_t',
function(idx)
dwarfmode_feed_input('D_ORDERS')
dwarfmode_feed_input(table.unpack(seq))
return true
end
)
else
dfhack.printerr("Won't scan for standing orders global manually: " .. gname)
return
end
ms.found_offset(gname, addr)
end
local function exec_finder_so(gname, seq, _depends)
local depends = {}
for k, v in pairs(_depends or {}) do
if k:find('standing_orders_') ~= 1 then
k = 'standing_orders_' .. k
end
depends[k] = v
end
if force_scan['standing_orders'] then
force_scan[gname] = true
end
exec_finder(function()
return find_standing_orders(gname, seq, depends)
end, gname)
end
--
-- MAIN FLOW
--
@ -1636,6 +1710,81 @@ exec_finder(find_process_jobs, 'process_jobs')
exec_finder(find_process_dig, 'process_dig')
exec_finder(find_pause_state, 'pause_state')
print('\nStanding orders:\n')
exec_finder_so('standing_orders_gather_animals', 'ORDERS_GATHER_ANIMALS')
exec_finder_so('standing_orders_gather_bodies', 'ORDERS_GATHER_BODIES')
exec_finder_so('standing_orders_gather_food', 'ORDERS_GATHER_FOOD')
exec_finder_so('standing_orders_gather_furniture', 'ORDERS_GATHER_FURNITURE')
exec_finder_so('standing_orders_gather_minerals', 'ORDERS_GATHER_STONE')
exec_finder_so('standing_orders_gather_wood', 'ORDERS_GATHER_WOOD')
exec_finder_so('standing_orders_gather_refuse',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_GATHER'})
exec_finder_so('standing_orders_gather_refuse_outside',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_OUTSIDE'}, {gather_refuse=1})
exec_finder_so('standing_orders_gather_vermin_remains',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_OUTSIDE_VERMIN'}, {gather_refuse=1, gather_refuse_outside=1})
exec_finder_so('standing_orders_dump_bones',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_BONE'}, {gather_refuse=1})
exec_finder_so('standing_orders_dump_corpses',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_CORPSE'}, {gather_refuse=1})
exec_finder_so('standing_orders_dump_hair',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_STRAND_TISSUE'}, {gather_refuse=1})
exec_finder_so('standing_orders_dump_other',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_OTHER'}, {gather_refuse=1})
exec_finder_so('standing_orders_dump_shells',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SHELL'}, {gather_refuse=1})
exec_finder_so('standing_orders_dump_skins',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SKIN'}, {gather_refuse=1})
exec_finder_so('standing_orders_dump_skulls',
{'ORDERS_REFUSE', 'ORDERS_REFUSE_DUMP_SKULL'}, {gather_refuse=1})
exec_finder_so('standing_orders_auto_butcher',
{'ORDERS_WORKSHOP', 'ORDERS_BUTCHER'})
exec_finder_so('standing_orders_auto_collect_webs',
{'ORDERS_WORKSHOP', 'ORDERS_COLLECT_WEB'})
exec_finder_so('standing_orders_auto_fishery',
{'ORDERS_WORKSHOP', 'ORDERS_AUTO_FISHERY'})
exec_finder_so('standing_orders_auto_kiln',
{'ORDERS_WORKSHOP', 'ORDERS_AUTO_KILN'})
exec_finder_so('standing_orders_auto_kitchen',
{'ORDERS_WORKSHOP', 'ORDERS_AUTO_KITCHEN'})
exec_finder_so('standing_orders_auto_loom',
{'ORDERS_WORKSHOP', 'ORDERS_LOOM'})
exec_finder_so('standing_orders_auto_other',
{'ORDERS_WORKSHOP', 'ORDERS_AUTO_OTHER'})
exec_finder_so('standing_orders_auto_slaughter',
{'ORDERS_WORKSHOP', 'ORDERS_SLAUGHTER'})
exec_finder_so('standing_orders_auto_smelter',
{'ORDERS_WORKSHOP', 'ORDERS_AUTO_SMELTER'})
exec_finder_so('standing_orders_auto_tan',
{'ORDERS_WORKSHOP', 'ORDERS_TAN'})
exec_finder_so('standing_orders_use_dyed_cloth',
{'ORDERS_WORKSHOP', 'ORDERS_DYED_CLOTH'})
exec_finder_so('standing_orders_forbid_other_dead_items',
{'ORDERS_AUTOFORBID', 'ORDERS_FORBID_OTHER_ITEMS'})
exec_finder_so('standing_orders_forbid_other_nohunt',
{'ORDERS_AUTOFORBID', 'ORDERS_FORBID_OTHER_CORPSE'})
exec_finder_so('standing_orders_forbid_own_dead',
{'ORDERS_AUTOFORBID', 'ORDERS_FORBID_YOUR_CORPSE'})
exec_finder_so('standing_orders_forbid_own_dead_items',
{'ORDERS_AUTOFORBID', 'ORDERS_FORBID_YOUR_ITEMS'})
exec_finder_so('standing_orders_forbid_used_ammo',
{'ORDERS_AUTOFORBID', 'ORDERS_FORBID_PROJECTILE'})
exec_finder_so('standing_orders_farmer_harvest', 'ORDERS_ALL_HARVEST')
exec_finder_so('standing_orders_job_cancel_announce', 'ORDERS_EXCEPTIONS')
exec_finder_so('standing_orders_mix_food', 'ORDERS_MIXFOODS')
exec_finder_so('standing_orders_zoneonly_drink',
{'ORDERS_ZONE', 'ORDERS_ZONE_DRINKING'})
exec_finder_so('standing_orders_zoneonly_fish',
{'ORDERS_ZONE', 'ORDERS_ZONE_FISHING'})
dwarfmode_to_top()
print('\nDone. Now exit the game with the die command and add\n'..
'the newly-found globals to symbols.xml. You can find them\n'..
'in stdout.log or here:\n')

@ -0,0 +1,146 @@
-- Display DF version information about the current save
--@module = true
--[[=begin
devel/save-version
==================
Display DF version information about the current save
=end]]
local function dummy() return nil end
function has_field(tbl, field)
return (pcall(function() assert(tbl[field] ~= nil) end))
end
function class_has_field(cls, field)
local obj = cls:new()
local ret = has_field(obj, field)
obj:delete()
return ret
end
versions = {
-- skipped v0.21-v0.28
[1287] = "0.31.01",
[1288] = "0.31.02",
[1289] = "0.31.03",
[1292] = "0.31.04",
[1295] = "0.31.05",
[1297] = "0.31.06",
[1300] = "0.31.08",
[1304] = "0.31.09",
[1305] = "0.31.10",
[1310] = "0.31.11",
[1311] = "0.31.12",
[1323] = "0.31.13",
[1325] = "0.31.14",
[1326] = "0.31.15",
[1327] = "0.31.16",
[1340] = "0.31.17",
[1341] = "0.31.18",
[1351] = "0.31.19",
[1353] = "0.31.20",
[1354] = "0.31.21",
[1359] = "0.31.22",
[1360] = "0.31.23",
[1361] = "0.31.24",
[1362] = "0.31.25",
[1372] = "0.34.01",
[1374] = "0.34.02",
[1376] = "0.34.03",
[1377] = "0.34.04",
[1378] = "0.34.05",
[1382] = "0.34.06",
[1383] = "0.34.07",
[1400] = "0.34.08",
[1402] = "0.34.09",
[1403] = "0.34.10",
[1404] = "0.34.11",
[1441] = "0.40.01",
[1442] = "0.40.02",
[1443] = "0.40.03",
[1444] = "0.40.04",
[1445] = "0.40.05",
[1446] = "0.40.06",
[1448] = "0.40.07",
[1449] = "0.40.08",
[1451] = "0.40.09",
[1452] = "0.40.10",
[1456] = "0.40.11",
[1459] = "0.40.12",
[1462] = "0.40.13",
[1469] = "0.40.14",
[1470] = "0.40.15",
[1471] = "0.40.16",
[1472] = "0.40.17",
[1473] = "0.40.18",
[1474] = "0.40.19",
[1477] = "0.40.20",
[1478] = "0.40.21",
[1479] = "0.40.22",
[1480] = "0.40.23",
[1481] = "0.40.24",
[1531] = "0.42.01",
[1532] = "0.42.02",
[1533] = "0.42.03",
[1534] = "0.42.04",
}
min_version = math.huge
max_version = -math.huge
for k in pairs(versions) do
min_version = math.min(min_version, k)
max_version = math.max(max_version, k)
end
if class_has_field(df.world.T_cur_savegame, 'save_version') then
function get_save_version()
return df.global.world.cur_savegame.save_version
end
elseif class_has_field(df.world.T_pathfinder, 'anon_2') then
function get_save_version()
return df.global.world.pathfinder.anon_2
end
else
get_save_version = dummy
end
if class_has_field(df.world, 'original_save_version') then
function get_original_save_version()
return df.global.world.original_save_version
end
else
get_original_save_version = dummy
end
function describe(version)
if version == 0 then
return 'no world loaded'
elseif versions[version] then
return versions[version]
elseif version < min_version then
return 'unknown old version before ' .. describe(min_version) .. ': ' .. tostring(version)
elseif version > max_version then
return 'unknown new version after ' .. describe(max_version) .. ': ' .. tostring(version)
else
return 'unknown version: ' .. tostring(version)
end
end
function dump(desc, func)
local ret = tonumber(func())
if ret then
print(desc .. ': ' .. describe(ret))
else
dfhack.printerr('could not find ' .. desc .. ' (DFHack version too old)')
end
end
if not moduleMode then
if not dfhack.isWorldLoaded() then qerror('no world loaded') end
dump('original DF version', get_original_save_version)
dump('most recent DF version', get_save_version)
end

@ -20,11 +20,16 @@ UnitPathUI.focus_path = 'unit-path'
UnitPathUI.ATTRS {
unit = DEFAULT_NIL,
has_path = false,
has_goal = false,
}
function UnitPathUI:init()
self.saved_mode = df.global.ui.main.mode
if self.unit then
self.has_path = #self.unit.path.path.x > 0
self.has_goal = self.unit.path.dest.x >= 0
end
end
function UnitPathUI:onShow()
@ -139,12 +144,11 @@ function UnitPathUI:onRenderBody(dc)
end
local cursor = guidm.getCursorPos()
local has_path = #self.unit.path.path.x>0
local vp = self:getViewport()
local mdc = gui.Painter.new(self.df_layout.map)
if not has_path then
if not self.has_path then
if gui.blink_visible(120) then
paintMapTile(mdc, vp, cursor, self.unit.pos, 15, COLOR_LIGHTRED, COLOR_RED)
end
@ -153,7 +157,7 @@ function UnitPathUI:onRenderBody(dc)
else
self:renderPath(mdc,vp,cursor)
dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal])
dc:seek(1,6):pen(COLOR_GREEN):string(df.unit_path_goal[self.unit.path.goal] or '?')
end
dc:newline():pen(COLOR_GREY)
@ -183,7 +187,7 @@ function UnitPathUI:onRenderBody(dc)
dc:newline():newline(1):pen(COLOR_WHITE)
dc:key('CUSTOM_Z'):string(": Zoom unit, ")
dc:key('CUSTOM_G'):string(": Zoom goal",COLOR_GREY,nil,has_path)
dc:key('CUSTOM_G'):string(": Zoom goal",COLOR_GREY,nil,self.has_goal)
dc:newline(1)
dc:key('CUSTOM_N'):string(": Zoom station",COLOR_GREY,nil,has_station)
dc:newline():newline(1)
@ -194,7 +198,7 @@ function UnitPathUI:onInput(keys)
if keys.CUSTOM_Z then
self:moveCursorTo(copyall(self.unit.pos))
elseif keys.CUSTOM_G then
if #self.unit.path.path.x > 0 then
if self.has_goal then
self:moveCursorTo(copyall(self.unit.path.dest))
end
elseif keys.CUSTOM_N then
@ -208,6 +212,10 @@ function UnitPathUI:onInput(keys)
end
end
function UnitPathUI:onGetSelectedUnit()
return self.unit
end
local unit = dfhack.gui.getSelectedUnit(true)
if not unit or not string.match(dfhack.gui.getCurFocus(), '^dwarfmode/ViewUnits/Some/') then

@ -85,155 +85,158 @@ end
--create an extra legends xml with extra data, by Mason11987 for World Viewer
function export_more_legends_xml()
local julian_day = math.floor(df.global.cur_year_tick / 1200) + 1
local month = math.floor(julian_day / 28) + 1 --days and months are 1-indexed
local day = julian_day % 28 + 1
local month = dfhack.world.ReadCurrentMonth() + 1 --days and months are 1-indexed
local day = dfhack.world.ReadCurrentDay()
local year_str = string.format('%0'..math.max(5, string.len(''..df.global.cur_year))..'d', df.global.cur_year)
local date_str = year_str..string.format('-%02d-%02d', month, day)
io.output(tostring(df.global.world.cur_savegame.save_dir).."-"..date_str.."-legends_plus.xml")
local filename = df.global.world.cur_savegame.save_dir.."-"..date_str.."-legends_plus.xml"
local file = io.open(filename, 'w')
if not file then qerror("could not open file: " .. filename) end
io.write ("<?xml version=\"1.0\" encoding='UTF-8'?>".."\n")
io.write ("<df_world>".."\n")
io.write ("<name>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."</name>".."\n")
io.write ("<altname>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."</altname>".."\n")
file:write("<?xml version=\"1.0\" encoding='UTF-8'?>\n")
file:write("<df_world>\n")
file:write("<name>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name)).."</name>\n")
file:write("<altname>"..dfhack.df2utf(dfhack.TranslateName(df.global.world.world_data.name,1)).."</altname>\n")
io.write ("<regions>".."\n")
file:write("<regions>\n")
for regionK, regionV in ipairs(df.global.world.world_data.regions) do
io.write ("\t".."<region>".."\n")
io.write ("\t\t".."<id>"..regionV.index.."</id>".."\n")
io.write ("\t\t".."<coords>")
file:write("\t<region>\n")
file:write("\t\t<id>"..regionV.index.."</id>\n")
file:write("\t\t<coords>")
for xK, xVal in ipairs(regionV.region_coords.x) do
io.write (xVal..","..regionV.region_coords.y[xK].."|")
file:write(xVal..","..regionV.region_coords.y[xK].."|")
end
io.write ("</coords>\n")
io.write ("\t".."</region>".."\n")
file:write("</coords>\n")
file:write("\t</region>\n")
end
io.write ("</regions>".."\n")
file:write("</regions>\n")
io.write ("<underground_regions>".."\n")
file:write("<underground_regions>\n")
for regionK, regionV in ipairs(df.global.world.world_data.underground_regions) do
io.write ("\t".."<underground_region>".."\n")
io.write ("\t\t".."<id>"..regionV.index.."</id>".."\n")
io.write ("\t\t".."<coords>")
file:write("\t<underground_region>\n")
file:write("\t\t<id>"..regionV.index.."</id>\n")
file:write("\t\t<coords>")
for xK, xVal in ipairs(regionV.region_coords.x) do
io.write (xVal..","..regionV.region_coords.y[xK].."|")
file:write(xVal..","..regionV.region_coords.y[xK].."|")
end
io.write ("</coords>\n")
io.write ("\t".."</underground_region>".."\n")
file:write("</coords>\n")
file:write("\t</underground_region>\n")
end
io.write ("</underground_regions>".."\n")
file:write("</underground_regions>\n")
io.write ("<sites>".."\n")
file:write("<sites>\n")
for siteK, siteV in ipairs(df.global.world.world_data.sites) do
if (#siteV.buildings > 0) then
io.write ("\t".."<site>".."\n")
file:write("\t<site>\n")
for k,v in pairs(siteV) do
if (k == "id") then
io.write ("\t\t".."<"..k..">"..tostring(v).."</"..k..">".."\n")
file:write("\t\t<"..k..">"..tostring(v).."</"..k..">\n")
elseif (k == "buildings") then
io.write ("\t\t".."<structures>".."\n")
file:write("\t\t<structures>\n")
for buildingK, buildingV in ipairs(siteV.buildings) do
io.write ("\t\t\t".."<structure>".."\n")
io.write ("\t\t\t\t".."<id>"..buildingV.id.."</id>".."\n")
io.write ("\t\t\t\t".."<type>"..df.abstract_building_type[buildingV:getType()]:lower().."</type>".."\n")
file:write("\t\t\t<structure>\n")
file:write("\t\t\t\t<id>"..buildingV.id.."</id>\n")
file:write("\t\t\t\t<type>"..df.abstract_building_type[buildingV:getType()]:lower().."</type>\n")
if (df.abstract_building_type[buildingV:getType()]:lower() ~= "underworld_spire") then
io.write ("\t\t\t\t".."<name>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."</name>".."\n")
io.write ("\t\t\t\t".."<name2>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."</name2>".."\n")
file:write("\t\t\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name, 1)).."</name>\n")
file:write("\t\t\t\t<name2>"..dfhack.df2utf(dfhack.TranslateName(buildingV.name)).."</name2>\n")
end
io.write ("\t\t\t".."</structure>".."\n")
file:write("\t\t\t</structure>\n")
end
io.write ("\t\t".."</structures>".."\n")
file:write("\t\t</structures>\n")
end
end
io.write ("\t".."</site>".."\n")
file:write("\t</site>\n")
end
end
io.write ("</sites>".."\n")
file:write("</sites>\n")
io.write ("<world_constructions>".."\n")
file:write("<world_constructions>\n")
for wcK, wcV in ipairs(df.global.world.world_data.constructions.list) do
io.write ("\t".."<world_construction>".."\n")
io.write ("\t\t".."<id>"..wcV.id.."</id>".."\n")
io.write ("\t\t".."<name>"..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."</name>".."\n")
io.write ("\t\t".."<type>"..(df.world_construction_type[wcV:getType()]):lower().."</type>".."\n")
io.write ("\t\t".."<coords>")
file:write("\t<world_construction>\n")
file:write("\t\t<id>"..wcV.id.."</id>\n")
file:write("\t\t<name>"..dfhack.df2utf(dfhack.TranslateName(wcV.name,1)).."</name>\n")
file:write("\t\t<type>"..(df.world_construction_type[wcV:getType()]):lower().."</type>\n")
file:write("\t\t<coords>")
for xK, xVal in ipairs(wcV.square_pos.x) do
io.write (xVal..","..wcV.square_pos.y[xK].."|")
file:write(xVal..","..wcV.square_pos.y[xK].."|")
end
io.write ("</coords>\n")
io.write ("\t".."</world_construction>".."\n")
file:write("</coords>\n")
file:write("\t</world_construction>\n")
end
io.write ("</world_constructions>".."\n")
file:write("</world_constructions>\n")
io.write ("<artifacts>".."\n")
file:write("<artifacts>\n")
for artifactK, artifactV in ipairs(df.global.world.artifacts.all) do
io.write ("\t".."<artifact>".."\n")
io.write ("\t\t".."<id>"..artifactV.id.."</id>".."\n")
file:write("\t<artifact>\n")
file:write("\t\t<id>"..artifactV.id.."</id>\n")
if (artifactV.item:getType() ~= -1) then
io.write ("\t\t".."<item_type>"..tostring(df.item_type[artifactV.item:getType()]):lower().."</item_type>".."\n")
file:write("\t\t<item_type>"..tostring(df.item_type[artifactV.item:getType()]):lower().."</item_type>\n")
if (artifactV.item:getSubtype() ~= -1) then
io.write ("\t\t".."<item_subtype>"..artifactV.item.subtype.name.."</item_subtype>".."\n")
file:write("\t\t<item_subtype>"..artifactV.item.subtype.name.."</item_subtype>\n")
end
end
if (table.containskey(artifactV.item,"description")) then
io.write ("\t\t".."<item_description>"..artifactV.item.description:lower().."</item_description>".."\n")
file:write("\t\t<item_description>"..dfhack.df2utf(artifactV.item.description:lower()).."</item_description>\n")
end
if (artifactV.item:getMaterial() ~= -1 and artifactV.item:getMaterialIndex() ~= -1) then
io.write ("\t\t".."<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."</mat>".."\n")
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(artifactV.item:getMaterial(), artifactV.item:getMaterialIndex())).."</mat>\n")
end
io.write ("\t".."</artifact>".."\n")
file:write("\t</artifact>\n")
end
io.write ("</artifacts>".."\n")
file:write("</artifacts>\n")
io.write ("<historical_figures>".."\n".."</historical_figures>".."\n")
file:write("<historical_figures>\n</historical_figures>\n")
io.write ("<entity_populations>".."\n")
file:write("<entity_populations>\n")
for entityPopK, entityPopV in ipairs(df.global.world.entity_populations) do
io.write ("\t".."<entity_population>".."\n")
io.write ("\t\t".."<id>"..entityPopV.id.."</id>".."\n")
file:write("\t<entity_population>\n")
file:write("\t\t<id>"..entityPopV.id.."</id>\n")
for raceK, raceV in ipairs(entityPopV.races) do
local raceName = (df.global.world.raws.creatures.all[raceV].creature_id):lower()
io.write ("\t\t".."<race>"..raceName..":"..entityPopV.counts[raceK].."</race>".."\n")
file:write("\t\t<race>"..raceName..":"..entityPopV.counts[raceK].."</race>\n")
end
io.write ("\t\t".."<civ_id>"..entityPopV.civ_id.."</civ_id>".."\n")
io.write ("\t".."</entity_population>".."\n")
file:write("\t\t<civ_id>"..entityPopV.civ_id.."</civ_id>\n")
file:write("\t</entity_population>\n")
end
io.write ("</entity_populations>".."\n")
file:write("</entity_populations>\n")
io.write ("<entities>".."\n")
file:write("<entities>\n")
for entityK, entityV in ipairs(df.global.world.entities.all) do
io.write ("\t".."<entity>".."\n")
io.write ("\t\t".."<id>"..entityV.id.."</id>".."\n")
io.write ("\t\t".."<race>"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."</race>".."\n")
io.write ("\t\t".."<type>"..(df.historical_entity_type[entityV.type]):lower().."</type>".."\n")
if (df.historical_entity_type[entityV.type]):lower() == "religion" then -- Get worshipped figure
if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nill and
file:write("\t<entity>\n")
file:write("\t\t<id>"..entityV.id.."</id>\n")
if entityV.race >= 0 then
file:write("\t\t<race>"..(df.global.world.raws.creatures.all[entityV.race].creature_id):lower().."</race>\n")
end
file:write("\t\t<type>"..(df.historical_entity_type[entityV.type]):lower().."</type>\n")
if entityV.type == df.historical_entity_type.Religion then -- Get worshipped figure
if (entityV.unknown1b ~= nil and entityV.unknown1b.worship ~= nil and
#entityV.unknown1b.worship == 1) then
io.write ("\t\t".."<worship_id>"..entityV.unknown1b.worship[0].."</worship_id>".."\n")
file:write("\t\t<worship_id>"..entityV.unknown1b.worship[0].."</worship_id>\n")
else
print(entityV.unknown1b, entityV.unknown1b.worship, #entityV.unknown1b.worship)
end
end
for id, link in pairs(entityV.entity_links) do
io.write ("\t\t".."<entity_link>".."\n")
file:write("\t\t<entity_link>\n")
for k, v in pairs(link) do
if (k == "type") then
io.write ("\t\t\t".."<"..k..">"..tostring(df.entity_entity_link_type[v]).."</"..k..">".."\n")
file:write("\t\t\t<"..k..">"..tostring(df.entity_entity_link_type[v]).."</"..k..">\n")
else
io.write ("\t\t\t".."<"..k..">"..v.."</"..k..">".."\n")
file:write("\t\t\t<"..k..">"..v.."</"..k..">\n")
end
end
io.write ("\t\t".."</entity_link>".."\n")
file:write("\t\t</entity_link>\n")
end
for id, link in ipairs(entityV.children) do
io.write ("\t\t".."<child>"..link.."</child>".."\n")
file:write("\t\t<child>"..link.."</child>\n")
end
io.write ("\t".."</entity>".."\n")
file:write("\t</entity>\n")
end
io.write ("</entities>".."\n")
file:write("</entities>\n")
io.write ("<historical_events>".."\n")
file:write("<historical_events>\n")
for ID, event in ipairs(df.global.world.history.events) do
if event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK
or event:getType() == df.history_event_type.ADD_HF_SITE_LINK
@ -274,9 +277,9 @@ function export_more_legends_xml()
or event:getType() == df.history_event_type.HIST_FIGURE_WOUNDED
or event:getType() == df.history_event_type.HIST_FIGURE_DIED
then
io.write ("\t".."<historical_event>".."\n")
io.write ("\t\t".."<id>"..event.id.."</id>".."\n")
io.write ("\t\t".."<type>"..tostring(df.history_event_type[event:getType()]):lower().."</type>".."\n")
file:write("\t<historical_event>\n")
file:write("\t\t<id>"..event.id.."</id>\n")
file:write("\t\t<type>"..tostring(df.history_event_type[event:getType()]):lower().."</type>\n")
for k,v in pairs(event) do
if k == "year" or k == "seconds" or k == "flags" or k == "id"
or (k == "region" and event:getType() ~= df.history_event_type.HF_DOES_INTERACTION)
@ -284,70 +287,70 @@ function export_more_legends_xml()
or k == "anon_1" or k == "anon_2" or k == "flags2" or k == "unk1" then
elseif event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK and k == "link_type" then
io.write ("\t\t".."<"..k..">"..df.histfig_entity_link_type[v]:lower().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.histfig_entity_link_type[v]:lower().."</"..k..">\n")
elseif event:getType() == df.history_event_type.ADD_HF_ENTITY_LINK and k == "position_id" then
local entity = findEntity(event.civ)
if (entity ~= nil and event.civ > -1 and v > -1) then
for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do
if entityPositionsV.id == v then
io.write ("\t\t".."<position>"..tostring(entityPositionsV.name[0]):lower().."</position>".."\n")
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
io.write ("\t\t".."<position>-1</position>".."\n")
file:write("\t\t<position>-1</position>\n")
end
elseif event:getType() == df.history_event_type.CREATE_ENTITY_POSITION and k == "position" then
local entity = findEntity(event.site_civ)
if (entity ~= nil and v > -1) then
for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do
if entityPositionsV.id == v then
io.write ("\t\t".."<position>"..tostring(entityPositionsV.name[0]):lower().."</position>".."\n")
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
io.write ("\t\t".."<position>-1</position>".."\n")
file:write("\t\t<position>-1</position>\n")
end
elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "link_type" then
io.write ("\t\t".."<"..k..">"..df.histfig_entity_link_type[v]:lower().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.histfig_entity_link_type[v]:lower().."</"..k..">\n")
elseif event:getType() == df.history_event_type.REMOVE_HF_ENTITY_LINK and k == "position_id" then
local entity = findEntity(event.civ)
if (entity ~= nil and event.civ > -1 and v > -1) then
for entitypositionsK, entityPositionsV in ipairs(entity.positions.own) do
if entityPositionsV.id == v then
io.write ("\t\t".."<position>"..tostring(entityPositionsV.name[0]):lower().."</position>".."\n")
file:write("\t\t<position>"..tostring(entityPositionsV.name[0]):lower().."</position>\n")
break
end
end
else
io.write ("\t\t".."<position>-1</position>".."\n")
file:write("\t\t<position>-1</position>\n")
end
elseif event:getType() == df.history_event_type.ADD_HF_HF_LINK and k == "type" then
io.write ("\t\t".."<link_type>"..df.histfig_hf_link_type[v]:lower().."</link_type>".."\n")
file:write("\t\t<link_type>"..df.histfig_hf_link_type[v]:lower().."</link_type>\n")
elseif event:getType() == df.history_event_type.ADD_HF_SITE_LINK and k == "type" then
io.write ("\t\t".."<link_type>"..df.histfig_site_link_type[v]:lower().."</link_type>".."\n")
file:write("\t\t<link_type>"..df.histfig_site_link_type[v]:lower().."</link_type>\n")
elseif event:getType() == df.history_event_type.REMOVE_HF_SITE_LINK and k == "type" then
io.write ("\t\t".."<link_type>"..df.histfig_site_link_type[v]:lower().."</link_type>".."\n")
file:write("\t\t<link_type>"..df.histfig_site_link_type[v]:lower().."</link_type>\n")
elseif (event:getType() == df.history_event_type.ITEM_STOLEN or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT
) and k == "item_type" then
io.write ("\t\t".."<item_type>"..df.item_type[v]:lower().."</item_type>".."\n")
file:write("\t\t<item_type>"..df.item_type[v]:lower().."</item_type>\n")
elseif (event:getType() == df.history_event_type.ITEM_STOLEN or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or
event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT
) and k == "item_subtype" then
--if event.item_type > -1 and v > -1 then
io.write ("\t\t".."<"..k..">"..getItemSubTypeName(event.item_type,v).."</"..k..">".."\n")
file:write("\t\t<"..k..">"..getItemSubTypeName(event.item_type,v).."</"..k..">\n")
--end
elseif event:getType() == df.history_event_type.ITEM_STOLEN and k == "mattype" then
if (v > -1) then
if (dfhack.matinfo.decode(event.mattype, event.matindex) == nil) then
io.write ("\t\t".."<mattype>"..event.mattype.."</mattype>".."\n")
io.write ("\t\t".."<matindex>"..event.matindex.."</matindex>".."\n")
file:write("\t\t<mattype>"..event.mattype.."</mattype>\n")
file:write("\t\t<matindex>"..event.matindex.."</matindex>\n")
else
io.write ("\t\t".."<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."</mat>".."\n")
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mattype, event.matindex)).."</mat>\n")
end
end
elseif (event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM or
@ -355,19 +358,19 @@ function export_more_legends_xml()
) and k == "mat_type" then
if (v > -1) then
if (dfhack.matinfo.decode(event.mat_type, event.mat_index) == nil) then
io.write ("\t\t".."<mat_type>"..event.mat_type.."</mat_type>".."\n")
io.write ("\t\t".."<mat_index>"..event.mat_index.."</mat_index>".."\n")
file:write("\t\t<mat_type>"..event.mat_type.."</mat_type>\n")
file:write("\t\t<mat_index>"..event.mat_index.."</mat_index>\n")
else
io.write ("\t\t".."<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."</mat>".."\n")
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.mat_type, event.mat_index)).."</mat>\n")
end
end
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "imp_mat_type" then
if (v > -1) then
if (dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index) == nil) then
io.write ("\t\t".."<imp_mat_type>"..event.imp_mat_type.."</imp_mat_type>".."\n")
io.write ("\t\t".."<imp_mat_index>"..event.imp_mat_index.."</imp_mat_index>".."\n")
file:write("\t\t<imp_mat_type>"..event.imp_mat_type.."</imp_mat_type>\n")
file:write("\t\t<imp_mat_index>"..event.imp_mat_index.."</imp_mat_index>\n")
else
io.write ("\t\t".."<imp_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."</imp_mat>".."\n")
file:write("\t\t<imp_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.imp_mat_type, event.imp_mat_index)).."</imp_mat>\n")
end
end
@ -387,76 +390,76 @@ function export_more_legends_xml()
event:getType() == df.history_event_type.TOPICAGREEMENT_REJECTED or
event:getType() == df.history_event_type.TOPICAGREEMENT_MADE
) and k == "topic" then
io.write ("\t\t".."<topic>"..tostring(df.meeting_topic[v]):lower().."</topic>".."\n")
file:write("\t\t<topic>"..tostring(df.meeting_topic[v]):lower().."</topic>\n")
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ITEM_IMPROVEMENT and k == "improvement_type" then
io.write ("\t\t".."<improvement_type>"..df.improvement_type[v]:lower().."</improvement_type>".."\n")
file:write("\t\t<improvement_type>"..df.improvement_type[v]:lower().."</improvement_type>\n")
elseif ((event:getType() == df.history_event_type.HIST_FIGURE_REACH_SUMMIT and k == "figures") or
(event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "group")
or (event:getType() == df.history_event_type.BODY_ABUSED and k == "bodies")) then
for detailK,detailV in pairs(v) do
io.write ("\t\t".."<"..k..">"..detailV.."</"..k..">".."\n")
file:write("\t\t<"..k..">"..detailV.."</"..k..">\n")
end
elseif event:getType() == df.history_event_type.HIST_FIGURE_NEW_PET and k == "pets" then
for detailK,detailV in pairs(v) do
io.write ("\t\t".."<"..k..">"..(df.global.world.raws.creatures.all[detailV].creature_id):lower().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..(df.global.world.raws.creatures.all[detailV].creature_id):lower().."</"..k..">\n")
end
elseif event:getType() == df.history_event_type.BODY_ABUSED and (k == "props") then
io.write ("\t\t".."<"..k.."_item_type"..">"..tostring(df.item_type[event.props.item.item_type]):lower().."</"..k.."_item_type"..">".."\n")
io.write ("\t\t".."<"..k.."_item_subtype"..">"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."</"..k.."_item_subtype"..">".."\n")
file:write("\t\t<"..k.."_item_type>"..tostring(df.item_type[event.props.item.item_type]):lower().."</"..k.."_item_type>\n")
file:write("\t\t<"..k.."_item_subtype>"..getItemSubTypeName(event.props.item.item_type,event.props.item.item_subtype).."</"..k.."_item_subtype>\n")
if (event.props.item.mat_type > -1) then
if (dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index) == nil) then
io.write ("\t\t".."<props_item_mat_type>"..event.props.item.mat_type.."</props_item_mat_type>".."\n")
io.write ("\t\t".."<props_item_mat_index>"..event.props.item.mat_index.."</props_item_mat_index>".."\n")
file:write("\t\t<props_item_mat_type>"..event.props.item.mat_type.."</props_item_mat_type>\n")
file:write("\t\t<props_item_mat_index>"..event.props.item.mat_index.."</props_item_mat_index>\n")
else
io.write ("\t\t".."<props_item_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."</props_item_mat>".."\n")
file:write("\t\t<props_item_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.props.item.mat_type, event.props.item.mat_index)).."</props_item_mat>\n")
end
end
--io.write ("\t\t".."<"..k.."_item_mat_type"..">"..tostring(event.props.item.mat_type).."</"..k.."_item_mat_index"..">".."\n")
--io.write ("\t\t".."<"..k.."_item_mat_index"..">"..tostring(event.props.item.mat_index).."</"..k.."_item_mat_index"..">".."\n")
io.write ("\t\t".."<"..k.."_pile_type"..">"..tostring(event.props.pile_type).."</"..k.."_pile_type"..">".."\n")
--file:write("\t\t<"..k.."_item_mat_type>"..tostring(event.props.item.mat_type).."</"..k.."_item_mat_index>\n")
--file:write("\t\t<"..k.."_item_mat_index>"..tostring(event.props.item.mat_index).."</"..k.."_item_mat_index>\n")
file:write("\t\t<"..k.."_pile_type>"..tostring(event.props.pile_type).."</"..k.."_pile_type>\n")
elseif event:getType() == df.history_event_type.ASSUME_IDENTITY and k == "identity" then
if (table.contains(df.global.world.identities.all,v)) then
if (df.global.world.identities.all[v].histfig_id == -1) then
local thisIdentity = df.global.world.identities.all[v]
io.write ("\t\t".."<identity_name>"..thisIdentity.name.first_name.."</identity_name>".."\n")
io.write ("\t\t".."<identity_race>"..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."</identity_race>".."\n")
io.write ("\t\t".."<identity_caste>"..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."</identity_caste>".."\n")
file:write("\t\t<identity_name>"..thisIdentity.name.first_name.."</identity_name>\n")
file:write("\t\t<identity_race>"..(df.global.world.raws.creatures.all[thisIdentity.race].creature_id):lower().."</identity_race>\n")
file:write("\t\t<identity_caste>"..(df.global.world.raws.creatures.all[thisIdentity.race].caste[thisIdentity.caste].caste_id):lower().."</identity_caste>\n")
else
io.write ("\t\t".."<identity_hf>"..df.global.world.identities.all[v].histfig_id.."</identity_hf>".."\n")
file:write("\t\t<identity_hf>"..df.global.world.identities.all[v].histfig_id.."</identity_hf>\n")
end
end
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_type" then
io.write ("\t\t".."<building_type>"..df.building_type[v]:lower().."</building_type>".."\n")
file:write("\t\t<building_type>"..df.building_type[v]:lower().."</building_type>\n")
elseif event:getType() == df.history_event_type.MASTERPIECE_CREATED_ARCH_CONSTRUCT and k == "building_subtype" then
if (df.building_type[event.building_type]:lower() == "furnace") then
io.write ("\t\t".."<building_subtype>"..df.furnace_type[v]:lower().."</building_subtype>".."\n")
file:write("\t\t<building_subtype>"..df.furnace_type[v]:lower().."</building_subtype>\n")
elseif v > -1 then
io.write ("\t\t".."<building_subtype>"..tostring(v).."</building_subtype>".."\n")
file:write("\t\t<building_subtype>"..tostring(v).."</building_subtype>\n")
end
elseif k == "race" then
if v > -1 then
io.write ("\t\t".."<race>"..(df.global.world.raws.creatures.all[v].creature_id):lower().."</race>".."\n")
file:write("\t\t<race>"..(df.global.world.raws.creatures.all[v].creature_id):lower().."</race>\n")
end
elseif k == "caste" then
if v > -1 then
io.write ("\t\t".."<caste>"..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."</caste>".."\n")
file:write("\t\t<caste>"..(df.global.world.raws.creatures.all[event.race].caste[v].caste_id):lower().."</caste>\n")
end
elseif k == "interaction" and event:getType() == df.history_event_type.HF_DOES_INTERACTION then
io.write ("\t\t".."<interaction_action>"..df.global.world.raws.interactions[v].str[3].value.."</interaction_action>".."\n")
io.write ("\t\t".."<interaction_string>"..df.global.world.raws.interactions[v].str[4].value.."</interaction_string>".."\n")
file:write("\t\t<interaction_action>"..df.global.world.raws.interactions[v].str[3].value.."</interaction_action>\n")
file:write("\t\t<interaction_string>"..df.global.world.raws.interactions[v].str[4].value.."</interaction_string>\n")
elseif k == "interaction" and event:getType() == df.history_event_type.HF_LEARNS_SECRET then
io.write ("\t\t".."<secret_text>"..df.global.world.raws.interactions[v].str[2].value.."</secret_text>".."\n")
file:write("\t\t<secret_text>"..df.global.world.raws.interactions[v].str[2].value.."</secret_text>\n")
elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "weapon" then
for detailK,detailV in pairs(v) do
if (detailK == "item") then
if detailV > -1 then
io.write ("\t\t".."<"..detailK..">"..detailV.."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\n")
local thisItem = df.item.find(detailV)
if (thisItem ~= nil) then
if (thisItem.flags.artifact == true) then
for refk,refv in pairs(thisItem.general_refs) do
if (refv:getType() == 1) then
io.write ("\t\t".."<artifact_id>"..refv.artifact_id.."</artifact_id>".."\n")
file:write("\t\t<artifact_id>"..refv.artifact_id.."</artifact_id>\n")
break
end
end
@ -466,27 +469,27 @@ function export_more_legends_xml()
end
elseif (detailK == "item_type") then
if event.weapon.item > -1 then
io.write ("\t\t".."<"..detailK..">"..tostring(df.item_type[detailV]):lower().."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..tostring(df.item_type[detailV]):lower().."</"..detailK..">\n")
end
elseif (detailK == "item_subtype") then
if event.weapon.item > -1 and detailV > -1 then
io.write ("\t\t".."<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.item_type,detailV).."</"..detailK..">\n")
end
elseif (detailK == "mattype") then
if (detailV > -1) then
io.write ("\t\t".."<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."</mat>".."\n")
file:write("\t\t<mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.mattype, event.weapon.matindex)).."</mat>\n")
end
elseif (detailK == "matindex") then
elseif (detailK == "shooter_item") then
if detailV > -1 then
io.write ("\t\t".."<"..detailK..">"..detailV.."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\n")
local thisItem = df.item.find(detailV)
if thisItem ~= nil then
if (thisItem.flags.artifact == true) then
for refk,refv in pairs(thisItem.general_refs) do
if (refv:getType() == 1) then
io.write ("\t\t".."<shooter_artifact_id>"..refv.artifact_id.."</shooter_artifact_id>".."\n")
file:write("\t\t<shooter_artifact_id>"..refv.artifact_id.."</shooter_artifact_id>\n")
break
end
end
@ -495,42 +498,42 @@ function export_more_legends_xml()
end
elseif (detailK == "shooter_item_type") then
if event.weapon.shooter_item > -1 then
io.write ("\t\t".."<"..detailK..">"..tostring(df.item_type[detailV]):lower().."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..tostring(df.item_type[detailV]):lower().."</"..detailK..">\n")
end
elseif (detailK == "shooter_item_subtype") then
if event.weapon.shooter_item > -1 and detailV > -1 then
io.write ("\t\t".."<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..getItemSubTypeName(event.weapon.shooter_item_type,detailV).."</"..detailK..">\n")
end
elseif (detailK == "shooter_mattype") then
if (detailV > -1) then
io.write ("\t\t".."<shooter_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."</shooter_mat>".."\n")
file:write("\t\t<shooter_mat>"..dfhack.matinfo.toString(dfhack.matinfo.decode(event.weapon.shooter_mattype, event.weapon.shooter_matindex)).."</shooter_mat>\n")
end
elseif (detailK == "shooter_matindex") then
--skip
elseif detailK == "slayer_race" or detailK == "slayer_caste" then
--skip
else
io.write ("\t\t".."<"..detailK..">"..detailV.."</"..detailK..">".."\n")
file:write("\t\t<"..detailK..">"..detailV.."</"..detailK..">\n")
end
end
elseif event:getType() == df.history_event_type.HIST_FIGURE_DIED and k == "death_cause" then
io.write ("\t\t".."<"..k..">"..df.death_type[v]:lower().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.death_type[v]:lower().."</"..k..">\n")
elseif event:getType() == df.history_event_type.CHANGE_HF_JOB and (k == "new_job" or k == "old_job") then
io.write ("\t\t".."<"..k..">"..df.profession[v]:lower().."</"..k..">".."\n")
file:write("\t\t<"..k..">"..df.profession[v]:lower().."</"..k..">\n")
else
io.write ("\t\t".."<"..k..">"..tostring(v).."</"..k..">".."\n")
file:write("\t\t<"..k..">"..tostring(v).."</"..k..">\n")
end
end
io.write ("\t".."</historical_event>".."\n")
file:write("\t</historical_event>\n")
end
end
io.write ("</historical_events>".."\n")
io.write ("<historical_event_collections>".."\n")
io.write ("</historical_event_collections>".."\n")
io.write ("<historical_eras>".."\n")
io.write ("</historical_eras>".."\n")
io.write ("</df_world>".."\n")
io.close()
file:write("</historical_events>\n")
file:write("<historical_event_collections>\n")
file:write("</historical_event_collections>\n")
file:write("<historical_eras>\n")
file:write("</historical_eras>\n")
file:write("</df_world>\n")
file:close()
end
-- export information and XML ('p, x')

@ -8,27 +8,34 @@ Shows a warning on world load for pre-release builds.
=end]]
if not dfhack.isPrerelease() then qerror('not a prerelease build') end
-- Don't fire during worldgen
if dfhack.internal.getAddress('gametype') and df.global.gametype == df.game_type.NONE then
return
end
local gui = require 'gui'
local dlg = require 'gui.dialogs'
local utils = require 'utils'
local message = {
message = {
'This is a prerelease build of DFHack. Some structures are likely', NEWLINE,
'to be incorrect, resulting in crashes or save corruption', NEWLINE,
{pen=COLOR_LIGHTRED, text='Make backups of your saves and avoid saving if possible.'}, NEWLINE,
{pen=COLOR_LIGHTRED, text='Make backups of your saves often!'},
}
pack_message = pack_message or [[
This should not be enabled by default in a pack.
If you are seeing this message and did not enable/install DFHack
yourself, please report this to your pack's maintainer.]]
path = dfhack.getHackPath():lower()
if path:find('lnp') or path:find('starter') or path:find('newb') or path:find('lazy') or path:find('pack') then
local pack_msg = [[
Under no circumstances should this be enabled by default in a pack.
If you are seeing this message and did not enable DFHack yourself,
please report this to your pack's maintainer.]]
for _, v in pairs(utils.split_string(pack_msg, '\n')) do
if #pack_message > 0 and (path:find('lnp') or path:find('starter') or path:find('newb') or path:find('lazy') or path:find('pack')) then
for _, v in pairs(utils.split_string(pack_message, '\n')) do
table.insert(message, NEWLINE)
table.insert(message, {text=v, pen=COLOR_LIGHTMAGENTA})
end
pack_message = ''
end
dfhack.print('\n')

@ -0,0 +1,46 @@
-- Print the weather map or change weather.
local helpstr = [[=begin
weather
=======
Prints a map of the local weather, or with arguments ``clear``,
``rain``, and ``snow`` changes the weather.
=end]]
local args = {...}
local cmd
local val_override = tonumber(args[1])
if args[1] then
cmd = args[1]:sub(1, 1)
end
if cmd == "h" or cmd == "?" then
print("The current weather is "..df.weather_type[dfhack.world.ReadCurrentWeather()])
print((helpstr:gsub('=[a-z]+', '')))
elseif cmd == "c" then
dfhack.world.SetCurrentWeather(df.weather_type.None)
print("The weather has cleared.")
elseif cmd == "r" then
dfhack.world.SetCurrentWeather(df.weather_type.Rain)
print("It is now raining.")
elseif cmd == "s" then
dfhack.world.SetCurrentWeather(df.weather_type.Snow)
print("It is now snowing.")
elseif val_override then
dfhack.world.SetCurrentWeather(val_override)
print("Set weather to " .. val_override)
elseif args[1] then
qerror("Unrecognized argument: " .. args[1])
else
-- df.global.current_weather is arranged in columns, not rows
kind = {[0]="C", "R", "S"}
print("Weather map (C = clear, R = rain, S = snow):")
for y=0, 4 do
s = ""
for x=0, 4 do
local cur = df.global.current_weather[x][y]
s = s .. (kind[cur] or cur) .. ' '
end
print(s)
end
end

@ -1,3 +1,4 @@
from __future__ import print_function
from io import open
import os
from os.path import basename, dirname, join, splitext
@ -16,6 +17,8 @@ def check_file(fname):
errors, doclines = 0, []
with open(fname, errors='ignore') as f:
for l in f.readlines():
if not l.strip():
continue
if doclines or l.strip().endswith('=begin'):
doclines.append(l.rstrip())
if l.startswith('=end'):
@ -26,7 +29,7 @@ def check_file(fname):
else:
print('Error: no documentation in: ' + fname)
return 1
title, underline = doclines[2], doclines[3]
title, underline = doclines[1], doclines[2]
if underline != '=' * len(title):
print('Error: title/underline mismatch:', fname, title, underline)
errors += 1