From 7b6bfc856a1ed982408aaea23d898b8eac65bb10 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Tue, 18 Feb 2025 17:25:31 -0600 Subject: [PATCH 01/34] add type identity for `std::filesystem::path` --- library/DataIdentity.cpp | 1 + library/LuaTypes.cpp | 18 ++++++++++++++++++ library/LuaWrapper.cpp | 2 +- library/include/DataIdentity.h | 24 ++++++++++++++++++++++++ library/include/LuaWrapper.h | 2 +- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/library/DataIdentity.cpp b/library/DataIdentity.cpp index e6b4889cf8..a385915d92 100644 --- a/library/DataIdentity.cpp +++ b/library/DataIdentity.cpp @@ -38,6 +38,7 @@ namespace df { const bool_identity identity_traits::identity; const stl_string_identity identity_traits::identity; + const path_identity identity_traits::identity; const ptr_string_identity identity_traits::identity; const ptr_string_identity identity_traits::identity; const pointer_identity identity_traits::identity; diff --git a/library/LuaTypes.cpp b/library/LuaTypes.cpp index 1aa7711e68..81914a7185 100644 --- a/library/LuaTypes.cpp +++ b/library/LuaTypes.cpp @@ -188,6 +188,24 @@ void df::stl_string_identity::lua_write(lua_State *state, int fname_idx, void *p *(std::string*)ptr = std::string(bytes, size); } +void df::path_identity::lua_read(lua_State* state, int fname_idx, void* ptr) const +{ + auto ppath = (std::filesystem::path*)ptr; + auto str = ppath->u8string(); + lua_pushlstring(state, (char*)str.data(), str.size()); +} + +void df::path_identity::lua_write(lua_State* state, int fname_idx, void* ptr, int val_index) const +{ + size_t size; + const char* bytes = lua_tolstring(state, val_index, &size); + if (!bytes) + field_error(state, fname_idx, "path expected", "write"); + + std::u8string str((char8_t*)bytes, size); + *(std::filesystem::path*)ptr = std::filesystem::path(str); +} + void df::pointer_identity::lua_read(lua_State *state, int fname_idx, void *ptr, const type_identity *target) { push_object_internal(state, target, *(void**)ptr); diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 608ef95683..892a6784cf 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -49,7 +49,7 @@ using namespace DFHack::LuaWrapper; /** * Report an error while accessing a field (index = field name). */ -void LuaWrapper::field_error(lua_State *state, int index, const char *err, const char *mode) +[[noreturn]] void LuaWrapper::field_error(lua_State *state, int index, const char *err, const char *mode) { if (lua_islightuserdata(state, UPVAL_METATABLE)) lua_pushstring(state, "(global)"); diff --git a/library/include/DataIdentity.h b/library/include/DataIdentity.h index 02bc8b3201..7e1f8eaaea 100644 --- a/library/include/DataIdentity.h +++ b/library/include/DataIdentity.h @@ -34,6 +34,7 @@ distribution. #include #include #include +#include #include "DataDefs.h" @@ -298,6 +299,24 @@ namespace df virtual void lua_write(lua_State *state, int fname_idx, void *ptr, int val_index) const; }; + class DFHACK_EXPORT path_identity : public DFHack::constructed_identity { + public: + path_identity() + : constructed_identity(sizeof(std::filesystem::path), &allocator_fn) + { + }; + + const std::string getFullName() const { return "path"; } + + virtual DFHack::identity_type type() const { return DFHack::IDTYPE_PRIMITIVE; } + + virtual bool isPrimitive() const { return true; } + + virtual void lua_read(lua_State* state, int fname_idx, void* ptr) const; + virtual void lua_write(lua_State* state, int fname_idx, void* ptr, int val_index) const; + }; + + class DFHACK_EXPORT stl_ptr_vector_identity : public ptr_container_identity { public: typedef std::vector container; @@ -616,6 +635,11 @@ namespace df static const stl_string_identity *get() { return &identity; } }; + template<> struct DFHACK_EXPORT identity_traits { + static const bool is_primitive = true; + static const path_identity identity; + static const path_identity* get() { return &identity; } + }; template<> struct DFHACK_EXPORT identity_traits { static const bool is_primitive = true; static const ptr_string_identity identity; diff --git a/library/include/LuaWrapper.h b/library/include/LuaWrapper.h index 347945e896..44bc19938c 100644 --- a/library/include/LuaWrapper.h +++ b/library/include/LuaWrapper.h @@ -142,7 +142,7 @@ namespace LuaWrapper { /** * Report an error while accessing a field (index = field name). */ - void field_error(lua_State *state, int index, const char *err, const char *mode); + [[noreturn]] void field_error(lua_State *state, int index, const char *err, const char *mode); /* * If is_method is true, these use UPVAL_TYPETABLE to save a hash lookup. From 20f7fd346aaa63c97b02b47777fe9613f2dc22c7 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 21 Feb 2025 17:18:54 -0600 Subject: [PATCH 02/34] flush cache after mempatch This adds code to flush cache after applying a hotpatch. While it's unlikely that a patched location will be in cache at the time we apply a patch, it is better to be safe than sorry Also fixed the spelling error in `setPermissions` while I was here --- library/Core.cpp | 6 ++++-- library/Process-linux.cpp | 9 ++++++++- library/Process-windows.cpp | 7 ++++++- library/include/MemAccess.h | 5 ++++- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/library/Core.cpp b/library/Core.cpp index 9dc2d2e68a..be53fe58fd 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -2987,7 +2987,7 @@ bool MemoryPatcher::verifyAccess(void *target, size_t count, bool write) save.push_back(perms); perms.write = perms.read = true; - if (!p->setPermisions(perms, perms)) + if (!p->setPermissions(perms, perms)) return false; } @@ -3000,13 +3000,15 @@ bool MemoryPatcher::write(void *target, const void *src, size_t size) return false; memmove(target, src, size); + + p->flushCache(target, size); return true; } void MemoryPatcher::close() { for (size_t i = 0; i < save.size(); i++) - p->setPermisions(save[i], save[i]); + p->setPermissions(save[i], save[i]); save.clear(); ranges.clear(); diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 85e83f54f6..321db80550 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -32,6 +32,7 @@ distribution. #include #include +#include #include #include #include @@ -205,7 +206,7 @@ int Process::getPID() return getpid(); } -bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) +bool Process::setPermissions(const t_memrange & range,const t_memrange &trgrange) { int result; int protect=0; @@ -217,6 +218,12 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) return result==0; } +bool Process::flushCache(const void* target, size_t count) +{ + return cacheflush(target, count, BCACHE); +} + + // returns -1 on error void* Process::memAlloc(const int length) { diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 2450eb87bd..0098c8c167 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -399,7 +399,7 @@ int Process::getPID() } -bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) +bool Process::setPermissions(const t_memrange & range,const t_memrange &trgrange) { DWORD newprotect=0; if(trgrange.read && !trgrange.write && !trgrange.execute)newprotect=PAGE_READONLY; @@ -414,6 +414,11 @@ bool Process::setPermisions(const t_memrange & range,const t_memrange &trgrange) return result; } +bool Process::flushCache(const void* target, size_t count) +{ + return 0 == FlushInstructionCache(d->my_handle, (LPCVOID)target, count); +} + void* Process::memAlloc(const int length) { void *ret; diff --git a/library/include/MemAccess.h b/library/include/MemAccess.h index 083d94434c..bcbab75bd3 100644 --- a/library/include/MemAccess.h +++ b/library/include/MemAccess.h @@ -270,11 +270,14 @@ namespace DFHack uint32_t getTickCount(); /// modify permisions of memory range - bool setPermisions(const t_memrange & range,const t_memrange &trgrange); + bool setPermissions(const t_memrange & range,const t_memrange &trgrange); /// write a possibly read-only memory area bool patchMemory(void *target, const void* src, size_t count); + /// flush cache + bool flushCache(const void* target, size_t count); + /// allocate new memory pages for code or stuff /// returns -1 on error (0 is a valid address) void* memAlloc(const int length); From 3b0d1ea40817e72ebf1ce3ea0728630f7eca6385 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 21 Feb 2025 17:32:08 -0600 Subject: [PATCH 03/34] use __builtin___clear_cache on linux apparently the documentation i found was... incomplete --- library/Process-linux.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 321db80550..1583b9d619 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -32,7 +32,6 @@ distribution. #include #include -#include #include #include #include @@ -220,7 +219,7 @@ bool Process::setPermissions(const t_memrange & range,const t_memrange &trgrange bool Process::flushCache(const void* target, size_t count) { - return cacheflush(target, count, BCACHE); + __builtin___clear_cache((char*)target, (char*)target + count - 1); } From a78f2a70e5e89a95a9569233cbbead221e19a500 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Fri, 21 Feb 2025 17:37:05 -0600 Subject: [PATCH 04/34] assume the intrinsic always succeeds --- library/Process-linux.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 1583b9d619..5e2458ec47 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -220,6 +220,7 @@ bool Process::setPermissions(const t_memrange & range,const t_memrange &trgrange bool Process::flushCache(const void* target, size_t count) { __builtin___clear_cache((char*)target, (char*)target + count - 1); + return false; /* assume always succeeds, as the builtin has no return type */ } From d5167e19daac69ce039ae3db5e2bbf79dc5c4aae Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Sat, 22 Feb 2025 14:12:15 -0600 Subject: [PATCH 05/34] switch `flushCache` to return `true` on success --- library/Process-linux.cpp | 2 +- library/Process-windows.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/library/Process-linux.cpp b/library/Process-linux.cpp index 5e2458ec47..a3fa9f2d67 100644 --- a/library/Process-linux.cpp +++ b/library/Process-linux.cpp @@ -220,7 +220,7 @@ bool Process::setPermissions(const t_memrange & range,const t_memrange &trgrange bool Process::flushCache(const void* target, size_t count) { __builtin___clear_cache((char*)target, (char*)target + count - 1); - return false; /* assume always succeeds, as the builtin has no return type */ + return true; /* assume always succeeds, as the builtin has no return type */ } diff --git a/library/Process-windows.cpp b/library/Process-windows.cpp index 0098c8c167..79bbea8002 100644 --- a/library/Process-windows.cpp +++ b/library/Process-windows.cpp @@ -416,7 +416,7 @@ bool Process::setPermissions(const t_memrange & range,const t_memrange &trgrange bool Process::flushCache(const void* target, size_t count) { - return 0 == FlushInstructionCache(d->my_handle, (LPCVOID)target, count); + return 0 != FlushInstructionCache(d->my_handle, (LPCVOID)target, count); } void* Process::memAlloc(const int length) From bf33e08efc0e20c9d95da7b9ab2a053f8e3efad1 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sun, 23 Feb 2025 20:19:49 +0100 Subject: [PATCH 06/34] map export to geoparquet using GDAL (prototype) --- plugins/CMakeLists.txt | 1 + plugins/export-map.cpp | 211 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 212 insertions(+) create mode 100644 plugins/export-map.cpp diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index 8f230ff583..f87f9002ce 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -71,6 +71,7 @@ if(BUILD_SUPPORTED) #dfhack_plugin(dwarfmonitor dwarfmonitor.cpp LINK_LIBRARIES lua) #add_subdirectory(embark-assistant) dfhack_plugin(eventful eventful.cpp LINK_LIBRARIES lua) + dfhack_plugin(export-map export-map.cpp COMPILE_FLAGS_GCC -fno-gnu-unique LINK_LIBRARIES gdal) dfhack_plugin(fastdwarf fastdwarf.cpp) dfhack_plugin(filltraffic filltraffic.cpp) dfhack_plugin(fix-occupancy fix-occupancy.cpp LINK_LIBRARIES lua) diff --git a/plugins/export-map.cpp b/plugins/export-map.cpp new file mode 100644 index 0000000000..0a9336a259 --- /dev/null +++ b/plugins/export-map.cpp @@ -0,0 +1,211 @@ +#include "Debug.h" +#include "Error.h" +#include "PluginManager.h" +#include "MiscUtils.h" + +#include "modules/Maps.h" +#include "modules/Translation.h" + +#include "df/world.h" +#include "df/map_block.h" +#include "df/world_data.h" +#include "df/region_map_entry.h" +#include "df/world_region.h" +#include "df/world_region_details.h" + +#include "gdal/ogrsf_frmts.h" + +#include +#include + +using std::string; +using std::vector; + +using namespace DFHack; + +DFHACK_PLUGIN("export-map"); + +REQUIRE_GLOBAL(world); + +namespace DFHack { + DBG_DECLARE(exportmap, log); +} + +static command_result do_command(color_ostream &out, vector ¶meters); + +DFhackCExport command_result plugin_init(color_ostream &out, std::vector &commands) { + DEBUG(log,out).print("initializing %s\n", plugin_name); + + commands.push_back(PluginCommand( + plugin_name, + "Export the world map.", + do_command)); + + return CR_OK; +} + +auto setGeometry(OGRFeature *feature, double x, double y, double dim) { + auto poly = new OGRPolygon(); + auto boundary = new OGRLinearRing(); + y = -y; + boundary->addPoint(x,y); + boundary->addPoint(x,y-dim); + boundary->addPoint(x+dim,y-dim); + boundary->addPoint(x+dim,y); + boundary->closeRings(); + //the "Directly" variants assume ownership of the objects created above + poly->addRingDirectly(boundary); + feature->SetGeometryDirectly( poly ); +} + +auto get_world_index(int world_x, int world_y, int8_t dir) { + switch (dir) { + case 1: world_x-- ; world_y++; break; + case 2: ; world_y++; break; + case 3: world_x++ ; world_y++; break; + case 4: world_x-- ; ; break; + // case 5 induces no change + case 6: world_x++ ; ; break; + case 7: world_x-- ; world_y--; break; + case 8: ; world_y--; break; + case 9: world_x++ ; world_y--; break; + } + world_x = std::min(std::max(0,world_x),world->world_data->world_width - 1); + world_y = std::min(std::max(0,world_y),world->world_data->world_height - 1); + return std::pair(world_x,world_y); +} + +auto create_field(OGRLayer *layer, std::string name, OGRFieldType type, int width = 0, OGRFieldSubType subtype = OFSTNone) { + OGRFieldDefn field( name.c_str() , type ); + if (subtype != OFSTNone) { + field.SetSubType(subtype); + } + if (width != 0) { + field.SetWidth(width); + } + // this should create a copy internally + if( layer->CreateField( &field ) != OGRERR_NONE ){ + throw CR_FAILURE; + } +} + +// PROJ.4 description of EPSG:3857 (https://epsg.io/3857) +static const char* EPSG_3857 = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"; + +static command_result do_command(color_ostream &out, vector ¶meters) { + out.print("exporting map... "); + + // set up coordinate system + OGRSpatialReference CRS; + if (CRS.importFromProj4(EPSG_3857) != OGRERR_NONE) { + out.printerr("could not set up coordinate system"); + return CR_FAILURE; + } + + // set up output driver + GDALAllRegister(); + const char *driver_name = "Parquet"; + const char *extension = "parquet"; + // const char *driver_name = "GPKG"; + // const char *extension = "gpkg"; + auto driver = GetGDALDriverManager()->GetDriverByName(driver_name); + CHECK_NULL_POINTER(driver); + + // create a data set and associate it to a file + std::string map("map."); + map.append(extension); + auto dataset = driver->Create( map.c_str(), 0, 0, 0, GDT_Unknown, NULL ); + + // create a layer for the biome data + auto layer = dataset->CreateLayer( "world_biomes", &CRS, wkbPolygon, NULL ); + + try { + create_field(layer, "biome_type", OFTString, 32); + create_field(layer, "elevation", OFTInteger); + create_field(layer, "evilness", OFTInteger); + create_field(layer, "savagery", OFTInteger); + create_field(layer, "region_id", OFTInteger); + create_field(layer, "region_name_en", OFTString, 100); + create_field(layer, "region_name_df", OFTString, 100); + create_field(layer, "landmass_id", OFTInteger); + create_field(layer, "volcanism", OFTInteger); + create_field(layer, "drainage", OFTInteger); + }catch (const DFHack::command_result& r) { + out.printerr("could not create fields for output layer"); + return r; + } + + #define REGION 1 + #ifndef REGION + for (int x = 0; x < world->world_data->world_width; ++x) { + for (int y = 0; y < world->world_data->world_height; ++y) { + // auto& entry = world->world_data->region_map[x][y]; + + auto feature = OGRFeature::CreateFeature( layer->GetLayerDefn() ); + setGeometry(feature, x, y, 1.0); + + auto biome = ENUM_KEY_STR(biome_type, Maps::getBiomeType(x,y)); + feature->SetField( "biome_type", biome.c_str() ); + + // updates the feature with the id it receives in the layer + if( layer->CreateFeature( feature ) != OGRERR_NONE ) + return CR_FAILURE; + + OGRFeature::DestroyFeature( feature ); + } + }Maps::getBiomeType(world_x + x_offset,world_y + y_offset)); + #else + int wdim = 768; // dimension of a world tile + int rdim = 48; // dimension of a region tile + for (auto const region_details : world->world_data->midmap_data.region_details) { + auto world_x = region_details->pos.x; + auto world_y = region_details->pos.y; + for (int region_x = 0; region_x < 16; ++region_x) { + for (int region_y = 0; region_y < 16; ++region_y) { + + auto feature = OGRFeature::CreateFeature( layer->GetLayerDefn() ); + setGeometry( + feature, + (double)(world_x * wdim + region_x * rdim), + (double)(world_y * wdim + region_y * rdim), + rdim + ); + + // get information from the region details + auto [biome_x,biome_y] = get_world_index(world_x, world_y, region_details->biome[region_x][region_y]); + feature->SetField( "biome_type", ENUM_KEY_STR(biome_type,Maps::getBiomeType(biome_x,biome_y)).c_str() ); + feature->SetField( "elevation", region_details->elevation[region_x][region_y]); + + // gets supplementary information from the world tile + auto& world_entry = world->world_data->region_map[biome_x][biome_y]; + #define SET_FIELD(name) feature->SetField( #name, world_entry.name) + SET_FIELD(evilness); + SET_FIELD(savagery); + SET_FIELD(region_id); + SET_FIELD(landmass_id); + SET_FIELD(volcanism); + SET_FIELD(drainage); + #undef SET_FIELD + + auto region = df::world_region::find(world_entry.region_id); + if (region) { + auto region_name_en = DF2UTF(Translation::translateName(®ion->name, true)); + feature->SetField( "region_name_en", region_name_en.c_str()); + auto region_name_df = DF2UTF(Translation::translateName(®ion->name, false)); + feature->SetField( "region_name_df", region_name_df.c_str()); + } + + // this updates the feature with the id it receives in the layer + if( layer->CreateFeature( feature ) != OGRERR_NONE ) + return CR_FAILURE; + // but we don't care and destroy the feature + OGRFeature::DestroyFeature( feature ); + } + } + } + #endif + + GDALClose( dataset ); + out.print("done!\n"); + return CR_OK; +} From d05b2df74862d3eae54552f29ec5f5c537820768 Mon Sep 17 00:00:00 2001 From: Squid Coder <92821989+realSquidCoder@users.noreply.github.com> Date: Sun, 23 Feb 2025 13:33:36 -0600 Subject: [PATCH 07/34] Fix spectate.lua (#5300) fix the follow panel hitbox --- plugins/lua/spectate.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/lua/spectate.lua b/plugins/lua/spectate.lua index b3b81ef79a..dbe80ac958 100644 --- a/plugins/lua/spectate.lua +++ b/plugins/lua/spectate.lua @@ -601,7 +601,7 @@ function FollowPanelOverlay:init() on_click=spectate_followNext, }, widgets.Label{ - frame={l=10, t=0, w=25}, + frame={l=10, t=0, w=14}, text={ ' spectate:', {text=function() return isEnabled() and ' on ' or 'off ' end, From a7a55f4b0eb3765296ea0dbe67c51bd650d5d9bf Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Sun, 23 Feb 2025 23:10:33 +0100 Subject: [PATCH 08/34] spectate.lua: refactor `set_setting()` --- plugins/lua/spectate.lua | 97 +++++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 47 deletions(-) diff --git a/plugins/lua/spectate.lua b/plugins/lua/spectate.lua index dbe80ac958..d70efc6238 100644 --- a/plugins/lua/spectate.lua +++ b/plugins/lua/spectate.lua @@ -167,61 +167,64 @@ local function set_setting(args) if n == 0 then qerror('missing key') end - local key = table.remove(args, 1) - if config[key] == nil then - qerror('unknown setting: ' .. key) - end - n = #args - if n == 0 - or (n == 1 and type(config[key]) == 'table') - then - qerror('missing value') - end - if n == 1 then - local value = args[1] - if key == 'follow-seconds' then - value = argparse.positiveInt(value, 'follow-seconds') - elseif key == 'tooltip-follow-blink-milliseconds' then - value = argparse.nonnegativeInt(value, 'tooltip-follow-blink-milliseconds') + local cfg = config + local v + for i = 1, n do + v = cfg[args[i]] + if v == nil then + -- probably an unknown option, but we may allow adding new keys + break + elseif type(v) == 'table' then + if i == n then + -- arrived at the very last argument, but have a table + qerror('missing value for ' .. table.concat(args, '/', 1, i)) + end + cfg = v else - value = argparse.boolean(value, key) - end - - config[key] = value - - if not key:startswith(lua_only_settings_prefix) then - if type(value) == 'boolean' then - value = value and 1 or 0 + -- arrived at something that's not a table + if i == n-1 then + -- if there is exactly 1 argument left, we're good + break + elseif i == n then + qerror('missing value for ' .. table.concat(args, '/', 1, i)) + else -- i < n-1 then + qerror('too many arguments for ' .. table.concat(args, '/', 1, i)) end - spectate_setSetting(key, value) end - else - local errorUnknownSettingIfNil = function(t) - if t == nil then - table.remove(args) - qerror('unknown setting: ' .. key .. '/' .. table.concat(args, '/')) - end + end + if v == nil then + if n == 3 and args[1] == 'tooltip-follow-job-shortenings' then + -- user should be able to add new shortenings, but not other things + else + qerror('unknown option: ' .. table.concat(args, '/', 1, i)) end + end - local t = config[key] - for i = 1, n - 2 do - errorUnknownSettingIfNil(t) - t = t[args[i]] - end - local k = args[n-1] - local v = args[n] - if key ~= 'tooltip-follow-job-shortenings' then - -- user should be able to add new shortenings, but not other things - errorUnknownSettingIfNil(t[k]) - if key:endswith('-stress-levels') and key ~= 'tooltip-stress-levels' then - v = argparse.boolean(v, key .. '/' .. k) - end + local path = table.concat(args, '/', 1, n-1) + local key = args[n-1] + local value = args[n] + local entry_type = type(cfg[key]) + if entry_type == 'table' then + -- here just in case, is already checked in the loop above + qerror('missing value for ' .. path) + elseif entry_type == 'boolean' then + value = argparse.boolean(value, path) + elseif entry_type == 'number' then + if path == 'follow-seconds' then + value = argparse.positiveInt(value, path) + else + value = argparse.nonnegativeInt(value, path) end - if type(t[k]) == 'table' then - qerror('missing value') + end + + cfg[key] = value + + if n == 2 and not key:startswith(lua_only_settings_prefix) then + if type(value) == 'boolean' then + value = value and 1 or 0 end - t[k] = v + spectate_setSetting(key, value) end save_state() From 1b590600c763293e5269f1d20cde40576eee05e0 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 25 Feb 2025 09:46:34 -0800 Subject: [PATCH 09/34] bump version and changelog to 51.06 --- CMakeLists.txt | 2 +- docs/changelog.txt | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c58b36bf2..f102c35554 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_policy(SET CMP0048 NEW) cmake_policy(SET CMP0074 NEW) # set up versioning. -set(DF_VERSION "51.05") +set(DF_VERSION "51.06") set(DFHACK_RELEASE "r1") set(DFHACK_PRERELEASE FALSE) diff --git a/docs/changelog.txt b/docs/changelog.txt index 4467585a8b..774b93e67d 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -68,10 +68,16 @@ Template for new versions: ## Removed +# 51.06-r1 + +## Misc Improvements +- Compatibility with DF 51.06 + # 51.05-r1 ## Misc Improvements - Compatibility with DF 51.05 + # 51.04-r1.1 ## Fixes From bb4cdc8a1006a4b65469a19b2101bd3d6057dce9 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Tue, 25 Feb 2025 09:59:51 -0800 Subject: [PATCH 10/34] update xml ref for 51.06 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 5513dc1ffa..62706ab9a0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 5513dc1ffa70d567d87ce946a0c57ce4760fae7c +Subproject commit 62706ab9a0b4b573ebbcbdf5c50dcb157a479f2a From 7652b346370b1c167111af4eae18ea9ca9681181 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Wed, 26 Feb 2025 07:17:39 +0000 Subject: [PATCH 11/34] Auto-update submodules plugins/stonesense: master --- plugins/stonesense | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/stonesense b/plugins/stonesense index 7badeff963..af8cf85f0c 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 7badeff963b0494e2e79e49a0dd3555961df5f04 +Subproject commit af8cf85f0c2a772439a441120eac246123afdc6a From 70cc6d4b3c95e429eaf5f2ecd2475fb1ca357c1d Mon Sep 17 00:00:00 2001 From: myk002 <977482+myk002@users.noreply.github.com> Date: Wed, 26 Feb 2025 15:51:13 +0000 Subject: [PATCH 12/34] Auto-update structures ref for 51.06 --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 62706ab9a0..9d207f7b70 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 62706ab9a0b4b573ebbcbdf5c50dcb157a479f2a +Subproject commit 9d207f7b70c7ecf3835b6ca7506e598622d29d04 From e71649449edb2b470cf15199d126db44233ff42f Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Wed, 26 Feb 2025 17:14:47 +0100 Subject: [PATCH 13/34] spectate.lua: minor simplification of `render_unit_banners()` --- plugins/lua/spectate.lua | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/lua/spectate.lua b/plugins/lua/spectate.lua index d70efc6238..e255989117 100644 --- a/plugins/lua/spectate.lua +++ b/plugins/lua/spectate.lua @@ -482,10 +482,10 @@ function TooltipOverlay:render_unit_banners(dc) for _, tok in ipairs(info) do local s if type(tok) == "string" then - writer = writer:pen(pen) + writer:pen(pen) s = tok else - writer = writer:pen(tok.pen) + writer:pen(tok.pen) s = tok.text end @@ -496,14 +496,15 @@ function TooltipOverlay:render_unit_banners(dc) -- we want to replace it with an `_`, so we need another `- 1` s = s:sub(1, usedAt - len - ix - 1 - 1) .. '_' - writer = writer:string(s) + writer:string(s) break -- nothing more will fit else - writer = writer:string(s) + writer:string(s) end ix = ix + len end + writer:pen(pen) -- just in case a different dc isn't resetting it -- mark coordinates as used for j = 0, #str - 1 do From 28cb1a0d19cce49b62b1862b9a1b169fd058fd46 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Wed, 26 Feb 2025 17:17:31 +0100 Subject: [PATCH 14/34] Update spectate.lua --- plugins/lua/spectate.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/lua/spectate.lua b/plugins/lua/spectate.lua index e255989117..7be670802f 100644 --- a/plugins/lua/spectate.lua +++ b/plugins/lua/spectate.lua @@ -504,7 +504,6 @@ function TooltipOverlay:render_unit_banners(dc) ix = ix + len end - writer:pen(pen) -- just in case a different dc isn't resetting it -- mark coordinates as used for j = 0, #str - 1 do From c96f1080f3ceb94af14afa94b0591f29d674abf3 Mon Sep 17 00:00:00 2001 From: Kelly Kinkade Date: Wed, 26 Feb 2025 11:46:35 -0600 Subject: [PATCH 15/34] add `std::abort` falllthrough to `field_error` this should not be required, but `luaL_error` is not marked `[[noreturn]]` even though it reliably does not return, and i don't want to alter the lua module any more than absolutely necessary, so we do this here instead --- library/LuaWrapper.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/library/LuaWrapper.cpp b/library/LuaWrapper.cpp index 892a6784cf..f9fd0e7832 100644 --- a/library/LuaWrapper.cpp +++ b/library/LuaWrapper.cpp @@ -59,6 +59,7 @@ using namespace DFHack::LuaWrapper; const char *fname = index ? lua_tostring(state, index) : "*"; luaL_error(state, "Cannot %s field %s.%s: %s.", mode, (cname ? cname : "?"), (fname ? fname : "?"), err); + std::abort(); // should never be reached but makes gcc happy } /* */ From f3e3570b990b6298fc760802434aa1f5484483c4 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 26 Feb 2025 11:00:42 -0800 Subject: [PATCH 16/34] add experimental as a symbols target --- .github/workflows/generate-symbols.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/generate-symbols.yml b/.github/workflows/generate-symbols.yml index 85a0067c85..9f4e32b146 100644 --- a/.github/workflows/generate-symbols.yml +++ b/.github/workflows/generate-symbols.yml @@ -36,6 +36,7 @@ on: default: default options: - default + - experimental - testing - adventure_test - beta From 4de298941b2904cee57e590212313951bcabbeca Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 26 Feb 2025 12:27:00 -0800 Subject: [PATCH 17/34] turn on autopush for df experimental branch --- .github/workflows/watch-df-release.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/watch-df-release.yml b/.github/workflows/watch-df-release.yml index 6e72333034..77f4947781 100644 --- a/.github/workflows/watch-df-release.yml +++ b/.github/workflows/watch-df-release.yml @@ -15,13 +15,17 @@ jobs: fail-fast: false matrix: # df_steam_branch: which DF Steam branch to watch - # platform: leave blank to default to all + # platform: for symbols generation; leave blank to default to all # structures_ref: leave blank to default to master # dfhack_ref: leave blank if no structures update is desired # steam_branch: leave blank if no DFHack steam push is desired include: - df_steam_branch: public - df_steam_branch: beta + - df_steam_branch: experimental + structures_ref: experimental + dfhack_ref: experimental + steam_branch: experimental steps: - name: Fetch state uses: actions/cache/restore@v4 From 15b72512252d00eee2c22b26a73ce0394f5095c7 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Wed, 26 Feb 2025 18:35:54 -0800 Subject: [PATCH 18/34] update xml ref --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 36117efc6b..79154f3e8f 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 36117efc6b233ade089dcf053fe996a04e06e7ef +Subproject commit 79154f3e8f7699f1630c9b9601ebd22f96116024 From 66f58df8fad5b7fc38e6d2a6ddb3a167560e4bcf Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Thu, 27 Feb 2025 07:17:49 +0000 Subject: [PATCH 19/34] Auto-update submodules library/xml: master --- library/xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/xml b/library/xml index 79154f3e8f..f5ca9135f0 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 79154f3e8f7699f1630c9b9601ebd22f96116024 +Subproject commit f5ca9135f09d0b13a23e5394b563609417d568c5 From 12cb12506d0857fc1dda0ffaf8e78352e69fa56b Mon Sep 17 00:00:00 2001 From: Quietust Date: Fri, 28 Feb 2025 10:30:48 -0600 Subject: [PATCH 20/34] Update item value functions. Fixes #5313 --- docs/changelog.txt | 2 ++ library/modules/Items.cpp | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 1995e9d1d3..612d5f9f70 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -79,6 +79,8 @@ Template for new versions: - ``Military::removeFromSquad``: removes unit from any squad assignments - ``Buildings::checkFreeTiles``: now replaces the extents parameter for a building pointer - ``Units::isUnitInBox``, ``Units::getUnitsInBox``: don't include inactive units +- ``Items::getItemBaseValue``: DF 51.06 reduced the base value of prepared meals +- ``Items::getValue``: magical powers now correctly contribute to item value ## Lua - ``dfhack.units.setAutomaticProfessions``: sets unit labors according to current work detail settings diff --git a/library/modules/Items.cpp b/library/modules/Items.cpp index a99c2bb761..ff92b3cece 100644 --- a/library/modules/Items.cpp +++ b/library/modules/Items.cpp @@ -64,6 +64,7 @@ distribution. #include "df/historical_entity.h" #include "df/item.h" #include "df/item_bookst.h" +#include "df/item_magicalst.h" #include "df/item_plant_growthst.h" #include "df/item_toolst.h" #include "df/item_type.h" @@ -1238,7 +1239,7 @@ int Items::getItemBaseValue(int16_t item_type, int16_t item_subtype, value = craw->misc.petvalue; return value; case FOOD: - return 10; + return 1; case CORPSE: case CORPSEPIECE: case REMAINS: @@ -1645,7 +1646,10 @@ int Items::getValue(df::item *item, df::caravan_state *caravan) { case 3: value = value / 4; break; // (XX) tattered } - // Ignore value bonuses from magic, since that never actually happens + // Magical powers have 500 value each + auto magic = item->getMagic(); + if (magic != NULL) + value += magic->power.size() * 500; // Artifacts have 10x value if (item->flags.bits.artifact_mood) From 88f23737dfcd9a11d12f9ff977f22f7d3529d393 Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 28 Feb 2025 10:10:10 -0800 Subject: [PATCH 21/34] remove now-unused binpatches but update docs and keep structure for future binpatches --- CMakeLists.txt | 1 - data/CMakeLists.txt | 4 ++++ data/patches/README.md | 13 +++++++++++++ docs/changelog.txt | 1 - docs/dev/Binpatches.rst | 14 ++++++++++++-- patches/CMakeLists.txt | 3 --- .../disable-vanilla-dimensions.dif | 6 ------ .../disable-vanilla-dimensions.dif | 6 ------ .../disable-vanilla-dimensions.dif | 6 ------ .../disable-vanilla-dimensions.dif | 6 ------ .../disable-vanilla-dimensions.dif | 6 ------ .../disable-vanilla-dimensions.dif | 6 ------ 12 files changed, 29 insertions(+), 43 deletions(-) create mode 100644 data/patches/README.md delete mode 100644 patches/CMakeLists.txt delete mode 100644 patches/v0.51.05 linux64 CLASSIC/disable-vanilla-dimensions.dif delete mode 100644 patches/v0.51.05 linux64 ITCH/disable-vanilla-dimensions.dif delete mode 100644 patches/v0.51.05 linux64 STEAM/disable-vanilla-dimensions.dif delete mode 100644 patches/v0.51.05 win64 CLASSIC/disable-vanilla-dimensions.dif delete mode 100644 patches/v0.51.05 win64 ITCH/disable-vanilla-dimensions.dif delete mode 100644 patches/v0.51.05 win64 STEAM/disable-vanilla-dimensions.dif diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e927a295d..f102c35554 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -451,7 +451,6 @@ if(INSTALL_DATA_FILES) install(FILES LICENSE.rst DESTINATION ${DFHACK_USERDOC_DESTINATION}) install(FILES docs/changelog-placeholder.txt DESTINATION ${DFHACK_USERDOC_DESTINATION} RENAME changelog.txt) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/depends/luacov/src/luacov/ DESTINATION ${DFHACK_DATA_DESTINATION}/lua/luacov) - install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/patches/ DESTINATION ${DFHACK_DATA_DESTINATION}/patches) endif() if(INSTALL_SCRIPTS) diff --git a/data/CMakeLists.txt b/data/CMakeLists.txt index 355182bfb7..75146071b8 100644 --- a/data/CMakeLists.txt +++ b/data/CMakeLists.txt @@ -24,6 +24,10 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/blueprints/ FILES_MATCHING PATTERN "*" PATTERN blueprints/test EXCLUDE) +install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/patches/ + DESTINATION ${DFHACK_DATA_DESTINATION}/patches +) + if(BUILD_TESTS) install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/blueprints/test/ DESTINATION "${DFHACK_DATA_DESTINATION}/data/blueprints/test") diff --git a/data/patches/README.md b/data/patches/README.md new file mode 100644 index 0000000000..599bbf47fb --- /dev/null +++ b/data/patches/README.md @@ -0,0 +1,13 @@ +Place IDA-exported `.dif` files for use by `binpatch` in subdirectories of this +directory. Each `.dif` file must be in a subdirectory named after the full +symbol table version string. For example, for DF version 51.05, you would use +these subdirectories: + +- "v0.51.05 linux64 CLASSIC" +- "v0.51.05 linux64 ITCH" +- "v0.51.05 linux64 STEAM" +- "v0.51.05 win64 CLASSIC" +- "v0.51.05 win64 ITCH" +- "v0.51.05 win64 STEAM" + +See https://docs.dfhack.org/en/stable/docs/dev/Binpatches.html for more details. diff --git a/docs/changelog.txt b/docs/changelog.txt index 612d5f9f70..f67755852f 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -58,7 +58,6 @@ Template for new versions: - `spectate`: new "cinematic-action" mode that dynamically speeds up perspective switches based on intensity of conflict - `spectate`: new global keybinding for toggling spectate mode: Ctrl-Shift-S - `spectate`: new overlay panel that allows you to cycle through following next/prevous units (regardless of whether spectate mode is enabled) -- ``disable-vanilla-dimensions``: new binpatch to disable the vanilla dimensions tooltip - `gui/sitemap`: is now the official "go to" tool. new global hotkey for fort and adventure mode: Ctrl-G ## Fixes diff --git a/docs/dev/Binpatches.rst b/docs/dev/Binpatches.rst index a566576168..d59b1f8482 100644 --- a/docs/dev/Binpatches.rst +++ b/docs/dev/Binpatches.rst @@ -50,10 +50,20 @@ directly in memory at runtime:: If the name of the patch has no extension or directory separators, the script uses :file:`hack/patches//.dif`, thus auto-selecting -the version appropriate for the currently loaded executable. +the version appropriate for the currently loaded executable. The ``df-version`` +is the version string in the loaded symbol table. For example, if you want to +make a patch for all distributed verisons of DF 51.05, you'd provide a ``dif`` +file in each of the following directories: + +- :file:`hack/patches/v0.51.05 linux64 CLASSIC/mypatch.dif` +- :file:`hack/patches/v0.51.05 linux64 ITCH/mypatch.dif` +- :file:`hack/patches/v0.51.05 linux64 STEAM/mypatch.dif` +- :file:`hack/patches/v0.51.05 win64 CLASSIC/mypatch.dif` +- :file:`hack/patches/v0.51.05 win64 ITCH/mypatch.dif` +- :file:`hack/patches/v0.51.05 win64 STEAM/mypatch.dif` This is the preferred method; it's easier to debug, does not cause persistent -problems, and leaves file checksums alone. As with many other commands, users +problems, and leaves file checksums alone. As with many other commands, users can simply add it to `dfhack.init` to reapply the patch every time DF is run. diff --git a/patches/CMakeLists.txt b/patches/CMakeLists.txt deleted file mode 100644 index 37ee7c9440..0000000000 --- a/patches/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -install(DIRECTORY ${patchdirectories} - DESTINATION ${DFHACK_DATA_DESTINATION}/patches -) diff --git a/patches/v0.51.05 linux64 CLASSIC/disable-vanilla-dimensions.dif b/patches/v0.51.05 linux64 CLASSIC/disable-vanilla-dimensions.dif deleted file mode 100644 index f8c95a2af6..0000000000 --- a/patches/v0.51.05 linux64 CLASSIC/disable-vanilla-dimensions.dif +++ /dev/null @@ -1,6 +0,0 @@ -Dwarf_Fortress -011ba9eb: e8 90 -011ba9ec: c0 90 -011ba9ed: 4a 90 -011ba9ee: e6 90 -011ba9ef: fe 90 diff --git a/patches/v0.51.05 linux64 ITCH/disable-vanilla-dimensions.dif b/patches/v0.51.05 linux64 ITCH/disable-vanilla-dimensions.dif deleted file mode 100644 index 1784576881..0000000000 --- a/patches/v0.51.05 linux64 ITCH/disable-vanilla-dimensions.dif +++ /dev/null @@ -1,6 +0,0 @@ -Dwarf_Fortress -011c605b: e8 90 -011c605c: 50 90 -011c605d: a5 90 -011c605e: e5 90 -011c605f: fe 90 diff --git a/patches/v0.51.05 linux64 STEAM/disable-vanilla-dimensions.dif b/patches/v0.51.05 linux64 STEAM/disable-vanilla-dimensions.dif deleted file mode 100644 index 142e53e4fa..0000000000 --- a/patches/v0.51.05 linux64 STEAM/disable-vanilla-dimensions.dif +++ /dev/null @@ -1,6 +0,0 @@ -Dwarf_Fortress -011c9c3b: e8 90 -011c9c3c: 80 90 -011c9c3d: 6a 90 -011c9c3e: e5 90 -011c9c3f: fe 90 diff --git a/patches/v0.51.05 win64 CLASSIC/disable-vanilla-dimensions.dif b/patches/v0.51.05 win64 CLASSIC/disable-vanilla-dimensions.dif deleted file mode 100644 index 2f5cf9e0ae..0000000000 --- a/patches/v0.51.05 win64 CLASSIC/disable-vanilla-dimensions.dif +++ /dev/null @@ -1,6 +0,0 @@ -Dwarf_Fortress -377d5d: e8 90 -377d5e: ee 90 -377d5f: 93 90 -377d60: 68 90 -377d61: 00 90 diff --git a/patches/v0.51.05 win64 ITCH/disable-vanilla-dimensions.dif b/patches/v0.51.05 win64 ITCH/disable-vanilla-dimensions.dif deleted file mode 100644 index e254650761..0000000000 --- a/patches/v0.51.05 win64 ITCH/disable-vanilla-dimensions.dif +++ /dev/null @@ -1,6 +0,0 @@ -Dwarf_Fortress -37af8d: e8 90 -37af8e: 1e 90 -37af8f: b7 90 -37af90: 68 90 -37af91: 00 90 diff --git a/patches/v0.51.05 win64 STEAM/disable-vanilla-dimensions.dif b/patches/v0.51.05 win64 STEAM/disable-vanilla-dimensions.dif deleted file mode 100644 index 44dd5ebb81..0000000000 --- a/patches/v0.51.05 win64 STEAM/disable-vanilla-dimensions.dif +++ /dev/null @@ -1,6 +0,0 @@ -Dwarf_Fortress -37d77d: e8 90 -37d77e: 6e 90 -37d77f: bb 90 -37d780: 68 90 -37d781: 00 90 From f4ff0cea2fabd579d182d6a6fb32164cd4e683bf Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 28 Feb 2025 10:30:40 -0800 Subject: [PATCH 22/34] add enable/disable hooks for widgets and update docs --- docs/changelog.txt | 1 + docs/dev/overlay-dev-guide.rst | 5 +++++ plugins/lua/overlay.lua | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/docs/changelog.txt b/docs/changelog.txt index f67755852f..254f3ea9e5 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -87,6 +87,7 @@ Template for new versions: - ``gui.dwarfmode``: adventure mode cursor now supported in ``getCursorPos``, ``setCursorPos``, and ``clearCursorPos`` funcitons - ``dfhack.buildings.checkFreeTiles``: now replaces the extents parameter for a building pointer - ``overlay.isOverlayEnabled``: new API for querying whether a given overlay is enabled +- ``overlay``: widgets can now declare ``overlay_onenable`` and ``overlay_ondisable`` functions to hook enable/disable ## Removed diff --git a/docs/dev/overlay-dev-guide.rst b/docs/dev/overlay-dev-guide.rst index 4302d5d4c8..d87178bfff 100644 --- a/docs/dev/overlay-dev-guide.rst +++ b/docs/dev/overlay-dev-guide.rst @@ -86,6 +86,11 @@ beyond your everyday `widgets.Widget `: end This allows for dynamic updates to UI overlays directly from the CLI. +- If an ``overlay_onenable()`` function is defined, it is called when the + overlay is enabled (including when the persisted state is reloaded at DF + startup). +- If an ``overlay_ondisable()`` function is defined, it is called when the + overlay is disabled. If the widget can take up a variable amount of space on the screen, and you want the widget to adjust its position according to the size of its contents, you can diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index 77df7d47a4..c93f8ed7bd 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -171,6 +171,9 @@ local function do_enable(args, quiet, skip_save) vs_name = normalize_viewscreen_name(vs_name) ensure_key(active_viewscreen_widgets, vs_name)[name] = db_entry end + if db_entry.widget.overlay_onenable then + db_entry.widget.overlay_onenable() + end if not quiet then print(('enabled widget %s'):format(name)) end @@ -202,6 +205,9 @@ local function do_disable(args, quiet) active_viewscreen_widgets[vs_name] = nil end end + if db_entry.widget.overlay_ondisable then + db_entry.widget.overlay_ondisable() + end if not quiet then print(('disabled widget %s'):format(name)) end From a224c5958a7effae46e8c2dfb04e8bd823e5d5fe Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 28 Feb 2025 10:47:41 -0800 Subject: [PATCH 23/34] reset draw context between rendering widgets --- docs/changelog.txt | 1 + plugins/lua/overlay.lua | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 254f3ea9e5..2352db036b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -65,6 +65,7 @@ Template for new versions: - `createitem`: output items will now end up at look cursor if active - `spectate`: don't allow temporarily modified announcement settings to be written to disk when "auto-unpause" mode is enabled - `changevein`: fix a crash that could occur when attempting to change a vein into itself +- `overlay`: reset draw context between rendering widgets so context changes can't propagate from widget to widget ## Misc Improvements - `spectate`: player-set configuration is now stored globally instead of per-fort diff --git a/plugins/lua/overlay.lua b/plugins/lua/overlay.lua index c93f8ed7bd..c1d1bcb434 100644 --- a/plugins/lua/overlay.lua +++ b/plugins/lua/overlay.lua @@ -538,16 +538,16 @@ function feed_viewscreen_widgets(vs_name, vs, keys) return true end -local function _render_viewscreen_widgets(vs_name, vs, full_dc, scaled_dc) +local function _render_viewscreen_widgets(vs_name, vs) local vs_widgets = active_viewscreen_widgets[vs_name] if not vs_widgets then return end local full, scaled = get_interface_rects() - full_dc = full_dc or gui.Painter.new(full) - scaled_dc = scaled_dc or gui.Painter.new(scaled) for _,db_entry in pairs(vs_widgets) do local w = db_entry.widget if (not vs or matches_focus_strings(db_entry, vs_name, vs)) and utils.getval(w.visible) then - detect_frame_change(w, function() w:render(w.fullscreen and full_dc or scaled_dc) end) + detect_frame_change(w, function() + w:render(w.fullscreen and gui.Painter.new(full) or gui.Painter.new(scaled)) + end) end end return full_dc, scaled_dc @@ -556,8 +556,8 @@ end local force_refresh function render_viewscreen_widgets(vs_name, vs) - local full_dc, scaled_dc = _render_viewscreen_widgets(vs_name, vs, nil, nil) - _render_viewscreen_widgets('all', nil, full_dc, scaled_dc) + _render_viewscreen_widgets(vs_name, vs) + _render_viewscreen_widgets('all', nil) if force_refresh then force_refresh = nil df.global.gps.force_full_display_count = 1 From eee505b4a556a7459f94016a015ddf2c156eadea Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Fri, 28 Feb 2025 18:57:14 +0000 Subject: [PATCH 24/34] Auto-update submodules scripts: master plugins/stonesense: master --- plugins/stonesense | 2 +- scripts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/stonesense b/plugins/stonesense index af8cf85f0c..30b3d1f1ce 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit af8cf85f0c2a772439a441120eac246123afdc6a +Subproject commit 30b3d1f1cefd5773fb9e10c49c89dba937c0f58c diff --git a/scripts b/scripts index 557ac5732e..72b6578e90 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 557ac5732e87d62f3b7970295322ae0c5f7eae2c +Subproject commit 72b6578e90f9abc91d31faf92d7aa9d4c6f978ae From b300afcb07dc9633f8f4d45add1d9a4528068d1d Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 28 Feb 2025 11:21:39 -0800 Subject: [PATCH 25/34] changelog editing pass and update refs --- docs/changelog.txt | 8 ++++---- library/xml | 2 +- plugins/stonesense | 2 +- scripts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index 2352db036b..496ef9febe 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -62,7 +62,7 @@ Template for new versions: ## Fixes - Windows console: fix possible hang if the console returns a too-small window width (for any reason) -- `createitem`: output items will now end up at look cursor if active +- `createitem`: produced items will now end up at the look cursor position (if it is active) - `spectate`: don't allow temporarily modified announcement settings to be written to disk when "auto-unpause" mode is enabled - `changevein`: fix a crash that could occur when attempting to change a vein into itself - `overlay`: reset draw context between rendering widgets so context changes can't propagate from widget to widget @@ -77,16 +77,16 @@ Template for new versions: ## API - ``Military::removeFromSquad``: removes unit from any squad assignments -- ``Buildings::checkFreeTiles``: now replaces the extents parameter for a building pointer +- ``Buildings::checkFreeTiles``: now takes a building instead of a pointer to the building extents - ``Units::isUnitInBox``, ``Units::getUnitsInBox``: don't include inactive units -- ``Items::getItemBaseValue``: DF 51.06 reduced the base value of prepared meals +- ``Items::getItemBaseValue``: adjust to the reduced value of prepared meals (changed in DF 51.06) - ``Items::getValue``: magical powers now correctly contribute to item value ## Lua - ``dfhack.units.setAutomaticProfessions``: sets unit labors according to current work detail settings - ``dfhack.military.removeFromSquad``: Lua API for ``Military::removeFromSquad`` - ``gui.dwarfmode``: adventure mode cursor now supported in ``getCursorPos``, ``setCursorPos``, and ``clearCursorPos`` funcitons -- ``dfhack.buildings.checkFreeTiles``: now replaces the extents parameter for a building pointer +- ``dfhack.buildings.checkFreeTiles``: now takes a building pointer instead of an extents parameter - ``overlay.isOverlayEnabled``: new API for querying whether a given overlay is enabled - ``overlay``: widgets can now declare ``overlay_onenable`` and ``overlay_ondisable`` functions to hook enable/disable diff --git a/library/xml b/library/xml index f5ca9135f0..f5a63e87b3 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit f5ca9135f09d0b13a23e5394b563609417d568c5 +Subproject commit f5a63e87b3d96c5c435d8737e95a70151bd218c1 diff --git a/plugins/stonesense b/plugins/stonesense index 30b3d1f1ce..8fdc2fdb49 160000 --- a/plugins/stonesense +++ b/plugins/stonesense @@ -1 +1 @@ -Subproject commit 30b3d1f1cefd5773fb9e10c49c89dba937c0f58c +Subproject commit 8fdc2fdb4939fda074b50626381a43581b0b8479 diff --git a/scripts b/scripts index 72b6578e90..108e5e9a37 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 72b6578e90f9abc91d31faf92d7aa9d4c6f978ae +Subproject commit 108e5e9a3774a57758404704c706361d2b1745d9 From 10e680794350bde96c4bf1a32e4193f2ccc813dc Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 28 Feb 2025 11:28:29 -0800 Subject: [PATCH 26/34] bump version to 51.06-r2rc1 --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f102c35554..22f04fe085 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,8 +7,8 @@ cmake_policy(SET CMP0074 NEW) # set up versioning. set(DF_VERSION "51.06") -set(DFHACK_RELEASE "r1") -set(DFHACK_PRERELEASE FALSE) +set(DFHACK_RELEASE "r2rc1") +set(DFHACK_PRERELEASE TRUE) set(DFHACK_VERSION "${DF_VERSION}-${DFHACK_RELEASE}") set(DFHACK_ABI_VERSION 2) From 8eb563a889d7bb4117d105003a47e13f9332c7cb Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 28 Feb 2025 13:00:39 -0800 Subject: [PATCH 27/34] remove MakeCheese job from basic orders list defer to autocheese --- data/orders/basic.json | 21 --------------------- docs/changelog.txt | 1 + docs/plugins/orders.rst | 11 +++++++++-- 3 files changed, 10 insertions(+), 23 deletions(-) diff --git a/data/orders/basic.json b/data/orders/basic.json index 688bdaa7ab..e1cd5ac8ff 100644 --- a/data/orders/basic.json +++ b/data/orders/basic.json @@ -114,27 +114,6 @@ "job" : "CustomReaction", "reaction" : "BREW_DRINK_FROM_PLANT_GROWTH" }, - { - "amount_left" : 1, - "amount_total" : 1, - "frequency" : "Daily", - "id" : 3, - "is_active" : false, - "is_validated" : false, - "item_conditions" : - [ - { - "condition" : "AtLeast", - "flags" : - [ - "unrotten", - "milk" - ], - "value" : 2 - } - ], - "job" : "MakeCheese" - }, { "amount_left" : 1, "amount_total" : 1, diff --git a/docs/changelog.txt b/docs/changelog.txt index 496ef9febe..b4a39ba389 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -91,6 +91,7 @@ Template for new versions: - ``overlay``: widgets can now declare ``overlay_onenable`` and ``overlay_ondisable`` functions to hook enable/disable ## Removed +- `orders`: MakeCheese job removed from library/basic orders set. Please use `autocheese` instead! # 51.06-r1 diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index 9ac3009af8..a56e527466 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -145,7 +145,14 @@ This collection of orders handles basic fort necessities: You should import it as soon as you have enough dwarves to perform the tasks. Right after the first migration wave is usually a good time. -Note that the jugs are specifically made out of wood. This is so, as long as you don't may any other "Tools" out of wood, you can have a stockpile just for jugs by restricting a finished goods stockpile to only take wooden tools. +These orders do not contain milking, shearing, or cheesemaking jobs since the +game does not provide sufficient order conditions. Please enable `automilk`, +`autoshear`, and `autocheese` on the DFHack `gui/control-panel` for these types +of jobs. + +Note that the jugs are specifically made out of wood. This is so, as long as +you don't may any other "Tools" out of wood, you can have a stockpile just for +jugs by restricting a finished goods stockpile to only take wooden tools. Armok's additional note: "shleggings? Yes, `shleggings `__." @@ -155,7 +162,7 @@ Armok's additional note: "shleggings? Yes, This collection creates basic items that require heat. It is separated out from ``library/basic`` to give players the opportunity to set up magma furnaces first -in order to save resources. It handles: +(if desired) in order to save resources. It handles: - charcoal (including smelting of bituminous coal and lignite) - pearlash From 989359fb41e9a160fb5a163ba70c123f7c7a4e66 Mon Sep 17 00:00:00 2001 From: Timur Kelman Date: Fri, 28 Feb 2025 22:33:50 +0100 Subject: [PATCH 28/34] spectate.lua: do not write into top and bottom 4 rows over DF's UI This is a quick workaround. A proper solution would involve calculating sizes and locations of the DF's widgets, as well as visibility for some of them. Also, remove `local writer = dc`, just use `dc`. --- plugins/lua/spectate.lua | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins/lua/spectate.lua b/plugins/lua/spectate.lua index 7be670802f..0bf46cef2b 100644 --- a/plugins/lua/spectate.lua +++ b/plugins/lua/spectate.lua @@ -428,6 +428,8 @@ function TooltipOverlay:render_unit_banners(dc) local oneTileOffset = GetScreenCoordinates({x = topleft.x + 1, y = topleft.y + 1, z = topleft.z + 0}) local pen = COLOR_WHITE + local _, screenHeight = dfhack.screen:getWindowSize() + local used_tiles = {} -- reverse order yields better offsets for overlapping texts for i = #units, 1, -1 do @@ -447,6 +449,10 @@ function TooltipOverlay:render_unit_banners(dc) local y = scrPos.y - 1 -- subtract 1 to move the text over the heads local x = scrPos.x + oneTileOffset.x - 1 -- subtract 1 to move the text inside the map tile + -- do not write anything in the top rows, where DF's interface is. + -- todo: use precise rectangles + if y < 4 then goto continue end + -- to resolve overlaps, we'll mark every coordinate we write anything in, -- and then check if the new tooltip will overwrite any used coordinate. -- if it will, try the next row, to a maximum offset of 4. @@ -477,15 +483,19 @@ function TooltipOverlay:render_unit_banners(dc) -- we can't place any useful information, and will ignore it instead. if 0 <= usedAt and usedAt <= 2 then goto continue end - local writer = dc:seek(x, y + dy) + -- do not write anything over DF's interface + -- todo: use precise rectangles + if y + dy > screenHeight - 4 then goto continue end + + dc:seek(x, y + dy) local ix = 0 for _, tok in ipairs(info) do local s if type(tok) == "string" then - writer:pen(pen) + dc:pen(pen) s = tok else - writer:pen(tok.pen) + dc:pen(tok.pen) s = tok.text end @@ -496,10 +506,10 @@ function TooltipOverlay:render_unit_banners(dc) -- we want to replace it with an `_`, so we need another `- 1` s = s:sub(1, usedAt - len - ix - 1 - 1) .. '_' - writer:string(s) + dc:string(s) break -- nothing more will fit else - writer:string(s) + dc:string(s) end ix = ix + len From d161360944b8b73ffb2f2581036e34949b0d9804 Mon Sep 17 00:00:00 2001 From: DFHack-Urist via GitHub Actions <63161697+DFHack-Urist@users.noreply.github.com> Date: Sat, 1 Mar 2025 00:54:15 +0000 Subject: [PATCH 29/34] Auto-update submodules scripts: master --- scripts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts b/scripts index 108e5e9a37..e927734288 160000 --- a/scripts +++ b/scripts @@ -1 +1 @@ -Subproject commit 108e5e9a3774a57758404704c706361d2b1745d9 +Subproject commit e927734288e1676ccb6f25b96228719b79b48f13 From edbce6a8ff93375c50f62a331e3af059b3123b9f Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 28 Feb 2025 16:56:12 -0800 Subject: [PATCH 30/34] double quote not-commands --- docs/plugins/orders.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/plugins/orders.rst b/docs/plugins/orders.rst index a56e527466..378881d12c 100644 --- a/docs/plugins/orders.rst +++ b/docs/plugins/orders.rst @@ -146,9 +146,9 @@ You should import it as soon as you have enough dwarves to perform the tasks. Right after the first migration wave is usually a good time. These orders do not contain milking, shearing, or cheesemaking jobs since the -game does not provide sufficient order conditions. Please enable `automilk`, -`autoshear`, and `autocheese` on the DFHack `gui/control-panel` for these types -of jobs. +game does not provide sufficient order conditions. Please enable ``automilk``, +``autoshear``, and `autocheese` on the DFHack `gui/control-panel` for these +types of jobs. Note that the jugs are specifically made out of wood. This is so, as long as you don't may any other "Tools" out of wood, you can have a stockpile just for From e0889266c44f8d53ebec616056b24811de593dae Mon Sep 17 00:00:00 2001 From: Myk Taylor Date: Fri, 28 Feb 2025 18:17:09 -0800 Subject: [PATCH 31/34] fix changelog typo --- docs/changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/changelog.txt b/docs/changelog.txt index b4a39ba389..c883d4868b 100644 --- a/docs/changelog.txt +++ b/docs/changelog.txt @@ -57,7 +57,7 @@ Template for new versions: - `spectate`: can now specify number of seconds (in real time) before switching to follow a new unit - `spectate`: new "cinematic-action" mode that dynamically speeds up perspective switches based on intensity of conflict - `spectate`: new global keybinding for toggling spectate mode: Ctrl-Shift-S -- `spectate`: new overlay panel that allows you to cycle through following next/prevous units (regardless of whether spectate mode is enabled) +- `spectate`: new overlay panel that allows you to cycle through following next/previous units (regardless of whether spectate mode is enabled) - `gui/sitemap`: is now the official "go to" tool. new global hotkey for fort and adventure mode: Ctrl-G ## Fixes From 539652a97919bcd956aa0279ff6e1f7c20801b6e Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sat, 1 Mar 2025 16:14:37 +0100 Subject: [PATCH 32/34] more fields and execution timing --- plugins/export-map.cpp | 77 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/plugins/export-map.cpp b/plugins/export-map.cpp index 0a9336a259..79f2f233c1 100644 --- a/plugins/export-map.cpp +++ b/plugins/export-map.cpp @@ -11,12 +11,14 @@ #include "df/world_data.h" #include "df/region_map_entry.h" #include "df/world_region.h" +#include "df/world_landmass.h" #include "df/world_region_details.h" #include "gdal/ogrsf_frmts.h" #include #include +#include using std::string; using std::vector; @@ -47,7 +49,7 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addPoint(x,y); boundary->addPoint(x,y-dim); boundary->addPoint(x+dim,y-dim); @@ -92,8 +94,35 @@ auto create_field(OGRLayer *layer, std::string name, OGRFieldType type, int widt // PROJ.4 description of EPSG:3857 (https://epsg.io/3857) static const char* EPSG_3857 = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"; + + +const char* describe_surroundings(int savagery, int evilness) { + constexpr std::arraysurroundings{ + "Serene", "Mirthful", "Joyous Wilds", + "Calm", "Wilderness", "Untamed Wilds", + "Sinister", "Haunted", "Terrifying" + }; + auto savagery_index = savagery < 33 ? 0 : (savagery > 65 ? 2 : 1); + auto evilness_index = evilness < 33 ? 0 : (evilness > 65 ? 2 : 1); + return surroundings[3 * evilness_index + savagery_index]; +} + static command_result do_command(color_ostream &out, vector ¶meters) { + // we don't want DF to delete region details while we are iterating over them... + CoreSuspender suspend; + + if (!Core::getInstance().isWorldLoaded()){ + out.printerr("This command requires a world to be loaded\n"); + return CR_WRONG_USAGE; + } + + out.print("%lu out of %d region map tiles loaded\n", + world->world_data->midmap_data.region_details.size(), + world->world_data->world_width * world->world_data->world_height + ); out.print("exporting map... "); + out.flush(); + const auto start{std::chrono::steady_clock::now()}; // set up coordinate system OGRSpatialReference CRS; @@ -120,16 +149,30 @@ static command_result do_command(color_ostream &out, vector ¶meters) auto layer = dataset->CreateLayer( "world_biomes", &CRS, wkbPolygon, NULL ); try { - create_field(layer, "biome_type", OFTString, 32); - create_field(layer, "elevation", OFTInteger); - create_field(layer, "evilness", OFTInteger); - create_field(layer, "savagery", OFTInteger); create_field(layer, "region_id", OFTInteger); create_field(layer, "region_name_en", OFTString, 100); create_field(layer, "region_name_df", OFTString, 100); create_field(layer, "landmass_id", OFTInteger); + create_field(layer, "landmass_name_en", OFTString, 100); + create_field(layer, "landmass_name_df", OFTString, 100); + + create_field(layer, "biome_type", OFTString, 32); + create_field(layer, "surroundings", OFTString, 16); + create_field(layer, "elevation", OFTInteger); + + create_field(layer, "evilness", OFTInteger); + create_field(layer, "savagery", OFTInteger); create_field(layer, "volcanism", OFTInteger); create_field(layer, "drainage", OFTInteger); + create_field(layer, "temperature", OFTInteger); + create_field(layer, "vegetation", OFTInteger); + create_field(layer, "rainfall", OFTInteger); + create_field(layer, "snowfall", OFTInteger); + create_field(layer, "salinity", OFTInteger); + + create_field(layer, "reanimating", OFTInteger, OFSTBoolean); + create_field(layer, "has_bogeymen", OFTInteger, OFSTBoolean); + }catch (const DFHack::command_result& r) { out.printerr("could not create fields for output layer"); return r; @@ -179,20 +222,36 @@ static command_result do_command(color_ostream &out, vector ¶meters) // gets supplementary information from the world tile auto& world_entry = world->world_data->region_map[biome_x][biome_y]; #define SET_FIELD(name) feature->SetField( #name, world_entry.name) - SET_FIELD(evilness); - SET_FIELD(savagery); SET_FIELD(region_id); SET_FIELD(landmass_id); + SET_FIELD(evilness); + SET_FIELD(savagery); SET_FIELD(volcanism); SET_FIELD(drainage); + SET_FIELD(temperature); + SET_FIELD(vegetation); + SET_FIELD(rainfall); + SET_FIELD(snowfall); + SET_FIELD(salinity); #undef SET_FIELD + feature->SetField( "surroundings", describe_surroundings(world_entry.savagery, world_entry.evilness)); + auto region = df::world_region::find(world_entry.region_id); if (region) { auto region_name_en = DF2UTF(Translation::translateName(®ion->name, true)); feature->SetField( "region_name_en", region_name_en.c_str()); auto region_name_df = DF2UTF(Translation::translateName(®ion->name, false)); feature->SetField( "region_name_df", region_name_df.c_str()); + feature->SetField("reanimating", region->reanimating); + feature->SetField("has_bogeymen", region->has_bogeymen); + } + auto landmass = df::world_landmass::find(world_entry.landmass_id); + if (landmass) { + auto landmass_name_en = DF2UTF(Translation::translateName(&landmass->name, true)); + feature->SetField( "landmass_name_en", landmass_name_en.c_str()); + auto landmass_name_df = DF2UTF(Translation::translateName(&landmass->name, false)); + feature->SetField( "landmass_name_df", landmass_name_df.c_str()); } // this updates the feature with the id it receives in the layer @@ -206,6 +265,8 @@ static command_result do_command(color_ostream &out, vector ¶meters) #endif GDALClose( dataset ); - out.print("done!\n"); + const auto finish{std::chrono::steady_clock::now()}; + const std::chrono::duration elapsed_seconds{finish - start}; + out.print("done in %f ms !\n", elapsed_seconds.count()); return CR_OK; } From 4eded3458abcf2d8255141a19f33943a93c1ecd7 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Sat, 1 Mar 2025 19:01:07 +0100 Subject: [PATCH 33/34] cleanup --- plugins/export-map.cpp | 44 +++++++++++------------------------------- 1 file changed, 11 insertions(+), 33 deletions(-) diff --git a/plugins/export-map.cpp b/plugins/export-map.cpp index 79f2f233c1..7e6732369d 100644 --- a/plugins/export-map.cpp +++ b/plugins/export-map.cpp @@ -94,8 +94,6 @@ auto create_field(OGRLayer *layer, std::string name, OGRFieldType type, int widt // PROJ.4 description of EPSG:3857 (https://epsg.io/3857) static const char* EPSG_3857 = "+proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs"; - - const char* describe_surroundings(int savagery, int evilness) { constexpr std::arraysurroundings{ "Serene", "Mirthful", "Joyous Wilds", @@ -116,7 +114,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) return CR_WRONG_USAGE; } - out.print("%lu out of %d region map tiles loaded\n", + out.print("%lu / %d region map tiles loaded\n", world->world_data->midmap_data.region_details.size(), world->world_data->world_width * world->world_data->world_height ); @@ -135,8 +133,6 @@ static command_result do_command(color_ostream &out, vector ¶meters) GDALAllRegister(); const char *driver_name = "Parquet"; const char *extension = "parquet"; - // const char *driver_name = "GPKG"; - // const char *extension = "gpkg"; auto driver = GetGDALDriverManager()->GetDriverByName(driver_name); CHECK_NULL_POINTER(driver); @@ -170,36 +166,19 @@ static command_result do_command(color_ostream &out, vector ¶meters) create_field(layer, "snowfall", OFTInteger); create_field(layer, "salinity", OFTInteger); - create_field(layer, "reanimating", OFTInteger, OFSTBoolean); - create_field(layer, "has_bogeymen", OFTInteger, OFSTBoolean); + create_field(layer, "reanimating", OFTInteger, 0, OFSTBoolean); + create_field(layer, "has_bogeymen", OFTInteger, 0, OFSTBoolean); }catch (const DFHack::command_result& r) { out.printerr("could not create fields for output layer"); return r; } - #define REGION 1 - #ifndef REGION - for (int x = 0; x < world->world_data->world_width; ++x) { - for (int y = 0; y < world->world_data->world_height; ++y) { - // auto& entry = world->world_data->region_map[x][y]; - - auto feature = OGRFeature::CreateFeature( layer->GetLayerDefn() ); - setGeometry(feature, x, y, 1.0); - - auto biome = ENUM_KEY_STR(biome_type, Maps::getBiomeType(x,y)); - feature->SetField( "biome_type", biome.c_str() ); - - // updates the feature with the id it receives in the layer - if( layer->CreateFeature( feature ) != OGRERR_NONE ) - return CR_FAILURE; - - OGRFeature::DestroyFeature( feature ); - } - }Maps::getBiomeType(world_x + x_offset,world_y + y_offset)); - #else int wdim = 768; // dimension of a world tile int rdim = 48; // dimension of a region tile + + // iterating over the region details allows the user to do partial map exports + // by manually scrolling on the embark site selection for (auto const region_details : world->world_data->midmap_data.region_details) { auto world_x = region_details->pos.x; auto world_y = region_details->pos.y; @@ -220,8 +199,8 @@ static command_result do_command(color_ostream &out, vector ¶meters) feature->SetField( "elevation", region_details->elevation[region_x][region_y]); // gets supplementary information from the world tile - auto& world_entry = world->world_data->region_map[biome_x][biome_y]; - #define SET_FIELD(name) feature->SetField( #name, world_entry.name) + auto& region_map_entry = world->world_data->region_map[biome_x][biome_y]; + #define SET_FIELD(name) feature->SetField( #name, region_map_entry.name) SET_FIELD(region_id); SET_FIELD(landmass_id); SET_FIELD(evilness); @@ -235,9 +214,9 @@ static command_result do_command(color_ostream &out, vector ¶meters) SET_FIELD(salinity); #undef SET_FIELD - feature->SetField( "surroundings", describe_surroundings(world_entry.savagery, world_entry.evilness)); + feature->SetField( "surroundings", describe_surroundings(region_map_entry.savagery, region_map_entry.evilness)); - auto region = df::world_region::find(world_entry.region_id); + auto region = df::world_region::find(region_map_entry.region_id); if (region) { auto region_name_en = DF2UTF(Translation::translateName(®ion->name, true)); feature->SetField( "region_name_en", region_name_en.c_str()); @@ -246,7 +225,7 @@ static command_result do_command(color_ostream &out, vector ¶meters) feature->SetField("reanimating", region->reanimating); feature->SetField("has_bogeymen", region->has_bogeymen); } - auto landmass = df::world_landmass::find(world_entry.landmass_id); + auto landmass = df::world_landmass::find(region_map_entry.landmass_id); if (landmass) { auto landmass_name_en = DF2UTF(Translation::translateName(&landmass->name, true)); feature->SetField( "landmass_name_en", landmass_name_en.c_str()); @@ -262,7 +241,6 @@ static command_result do_command(color_ostream &out, vector ¶meters) } } } - #endif GDALClose( dataset ); const auto finish{std::chrono::steady_clock::now()}; From 87492a69b302b9825403f01ab96fd75e01e096f8 Mon Sep 17 00:00:00 2001 From: Christian Doczkal <20443222+chdoc@users.noreply.github.com> Date: Thu, 13 Mar 2025 18:30:37 +0100 Subject: [PATCH 34/34] export site data into a spatialite database --- plugins/export-map.cpp | 170 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 160 insertions(+), 10 deletions(-) diff --git a/plugins/export-map.cpp b/plugins/export-map.cpp index 7e6732369d..0891cd5701 100644 --- a/plugins/export-map.cpp +++ b/plugins/export-map.cpp @@ -6,9 +6,13 @@ #include "modules/Maps.h" #include "modules/Translation.h" +#include "df/entity_raw.h" +#include "df/creature_raw.h" +#include "df/historical_entity.h" #include "df/world.h" #include "df/map_block.h" #include "df/world_data.h" +#include "df/world_site.h" #include "df/region_map_entry.h" #include "df/world_region.h" #include "df/world_landmass.h" @@ -46,14 +50,15 @@ DFhackCExport command_result plugin_init(color_ostream &out, std::vector addPoint(x,y); - boundary->addPoint(x,y-dim); - boundary->addPoint(x+dim,y-dim); - boundary->addPoint(x+dim,y); + boundary->addPoint(x,y-dimy); + boundary->addPoint(x+dimx,y-dimy); + boundary->addPoint(x+dimx,y); boundary->closeRings(); //the "Directly" variants assume ownership of the objects created above poly->addRingDirectly(boundary); @@ -105,8 +110,14 @@ const char* describe_surroundings(int savagery, int evilness) { return surroundings[3 * evilness_index + savagery_index]; } -static command_result do_command(color_ostream &out, vector ¶meters) { - // we don't want DF to delete region details while we are iterating over them... +static int wdim = 768; // dimension of a world tile +static int rdim = 48; // dimension of a region tile + +static command_result export_region_tiles(color_ostream &out); +static command_result export_sites(color_ostream &out); + +static command_result do_command(color_ostream &out, vector ¶meters) +{ CoreSuspender suspend; if (!Core::getInstance().isWorldLoaded()){ @@ -114,6 +125,148 @@ static command_result do_command(color_ostream &out, vector ¶meters) return CR_WRONG_USAGE; } + if (parameters.size() && parameters[0] == "sites") + { + return export_sites(out); + } + else + { + return export_region_tiles(out); + } +} + +static command_result export_sites(color_ostream &out) +{ + out.print("exporting sites... "); + out.flush(); + const auto start{std::chrono::steady_clock::now()}; + + // set up coordinate system + OGRSpatialReference CRS; + if (CRS.importFromProj4(EPSG_3857) != OGRERR_NONE) { + out.printerr("could not set up coordinate system"); + return CR_FAILURE; + } + + // set up output driver + GDALAllRegister(); + const char *driver_name = "SQLite"; + const char *extension = "sqlite"; + auto driver = GetGDALDriverManager()->GetDriverByName(driver_name); + if (!driver) { + out.printerr("could not find sqlite driver"); + return CR_FAILURE; + } + + // create a dataset and associate it to a file + std::string sites("sites."); + sites.append(extension); + const char* options[] = { "SPATIALITE=YES", nullptr }; + auto dataset = driver->Create( sites.c_str(), 0, 0, 0, GDT_Unknown, options); + if (!dataset) { + out.printerr("could not create dataset"); + return CR_FAILURE; + } + + // create a layer for the biome data + // const char* format[] = { "FORMAT=WKT", nullptr }; + auto layer = dataset->CreateLayer( "world_sites", &CRS, wkbPolygon, nullptr ); + if (!layer) { + out.printerr("could not create layer"); + return CR_FAILURE; + } + + try { + create_field(layer, "site_id", OFTInteger); + create_field(layer, "civ_id", OFTInteger); + create_field(layer, "created_year", OFTInteger); + create_field(layer, "cur_owner_id", OFTInteger); + + create_field(layer, "type", OFTString, 15); + + create_field(layer, "site_name_df", OFTString, 100); + create_field(layer, "site_name_en", OFTString, 100); + + create_field(layer, "civ_name_df", OFTString, 100); + create_field(layer, "civ_name_en", OFTString, 100); + + create_field(layer, "site_government_df", OFTString, 100); + create_field(layer, "site_government_en", OFTString, 100); + + create_field(layer, "owner_race", OFTString, 15); + + // create_field(layer, "local_ruler", OFTString, 100); + + } + catch (const DFHack::command_result& r) { + out.printerr("could not create fields for output layer"); + return r; + } + + if (dataset->StartTransaction() != OGRERR_NONE) { + out.printerr("could not start a transaction\n"); + } + + for (auto const site : world->world_data->sites) + { + + auto feature = OGRFeature::CreateFeature( layer->GetLayerDefn() ); + + setGeometry( + feature, + site->global_min_x * rdim, + site->global_min_y * rdim, + (site->global_max_x - site->global_min_x + 1) * rdim, + (site->global_max_y - site->global_min_y + 1) * rdim + ); + feature->SetField( "site_id", site->id ); + feature->SetField( "type", ENUM_KEY_STR(world_site_type, site->type).c_str() ); + #define SET_FIELD(name) feature->SetField( #name, site->name) + SET_FIELD(civ_id); + SET_FIELD(created_year); + SET_FIELD(cur_owner_id); + #undef SET_FIELD + + #define TRANSLATE_NAME(field_name, name_object)\ + feature->SetField((#field_name"_df"), DF2UTF(Translation::translateName(&name_object, false)).c_str());\ + feature->SetField((#field_name"_en"), DF2UTF(Translation::translateName(&name_object, true)).c_str()); + + TRANSLATE_NAME(site_name, site->name) + + auto civ = df::historical_entity::find(site->civ_id); + if (civ) { TRANSLATE_NAME(civ_name,civ->name) } + + auto owner = df::historical_entity::find(site->cur_owner_id); + if (owner) { + TRANSLATE_NAME(site_government,owner->name) + auto race = df::creature_raw::find(owner->race); + if (!race){ + race = df::creature_raw::find(civ->race); + } + if (race) { + feature->SetField( "owner_race", race->name[2].c_str() ); + } + } + + + // this updates the feature with the id it receives in the layer + if( layer->CreateFeature( feature ) != OGRERR_NONE ) + return CR_FAILURE; + // but we don't care and destroy the feature + OGRFeature::DestroyFeature( feature ); + } + + dataset->CommitTransaction(); + + GDALClose( dataset ); + const auto finish{std::chrono::steady_clock::now()}; + const std::chrono::duration elapsed_seconds{finish - start}; + out.print("done in %f ms !\n", elapsed_seconds.count()); + return CR_OK; +} + +static command_result export_region_tiles(color_ostream &out) +{ out.print("%lu / %d region map tiles loaded\n", world->world_data->midmap_data.region_details.size(), world->world_data->world_width * world->world_data->world_height @@ -169,14 +322,11 @@ static command_result do_command(color_ostream &out, vector ¶meters) create_field(layer, "reanimating", OFTInteger, 0, OFSTBoolean); create_field(layer, "has_bogeymen", OFTInteger, 0, OFSTBoolean); - }catch (const DFHack::command_result& r) { + } catch (const DFHack::command_result& r) { out.printerr("could not create fields for output layer"); return r; } - int wdim = 768; // dimension of a world tile - int rdim = 48; // dimension of a region tile - // iterating over the region details allows the user to do partial map exports // by manually scrolling on the embark site selection for (auto const region_details : world->world_data->midmap_data.region_details) {