Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/changelog.txt
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Template for new versions:
- `buildingplan`: fix roller material estimate asking for one chain per tile instead of one chain total
- `buildingplan`: fix "Unlink all" only unlinking some mechanisms (or crashing) when freeing mechanisms from the building
- `tiletypes`: remove plants (including trees) rooted on a tile that is painted into something that cannot host them, so cleared ground no longer regrows floating trees
- Fixed gem and glass windows being unable to be made part of a room; they are the only buildings nobles can demand that could not be, whether the window or the room is placed first
- `timestream`: deal properly with units who have breathing difficulties
- `stocks`: overlay now resets scroll position when collapsing categories so the item list is no longer left blank and unscrollable
- Fixed persistent site data API (``dfhack.persistent.saveSiteData``/``getSiteData``) failing on newly reclaimed fortresses until the first save
Expand Down
33 changes: 33 additions & 0 deletions library/modules/Buildings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ distribution.
#include "TileTypes.h"
#include "MiscUtils.h"
#include "DataDefs.h"
#include "VTableInterpose.h"

#include "modules/Buildings.h"
#include "modules/Maps.h"
Expand Down Expand Up @@ -61,6 +62,8 @@ distribution.
#include "df/building_water_wheelst.h"
#include "df/building_weaponst.h"
#include "df/building_wellst.h"
#include "df/building_window_gemst.h"
#include "df/building_window_glassst.h"
#include "df/building_workshopst.h"
#include "df/buildingitemst.h"
#include "df/buildings_other_id.h"
Expand Down Expand Up @@ -134,13 +137,43 @@ static df::building_extents_type *getExtentTile(const df::building::T_room &room
*/
bool buildings_do_onupdate = false;

/*
* Mitigation for a DF bug: gem and glass windows are the only buildings that
* nobles can demand in their rooms whose masterablebuilding (canMakeRoom)
* vmethod returns false, so a window built inside an existing room is linked
* to it while a room designated over an existing window is not. Interpose the
* vmethod on the two concrete subclasses; the abstract building_windowst
* vtable cannot be hooked.
*/
struct window_gem_masterable_hook : df::building_window_gemst {
typedef df::building_window_gemst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, canMakeRoom, ())
{
return true;
}
};
IMPLEMENT_VMETHOD_INTERPOSE(window_gem_masterable_hook, canMakeRoom);

struct window_glass_masterable_hook : df::building_window_glassst {
typedef df::building_window_glassst interpose_base;
DEFINE_VMETHOD_INTERPOSE(bool, canMakeRoom, ())
{
return true;
}
};
IMPLEMENT_VMETHOD_INTERPOSE(window_glass_masterable_hook, canMakeRoom);

void buildings_onStateChange(color_ostream &out, state_change_event event)
{
switch (event) {
case SC_MAP_LOADED:
INTERPOSE_HOOK(window_gem_masterable_hook, canMakeRoom).apply();
INTERPOSE_HOOK(window_glass_masterable_hook, canMakeRoom).apply();
buildings_do_onupdate = true;
break;
case SC_MAP_UNLOADED:
INTERPOSE_HOOK(window_gem_masterable_hook, canMakeRoom).remove();
INTERPOSE_HOOK(window_glass_masterable_hook, canMakeRoom).remove();
buildings_do_onupdate = false;
break;
default:
Expand Down
92 changes: 92 additions & 0 deletions test/modules/buildings_fortress.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
config.target = 'core'
config.mode = 'fortress'

local function contains(vec, value)
for _,entry in ipairs(vec) do
if entry == value then return true end
end
return false
end

local function construct_window_in_zone(window_type)
for _,zone in ipairs(df.global.world.buildings.other.ANY_ZONE) do
if zone.room.extents then
for y = zone.y1, zone.y2 do
for x = zone.x1, zone.x2 do
local pos = xyz2pos(x, y, zone.z)
if not dfhack.buildings.findAtTile(pos) then
local bld = dfhack.buildings.constructBuilding{
pos=pos,
type=window_type,
filters=dfhack.buildings.getFiltersByType(
{}, window_type, -1, -1),
}
if bld then return bld, zone end
end
end
end
end
end
end

local function expect_window_linked(window_type)
local bld, zone = construct_window_in_zone(window_type)
expect.ne(nil, bld, 'could not find a zone tile suitable for a window')
if not bld then return end
dfhack.with_finalize(
function() dfhack.buildings.deconstruct(bld) end,
function()
expect.true_(contains(bld.relations, zone))
expect.true_(contains(zone.contained_buildings, bld))
end)
end

function test.constructed_gem_window_is_linked_to_zone()
expect_window_linked(df.building_type.WindowGem)
end

function test.constructed_glass_window_is_linked_to_zone()
expect_window_linked(df.building_type.WindowGlass)
end

local function expect_zoned_window_linked(window_type)
local bld, _ = construct_window_in_zone(window_type)
expect.ne(nil, bld, 'could not find a tile suitable for a window')
if not bld then return end
dfhack.with_finalize(
function() dfhack.buildings.deconstruct(bld) end,
function()
local pos = xyz2pos(bld.centerx, bld.centery, bld.z)
local extents = df.reinterpret_cast(df.building_extents_type,
df.new('uint8_t', 1))
extents[0] = 1
local zone, err = dfhack.buildings.constructBuilding{
type=df.building_type.Civzone,
subtype=df.civzone_type.Bedroom,
abstract=true,
pos=pos, width=1, height=1,
fields={
assigned_unit_id=-1,
room={x=pos.x, y=pos.y, width=1, height=1,
extents=extents},
},
}
expect.ne(nil, zone, 'could not place zone over window: ' ..
tostring(err))
if not zone then return end
dfhack.with_finalize(
function() dfhack.buildings.deconstruct(zone) end,
function()
expect.true_(contains(bld.relations, zone))
expect.true_(contains(zone.contained_buildings, bld))
end)
end)
end

function test.zoned_gem_window_is_linked_to_zone()
expect_zoned_window_linked(df.building_type.WindowGem)
end

function test.zoned_glass_window_is_linked_to_zone()
expect_zoned_window_linked(df.building_type.WindowGlass)
end
Loading