|
NSEW_pairs = sorted_pairs |
|
|
|
|
|
|
|
local gofPermanent = const.gofPermanent |
|
|
|
if FirstLoad then |
|
GedRoomEditor = false |
|
GedRoomEditorObjList = false |
|
g_RoomCornerTaskList = {} |
|
SelectedVolume = false |
|
VolumeCollisonEnabled = false |
|
HideFloorsAboveThisOne = false |
|
|
|
|
|
RepositionWallSlabsOnLoad = Platform.developer or false |
|
end |
|
|
|
function VolumeStructuresList() |
|
local list = {""} |
|
EnumVolumes(function (volume, list, find) |
|
if not find(list, volume.structure) then |
|
list[#list + 1] = volume.structure |
|
end |
|
end, list, table.find) |
|
table.sort(list) |
|
return list |
|
end |
|
|
|
|
|
local noneWallMat = const.SlabNoMaterial |
|
local defaultWallMat = "default" |
|
|
|
DefineClass.Volume = { |
|
__parents = { "RoomRoof", "StripObjectProperties", "AlignedObj", "ComponentAttach", "EditorVisibleObject" }, |
|
flags = { gofPermanent = true, cofComponentVolume = true, efVisible = true }, |
|
|
|
properties = { |
|
{ category = "Not Room Specific", id = "volumeCollisionEnabled", name = "Toggle Global Volume Collision", default = true, editor = "bool", dont_save = true }, |
|
{ category = "Not Room Specific", id = "buttons3", name = "Buttons", editor = "buttons", default = false, dont_save = true, read_only = true, |
|
buttons = { |
|
{name = "Recreate All Walls", func = "RecreateAllWallsOnMap"}, |
|
{name = "Recreate All Roofs", func = "RecreateAllRoofsOnMap"}, |
|
{name = "Recreate All Floors", func = "RecreateAllFloorsOnMap"}, |
|
}, |
|
}, |
|
{ category = "General", id = "buttons2", name = "Buttons", editor = "buttons", default = false, dont_save = true, read_only = true, |
|
buttons = { |
|
{name = "Recreate Walls", func = "RecreateWalls"}, |
|
{name = "Recreate Floor", func = "RecreateFloor"}, |
|
{name = "Recreate Roof", func = "RecreateRoofBtn"}, |
|
{name = "Re Randomize", func = "ReRandomize"}, |
|
{name = "Copy Above", func = "CopyAbove"}, |
|
{name = "Copy Below", func = "CopyBelow"}, |
|
}, |
|
}, |
|
{ category = "General", id = "buttons2row2", name = "Buttons", editor = "buttons", default = false, dont_save = true, read_only = true, |
|
buttons = { |
|
{name = "Lock Subvariants", func = "LockAllSlabsToCurrentSubvariants"}, |
|
{name = "Unlock Subvariants", func = "UnlockAllSlabs"}, |
|
{name = "Make Slabs Vulnerable", func = "MakeOwnedSlabsVulnerable"}, |
|
{name = "Make Slabs Invulnerable", func = "MakeOwnedSlabsInvulnerable"}, |
|
}, |
|
}, |
|
|
|
{ category = "General", id = "box", name = "Box", editor = "box", default = false, no_edit = true }, |
|
{ category = "General", id = "locked_slabs_count", name = "Locked Slabs Count", editor = "text", default = "", read_only = true, dont_save = true}, |
|
{ category = "General", id = "wireframe_visible", name = "Wireframe Visible", editor = "bool", default = false,}, |
|
{ category = "General", id = "wall_text_markers_visible", name = "Wall Text ID Visible", editor = "bool", default = false, dont_save = true}, |
|
{ category = "General", id = "dont_use_interior_lighting", name = "No Interior Lighting", editor = "bool", default = false, }, |
|
{ category = "General", id = "seed", name = "Random Seed", editor = "number", default = false}, |
|
{ category = "General", id = "floor", name = "Floor", editor = "number", default = 1, min = -9, max = 99 }, |
|
|
|
{ category = "General", id = "position", name = "Position", editor = "point", default = false, no_edit = true, }, |
|
{ category = "General", id = "size", name = "Size", editor = "point", default = point30, no_edit = true, }, |
|
|
|
{ category = "General", id = "override_terrain_z", editor = "number", default = false, no_edit = true }, |
|
{ category = "General", id = "structure", name = "Structure", editor = "combo", default = "", items = VolumeStructuresList }, |
|
|
|
{ category = "Roof", id = "room_is_roof", name = "Room is Roof", editor = "bool", default = false, help = "Mark room as roof, roofs are hidden entirely (all walls, floors, etc.) when their floor is touched. Rooms that have zero height are considered roofs by default."}, |
|
}, |
|
|
|
wireframeColor = RGB(100, 100, 100), |
|
lines = false, |
|
|
|
adjacent_rooms = false, |
|
|
|
text_markers = false, |
|
|
|
last_wall_recreate_seed = false, |
|
building = false, |
|
|
|
being_placed = false, |
|
enable_collision = true, |
|
|
|
EditorView = Untranslated("<opt(u(structure),'',' - ')><u(name)>"), |
|
|
|
light_vol_obj = false, |
|
entity = "InvisibleObject", |
|
editor_force_excluded = true, |
|
} |
|
|
|
local voxelSizeX = const.SlabSizeX or 0 |
|
local voxelSizeY = const.SlabSizeY or 0 |
|
local voxelSizeZ = const.SlabSizeZ or 0 |
|
local halfVoxelSizeX = voxelSizeX / 2 |
|
local halfVoxelSizeY = voxelSizeY / 2 |
|
local halfVoxelSizeZ = voxelSizeZ / 2 |
|
local InvalidZ = const.InvalidZ |
|
maxRoomVoxelSizeX = const.MaxRoomVoxelSizeX or 40 |
|
maxRoomVoxelSizeY = const.MaxRoomVoxelSizeY or 40 |
|
maxRoomVoxelSizeZ = const.MaxRoomVoxelSizeZ or 40 |
|
roomQueryRadius = Max((maxRoomVoxelSizeX + 1) * voxelSizeX, (maxRoomVoxelSizeY + 1) * voxelSizeY, (maxRoomVoxelSizeZ + 1) * voxelSizeZ) |
|
defaultVolumeVoxelHeight = 4 |
|
|
|
local halfVoxelPtZeroZ = point(halfVoxelSizeX, halfVoxelSizeY, 0) |
|
local halfVoxelPt = point(halfVoxelSizeX, halfVoxelSizeY, halfVoxelSizeZ) |
|
function SnapVolumePos(pos) |
|
return SnapToVoxel(pos) - halfVoxelPtZeroZ |
|
end |
|
|
|
function snapZRound(z) |
|
z = (z + halfVoxelSizeZ) / voxelSizeZ |
|
return z * voxelSizeZ |
|
end |
|
|
|
function snapZCeil(z) |
|
z = DivCeil(z, voxelSizeZ) |
|
return z * voxelSizeZ |
|
end |
|
|
|
function snapZ(z) |
|
z = z / voxelSizeZ |
|
return z * voxelSizeZ |
|
end |
|
|
|
function Volume:Getlocked_slabs_count() |
|
return "N/A" |
|
end |
|
|
|
function Volume:GetvolumeCollisionEnabled() |
|
return VolumeCollisonEnabled |
|
end |
|
|
|
function Volume:Setroom_is_roof(val) |
|
if self.room_is_roof == val then return end |
|
self.room_is_roof = val |
|
ComputeSlabVisibilityInBox(self.box) |
|
end |
|
|
|
function Volume:IsRoofOnly() |
|
return self.room_is_roof or self.size:z() == 0 |
|
end |
|
|
|
function Volume:SetvolumeCollisionEnabled(v) |
|
VolumeCollisonEnabled = v |
|
end |
|
|
|
function Volume:Init() |
|
|
|
self.text_markers = { |
|
North = PlaceObject("Text"), |
|
South = PlaceObject("Text"), |
|
West = PlaceObject("Text"), |
|
East = PlaceObject("Text"), |
|
} |
|
|
|
for k, v in pairs(self.text_markers) do |
|
v.hide_in_editor = false |
|
v:SetText(k) |
|
v:SetColor(RGB(255, 0, 0)) |
|
v:SetGameFlags(const.gofDetailClass1) |
|
v:ClearGameFlags(const.gofDetailClass0) |
|
end |
|
|
|
self:SetPosMarkersVisible(self.wall_text_markers_visible) |
|
self:CopyBoxToCCD() |
|
self:InitEntity() |
|
end |
|
|
|
function Volume:InitEntity() |
|
if IsEditorActive() then |
|
self:ChangeEntity("RoomHelper") |
|
end |
|
end |
|
|
|
function Volume:EditorEnter() |
|
|
|
self:ChangeEntity("RoomHelper") |
|
end |
|
|
|
function Volume:EditorExit() |
|
|
|
self:ChangeEntity("InvisibleObject") |
|
end |
|
|
|
function Volume:Setdont_use_interior_lighting(val) |
|
self.dont_use_interior_lighting = val |
|
self:UpdateInteriorLighting() |
|
end |
|
|
|
function Volume:CopyBoxToCCD() |
|
local box = self.box |
|
if not box then return end |
|
SetVolumeBox(self, box) |
|
self:UpdateInteriorLighting() |
|
end |
|
|
|
function Volume:UpdateInteriorLighting() |
|
local box = self.box |
|
if not box then |
|
return |
|
end |
|
|
|
if self.dont_use_interior_lighting then |
|
DoneObject(self.light_vol_obj) |
|
self.light_vol_obj = nil |
|
return |
|
end |
|
|
|
local lo = self.light_vol_obj |
|
if not IsValid(lo) then |
|
lo = PlaceObject("ComponentLight") |
|
lo:SetLightType(const.eLightTypeClusterVolume) |
|
self.light_vol_obj = lo |
|
end |
|
|
|
if not self.dont_use_interior_lighting and self.floor_mat == noneWallMat and self.floor == 1 then |
|
lo:SetClusterVolumeBox(box:minx(), box:miny(), box:minz() - 100, box:maxx(), box:maxy(), box:maxz()) |
|
else |
|
lo:SetClusterVolumeBox(box:minx(), box:miny(), box:minz(), box:maxx(), box:maxy(), box:maxz()) |
|
end |
|
lo:SetVolumeId(self.handle) |
|
lo:SetPos(self:GetPos()) |
|
end |
|
|
|
function Volume:CalcZ() |
|
local posZ = self.position:z() |
|
|
|
if self.being_placed then |
|
local z = self.override_terrain_z or terrain.GetHeight(self.position) |
|
posZ = snapZ(z + voxelSizeZ / 2) |
|
self.position = self.position:SetZ(posZ) |
|
end |
|
|
|
if posZ == nil then |
|
|
|
local z = self.override_terrain_z or terrain.GetHeight(self.position) |
|
z = snapZ(z + voxelSizeZ / 2) |
|
posZ = (rawget(self, "z_offset") or 0) * voxelSizeZ + z + (self.floor - 1) * self.size:z() * voxelSizeZ |
|
self.position = self.position:SetZ(posZ) |
|
end |
|
|
|
return posZ |
|
end |
|
|
|
function Volume:LockToCurrentTerrainZ() |
|
self.override_terrain_z = terrain.GetHeight(self.position) |
|
return self.override_terrain_z |
|
end |
|
|
|
function Volume:CalcSnappedZ() |
|
local z = self:CalcZ() |
|
z = z / voxelSizeZ |
|
z = z * voxelSizeZ |
|
return z |
|
end |
|
|
|
function FloorFromZ(z, roomHeight, ground_level) |
|
return (z - ground_level) / (roomHeight * voxelSizeZ) + 1 |
|
end |
|
|
|
function ZFromFloor(f, roomHeight, ground_level) |
|
return ground_level + (f - 1) * (roomHeight * voxelSizeZ) |
|
end |
|
|
|
function Volume:Move(pos) |
|
|
|
self.position = SnapVolumePos(pos) |
|
self:AlignObj() |
|
end |
|
|
|
function Volume:ChangeFloor(newFloor) |
|
if self.floor == newFloor then |
|
return |
|
end |
|
|
|
self.floor = newFloor |
|
self:AlignObj() |
|
end |
|
|
|
function Volume:SetSize(newSize) |
|
if self.size == newSize then |
|
return |
|
end |
|
|
|
self.size = newSize |
|
self:AlignObj() |
|
end |
|
|
|
function Volume:AlignObj(pos, angle) |
|
if pos then |
|
local v = pos - self:GetPos() |
|
self.position = SnapVolumePos(self.position + v) |
|
end |
|
self:InternalAlignObj() |
|
end |
|
|
|
function Volume:InternalAlignObj(test) |
|
local w, h, d = self.size:x() * voxelSizeX, self.size:y() * voxelSizeY, self.size:z() * voxelSizeZ |
|
local cx, cy = w / 2, h / 2 |
|
local z = self:CalcZ() |
|
local pos = point(self.position:x() + cx, self.position:y() + cy, z) |
|
local p = self.position |
|
local newBox = box(p:x(), p:y(), z, p:x() + w, p:y() + h, z + d) |
|
if not test and self:GetPos() == pos and self.box == newBox then return p end |
|
self:SetPos(pos) |
|
self:SetAngle(0) |
|
self.box = newBox |
|
if not test then |
|
self:FinishAlign() |
|
end |
|
return p |
|
end |
|
|
|
function GetOppositeSide(side) |
|
if side == "North" then return "South" |
|
elseif side == "South" then return "North" |
|
elseif side == "West" then return "East" |
|
elseif side == "East" then return "West" |
|
end |
|
end |
|
|
|
local function GetOppositeCorner(c) |
|
if c == "NW" then return "SE" |
|
elseif c == "NE" then return "SW" |
|
elseif c == "SW" then return "NE" |
|
elseif c == "SE" then return "NW" |
|
end |
|
end |
|
|
|
local function SetAdjacentRoom(adjacent_rooms, room, data) |
|
if not adjacent_rooms then |
|
return |
|
end |
|
if data then |
|
if not adjacent_rooms[room] then |
|
adjacent_rooms[#adjacent_rooms + 1] = room |
|
end |
|
adjacent_rooms[room] = data |
|
return data |
|
end |
|
data = adjacent_rooms[room] |
|
if data then |
|
adjacent_rooms[room] = nil |
|
table.remove_value(adjacent_rooms, room) |
|
return data |
|
end |
|
end |
|
|
|
function Volume:ClearAdjacencyData() |
|
local adjacent_rooms = self.adjacent_rooms |
|
self.adjacent_rooms = nil |
|
|
|
for _, room in ipairs(adjacent_rooms or empty_table) do |
|
local hisData = SetAdjacentRoom(room.adjacent_rooms, self, false) |
|
if hisData then |
|
local hisAW = hisData[2] |
|
for i = 1, #(hisAW or empty_table) do |
|
room:OnAdjacencyChanged(hisAW[i]) |
|
end |
|
end |
|
end |
|
end |
|
|
|
|
|
local AdjacencyEvents = {} |
|
function Volume:RebuildAdjacencyData() |
|
|
|
assert(XEditorUndo:AssertOpCapture()) |
|
|
|
local adjacent_rooms = self.adjacent_rooms |
|
local new_adjacent_rooms = {} |
|
local mb = self.box |
|
local events = {} |
|
|
|
local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 |
|
local gameFlags = is_permanent and gofPermanent or nil |
|
MapForEach(self, roomQueryRadius, self.class, nil, nil, gameFlags, function(o, mb, is_permanent) |
|
if o == self or not is_permanent and o:GetGameFlags(gofPermanent) ~= 0 then |
|
return |
|
end |
|
|
|
|
|
local hb = o.box |
|
|
|
local ib = IntersectRects(hb, mb) |
|
|
|
if not ib:IsValid() then |
|
return |
|
end |
|
local myData = adjacent_rooms and adjacent_rooms[o] |
|
local oldIb = myData and myData[1] |
|
|
|
local myNewData = {} |
|
local hisData = o.adjacent_rooms and o.adjacent_rooms[self] |
|
local hisNewData = {} |
|
|
|
|
|
local myaw = myData and myData[2] |
|
local hisaw = hisData and hisData[2] |
|
for i = 1, #(myaw or empty_table) do |
|
table.insert(events, {self, myaw[i]}) |
|
end |
|
for i = 1, #(hisaw or empty_table) do |
|
table.insert(events, {o, hisaw[i]}) |
|
end |
|
|
|
hisNewData[1] = ib |
|
myNewData[1] = ib |
|
hisNewData[2] = {} |
|
myNewData[2] = {} |
|
|
|
if ib:sizez() > 0 then |
|
if ib:minx() == ib:maxx() and ib:miny() == ib:maxy() then |
|
|
|
local p = ib:min() |
|
if p:x() == mb:minx() then |
|
if p:y() == mb:miny() then |
|
table.insert(events, {self, "NW"}) |
|
table.insert(hisNewData[2], "SE") |
|
table.insert(myNewData[2], "NW") |
|
else |
|
table.insert(events, {self, "SW"}) |
|
table.insert(hisNewData[2], "NE") |
|
table.insert(myNewData[2], "SW") |
|
end |
|
else |
|
if p:y() == mb:miny() then |
|
table.insert(events, {self, "NE"}) |
|
table.insert(hisNewData[2], "SW") |
|
table.insert(myNewData[2], "NE") |
|
else |
|
table.insert(events, {self, "SE"}) |
|
table.insert(hisNewData[2], "NW") |
|
table.insert(myNewData[2], "SE") |
|
end |
|
end |
|
elseif ib:minx() == ib:maxx() |
|
and ib:miny() ~= ib:maxy() then |
|
|
|
if mb:maxx() == ib:maxx() then |
|
|
|
table.insert(events, {self, "East"}) |
|
table.insert(events, {o, "West"}) |
|
table.insert(hisNewData[2], "West") |
|
table.insert(myNewData[2], "East") |
|
else |
|
|
|
table.insert(events, {self, "West"}) |
|
table.insert(events, {o, "East"}) |
|
table.insert(hisNewData[2], "East") |
|
table.insert(myNewData[2], "West") |
|
end |
|
elseif ib:minx() ~= ib:maxx() |
|
and ib:miny() == ib:maxy() then |
|
|
|
if mb:maxy() == ib:maxy() then |
|
|
|
table.insert(events, {self, "South"}) |
|
table.insert(events, {o, "North"}) |
|
table.insert(hisNewData[2], "North") |
|
table.insert(myNewData[2], "South") |
|
else |
|
|
|
table.insert(events, {self, "North"}) |
|
table.insert(events, {o, "South"}) |
|
table.insert(hisNewData[2], "South") |
|
table.insert(myNewData[2], "North") |
|
end |
|
else |
|
|
|
if (ib:maxx() == mb:maxx() or ib:minx() == mb:maxx()) |
|
and mb:maxx() == hb:maxx() then |
|
|
|
table.insert(events, {self, "East"}) |
|
table.insert(myNewData[2], "East") |
|
table.insert(hisNewData[2], "East") |
|
end |
|
|
|
if (ib:minx() == mb:minx() or ib:maxx() == mb:minx()) |
|
and mb:minx() == hb:minx() then |
|
|
|
table.insert(events, {self, "West"}) |
|
table.insert(myNewData[2], "West") |
|
table.insert(hisNewData[2], "West") |
|
end |
|
|
|
if (ib:maxy() == mb:maxy() or ib:miny() == mb:maxy()) |
|
and mb:maxy() == hb:maxy() then |
|
|
|
table.insert(events, {self, "South"}) |
|
table.insert(myNewData[2], "South") |
|
table.insert(hisNewData[2], "South") |
|
end |
|
|
|
if ib:maxy() == mb:miny() or ib:miny() == mb:miny() |
|
and mb:miny() == hb:miny() then |
|
|
|
table.insert(events, {self, "North"}) |
|
table.insert(myNewData[2], "North") |
|
table.insert(hisNewData[2], "North") |
|
end |
|
end |
|
end |
|
|
|
|
|
if ib:sizex() > 0 and ib:sizey() > 0 then |
|
if (mb:minz() >= ib:minz() and mb:minz() <= ib:maxz()) or |
|
(hb:maxz() >= ib:minz() and hb:maxz() <= ib:maxz()) then |
|
|
|
table.insert(events, {self, "Floor"}) |
|
table.insert(myNewData[2], "Floor") |
|
table.insert(hisNewData[2], "Roof") |
|
end |
|
|
|
if (mb:maxz() <= ib:maxz() and mb:maxz() >= ib:minz()) or |
|
(hb:minz() <= ib:maxz() and hb:minz() >= ib:minz()) then |
|
|
|
table.insert(events, {self, "Roof"}) |
|
table.insert(myNewData[2], "Roof") |
|
table.insert(hisNewData[2], "Floor") |
|
end |
|
end |
|
|
|
SetAdjacentRoom(o.adjacent_rooms, self, #hisNewData[2] > 0 and hisNewData) |
|
SetAdjacentRoom(new_adjacent_rooms, o, #myNewData[2] > 0 and myNewData) |
|
end, mb, is_permanent) |
|
|
|
for _, room in ipairs(adjacent_rooms or empty_table) do |
|
if not new_adjacent_rooms[room] then |
|
|
|
local data = adjacent_rooms[room] |
|
local myaw = data[2] |
|
local hisData = SetAdjacentRoom(room.adjacent_rooms, self, false) |
|
local hisaw = hisData and hisData[2] |
|
for i = 1, #(myaw or empty_table) do |
|
table.insert(events, {self, myaw[i]}) |
|
end |
|
for i = 1, #(hisaw or empty_table) do |
|
table.insert(events, {room, hisaw[i]}) |
|
end |
|
end |
|
end |
|
|
|
self.adjacent_rooms = new_adjacent_rooms |
|
|
|
if IsChangingMap() or XEditorUndo.undoredo_in_progress then |
|
return |
|
end |
|
|
|
if #(events or empty_table) > 0 then |
|
table.insert(AdjacencyEvents, events) |
|
Wakeup(PeriodicRepeatThreads["AdjacencyEvents"]) |
|
end |
|
end |
|
|
|
function ProcessVolumeAdjacencyEvents() |
|
local passed = {} |
|
for i = 1, #AdjacencyEvents do |
|
local events = AdjacencyEvents[i] |
|
for i = 1, #(events or empty_table) do |
|
local ev = events[i] |
|
local o = ev[1] |
|
local s = ev[2] |
|
if IsValid(o) and (not passed[o] or (passed[o] and not passed[o][s])) then |
|
passed[o] = passed[o] or {} |
|
passed[o][s] = true |
|
|
|
o:OnAdjacencyChanged(s) |
|
end |
|
end |
|
end |
|
table.clear(AdjacencyEvents) |
|
end |
|
|
|
|
|
OnMsg.EditorObjectOperationEnding = ProcessVolumeAdjacencyEvents |
|
|
|
MapGameTimeRepeat("AdjacencyEvents", -1, function(sleep) |
|
PauseInfiniteLoopDetection("AdjacencyEvents") |
|
ProcessVolumeAdjacencyEvents() |
|
ResumeInfiniteLoopDetection("AdjacencyEvents") |
|
WaitWakeup() |
|
end) |
|
|
|
local dirToWallMatMember = { |
|
North = "north_wall_mat", |
|
South = "south_wall_mat", |
|
West = "west_wall_mat", |
|
East = "east_wall_mat", |
|
Floor = "floor_mat", |
|
} |
|
|
|
local sideToFuncName = { |
|
NW = "RecreateNWCornerBeam", |
|
NE = "RecreateNECornerBeam", |
|
SW = "RecreateSWCornerBeam", |
|
SE = "RecreateSECornerBeam", |
|
} |
|
|
|
function Volume:CheckWallSizes() |
|
if not Platform.developer then return end |
|
local t = self.spawned_walls |
|
assert(#t.West == #t.East) |
|
assert(#t.North == #t.South) |
|
end |
|
|
|
function Volume:OnAdjacencyChanged(side) |
|
if #side == 2 then |
|
self[sideToFuncName[side]](self) |
|
elseif side == "Floor" then |
|
self:CreateFloor(self.floor_mat) |
|
elseif side == "Roof" then |
|
if not self.being_placed then |
|
self:UpdateRoofSlabVisibility() |
|
end |
|
else |
|
self:CreateWalls(side, self[dirToWallMatMember[side]]) |
|
self:CheckWallSizes() |
|
end |
|
|
|
self:DelayedRecalcRoof() |
|
end |
|
|
|
if FirstLoad then |
|
SelectedRooms = false |
|
RoomSelectionMode = false |
|
end |
|
|
|
function SetRoomSelectionMode(bVal) |
|
RoomSelectionMode = bVal |
|
print(string.format("RoomSelectionMode is %s", RoomSelectionMode and "ON" or "OFF")) |
|
end |
|
|
|
function ToggleRoomSelectionMode() |
|
SetRoomSelectionMode(not RoomSelectionMode) |
|
end |
|
|
|
if FirstLoad then |
|
roomsToDeselect = false |
|
end |
|
|
|
local function selectRoomHelper(r, t) |
|
t = t or SelectedRooms |
|
t = t or {} |
|
r:SetPosMarkersVisible(true) |
|
table.insert(t, r) |
|
if roomsToDeselect then |
|
table.remove_entry(roomsToDeselect, r) |
|
end |
|
end |
|
|
|
local function deselectRoomHelper(r) |
|
if IsValid(r) then |
|
r:SetPosMarkersVisible(false) |
|
r:ClearSelectedWall() |
|
end |
|
end |
|
|
|
local function deselectRooms() |
|
for i = 1, #(roomsToDeselect or "") do |
|
deselectRoomHelper(roomsToDeselect[i]) |
|
end |
|
roomsToDeselect = false |
|
end |
|
|
|
function OnMsg.EditorSelectionChanged(objects) |
|
|
|
if RoomSelectionMode then |
|
|
|
local o = #objects == 1 and objects[1] |
|
if o and IsKindOf(o, "Slab") and IsValid(o.room) then |
|
editor.ClearSel() |
|
editor.AddToSel({o.room}) |
|
return |
|
end |
|
end |
|
|
|
local newSelectedRooms = {} |
|
|
|
for i = 1, #objects do |
|
local o = objects[i] |
|
if IsKindOf(o, "Slab") then |
|
local r = o.room |
|
if IsValid(r) then |
|
selectRoomHelper(r, newSelectedRooms) |
|
end |
|
elseif IsKindOf(o, "Room") then |
|
selectRoomHelper(o, newSelectedRooms) |
|
end |
|
end |
|
|
|
for i = 1, #(SelectedRooms or "") do |
|
local r = SelectedRooms[i] |
|
if not table.find(newSelectedRooms, r) then |
|
|
|
roomsToDeselect = roomsToDeselect or {} |
|
table.insert(roomsToDeselect, r) |
|
DelayedCall(0, deselectRooms) |
|
end |
|
end |
|
|
|
SelectedRooms = #newSelectedRooms > 0 and newSelectedRooms or false |
|
end |
|
|
|
function Volume:TogglePosMarkersVisible() |
|
local el = self.text_markers.North |
|
self:SetPosMarkersVisible(el:GetEnumFlags(const.efVisible) == 0) |
|
end |
|
|
|
function Volume:SetPosMarkersVisible(val) |
|
for k, v in pairs(self.text_markers) do |
|
if not val then |
|
v:ClearEnumFlags(const.efVisible) |
|
else |
|
v:SetEnumFlags(const.efVisible) |
|
end |
|
end |
|
end |
|
|
|
function Volume:PositionWallTextMarkers() |
|
local t = self.text_markers |
|
local gz = self:CalcZ() + self.size:z() * voxelSizeZ / 2 |
|
local p = self.position + point(self.size:x() * voxelSizeX / 2, 0) |
|
p = p:SetZ(gz) |
|
t.North:SetPos(p) |
|
p = self.position + point(self.size:x() * voxelSizeX / 2, self.size:y() * voxelSizeY) |
|
p = p:SetZ(gz) |
|
t.South:SetPos(p) |
|
p = self.position + point(0, self.size:y() * voxelSizeY / 2) |
|
p = p:SetZ(gz) |
|
t.West:SetPos(p) |
|
p = self.position + point(self.size:x() * voxelSizeX, self.size:y() * voxelSizeY / 2) |
|
p = p:SetZ(gz) |
|
t.East:SetPos(p) |
|
end |
|
|
|
function Volume:FinishAlign() |
|
if not self.seed then |
|
self.seed = EncodeVoxelPos(self) |
|
end |
|
|
|
self:CopyBoxToCCD() |
|
self:RebuildAdjacencyData() |
|
if self.wireframe_visible then |
|
self:GenerateGeometry() |
|
else |
|
self:DoneLines() |
|
end |
|
self:PositionWallTextMarkers() |
|
self.box_at_last_roof_edit = self.box |
|
|
|
if not IsChangingMap() then |
|
self:RefreshFloorCombatStatus() |
|
end |
|
|
|
Msg("RoomAligned", self) |
|
end |
|
|
|
function Volume:RefreshFloorCombatStatus() |
|
end |
|
|
|
function Volume:Setfloor(v) |
|
self.floor = v |
|
self:RefreshFloorCombatStatus() |
|
end |
|
|
|
function Volume:VolumeDestructor() |
|
self:DoneLines() |
|
DoneObject(self.light_vol_obj) |
|
DoneObjects(self.light_vol_objs) |
|
self.light_vol_obj = nil |
|
self.light_vol_objs = nil |
|
for k, v in pairs(self.text_markers) do |
|
DoneObject(v) |
|
end |
|
self["VolumeDestructor"] = empty_func |
|
end |
|
|
|
function Volume:Done() |
|
self:VolumeDestructor() |
|
end |
|
|
|
function Volume.ToggleVolumeCollision(_, self) |
|
VolumeCollisonEnabled = not VolumeCollisonEnabled |
|
end |
|
|
|
function Volume:CheckCollision(cls, box) |
|
if not VolumeCollisonEnabled then return false end |
|
if not self.enable_collision then return false end |
|
cls = cls or self.class |
|
local ret = false |
|
box = box or self.box |
|
MapForEach(self:GetPos(), roomQueryRadius, cls, function(o) |
|
if o ~= self and o.enable_collision then |
|
if box:Intersect(o.box) ~= 0 then |
|
ret = true |
|
return "break" |
|
end |
|
end |
|
end, box) |
|
|
|
return ret |
|
end |
|
|
|
local dontCopyTheeseProps = { |
|
name = true, |
|
floor = true, |
|
adjacent_rooms = true, |
|
box = true, |
|
position = true, |
|
roof_objs = true, |
|
spawned_doors = true, |
|
spawned_windows = true, |
|
spawned_decals = true, |
|
spawned_walls = true, |
|
spawned_corners = true, |
|
spawned_floors = true, |
|
text_markers = true, |
|
} |
|
|
|
function Volume:RecreateAllWallsOnMap() |
|
MapForEach("map", "Volume", Volume.RecreateWalls) |
|
end |
|
|
|
function Volume:RecreateAllRoofsOnMap() |
|
local all_volumes = MapGet("map", "Volume") |
|
table.sortby_field(all_volumes, "floor") |
|
for i,volume in ipairs(all_volumes) do |
|
volume:RecreateRoof() |
|
end |
|
end |
|
|
|
function Volume:RecreateAllFloorsOnMap() |
|
MapForEach("map", "Volume", Volume.RecreateFloor) |
|
end |
|
|
|
function Volume:RecreateWalls() |
|
SuspendPassEdits("Volume:RecreateWalls") |
|
self:DeleteAllWallObjs() |
|
self:DeleteAllCornerObjs() |
|
self:CreateAllWalls() |
|
self:CreateAllCorners() |
|
self:OnSetouter_colors(self.outer_colors) |
|
self:OnSetinner_colors(self.inner_colors) |
|
ResumePassEdits("Volume:RecreateWalls") |
|
end |
|
|
|
function Volume:RecreateFloor() |
|
SuspendPassEdits("Volume:RecreateFloor") |
|
self:DeleteAllFloors() |
|
self:CreateFloor() |
|
self:OnSetfloor_colors(self.floor_colors) |
|
ResumePassEdits("Volume:RecreateFloor") |
|
end |
|
|
|
function Volume:RecreateRoofBtn() |
|
self:RecreateRoof() |
|
end |
|
|
|
function Volume:ReRandomize() |
|
self.last_wall_recreate_seed = self.seed |
|
self.seed = BraidRandom(self.seed) |
|
self:CreateAllWalls() |
|
self.last_wall_recreate_seed = self.seed |
|
ObjModified(self) |
|
end |
|
|
|
function Volume:CopyAbove() |
|
XEditorUndo:BeginOp() |
|
local nv = self:Copy(1) |
|
SetSelectedVolume(nv) |
|
XEditorUndo:EndOp{ nv } |
|
end |
|
|
|
function Volume:CopyBelow() |
|
XEditorUndo:BeginOp() |
|
local nv = self:Copy(-1) |
|
SetSelectedVolume(nv) |
|
XEditorUndo:EndOp{ nv } |
|
end |
|
|
|
function Volume:CollisionCheckNextFloor(floorOffset) |
|
if not VolumeCollisonEnabled then return false end |
|
if not self.enable_collision then return false end |
|
local b = self.box |
|
local offset = point(0, 0, voxelSizeZ * self.size:z() * floorOffset) |
|
b = Offset(b, offset) |
|
local collision = false |
|
MapForEach(self:GetPos(), roomQueryRadius, self.class, function(o) |
|
if o ~= self and o.enable_collision then |
|
if b:Intersect(o.box) ~= 0 then |
|
collision = true |
|
return "break" |
|
end |
|
end |
|
end) |
|
return collision |
|
end |
|
|
|
function Volume:Copy(floorOffset, inputObj, skipCollisionTest) |
|
local offset = point(0, 0, voxelSizeZ * self.size:z() * floorOffset) |
|
local collision = false |
|
if not skipCollisionTest then |
|
collision = self:CollisionCheckNextFloor(floorOffset) |
|
end |
|
|
|
if skipCollisionTest or not collision then |
|
inputObj = inputObj or {} |
|
inputObj.floor = inputObj.floor or self.floor + floorOffset |
|
inputObj.position = inputObj.position or self.position + offset |
|
inputObj.size = inputObj.size or self.size |
|
inputObj.name = inputObj.name or self.name .. " Copy" |
|
local doNotCopyTheseEither = table.copy(inputObj) |
|
|
|
local cpy = PlaceObject(self.class, inputObj) |
|
local prps = self:GetProperties() |
|
for i = 1, #prps do |
|
local prop = prps[i] |
|
if not dontCopyTheeseProps[prop.id] and not doNotCopyTheseEither[prop.id] then |
|
cpy:SetProperty(prop.id, self:GetProperty(prop.id)) |
|
end |
|
end |
|
cpy:OnCopied(self, offset) |
|
DelayedCall(500, BuildBuildingsData) |
|
return cpy |
|
end |
|
end |
|
|
|
function Volume:OnCopied(from) |
|
self:AlignObj() |
|
end |
|
|
|
function Volume:ToggleGeometryVisible() |
|
if self.lines == false then |
|
self:GenerateGeometry() |
|
return |
|
end |
|
if self.lines and self.lines[1] then |
|
local visible = self.lines[1]:GetEnumFlags(const.efVisible) == 0 |
|
for i = 1, #(self.lines or empty_table) do |
|
if visible then |
|
self.lines[i]:SetEnumFlags(const.efVisible) |
|
else |
|
self.lines[i]:ClearEnumFlags(const.efVisible) |
|
end |
|
end |
|
end |
|
end |
|
|
|
function Volume:DoneLines() |
|
DoneObjects(self.lines) |
|
self.lines = false |
|
end |
|
|
|
function Volume:GetWallBox(side, roomBox) |
|
local ret = false |
|
local b = roomBox or self.box |
|
if side == "North" then |
|
ret = box(b:minx(), b:miny(), b:minz(), b:maxx(), b:miny() + 1, b:maxz()) |
|
elseif side == "South" then |
|
ret = box(b:minx(), b:maxy() - 1, b:minz(), b:maxx(), b:maxy(), b:maxz()) |
|
elseif side == "East" then |
|
ret = box(b:maxx() - 1, b:miny(), b:minz(), b:maxx(), b:maxy(), b:maxz()) |
|
elseif side == "West" then |
|
ret = box(b:minx(), b:miny(), b:minz(), b:minx() + 1, b:maxy(), b:maxz()) |
|
end |
|
|
|
return ret |
|
end |
|
|
|
local function SetLineMesh(line, line_pstr) |
|
if not line_pstr or line_pstr:size() == 0 then return end |
|
line:SetMesh(line_pstr) |
|
return line |
|
end |
|
|
|
local offsetFromVoxelEdge = 20 |
|
function Volume:GenerateGeometry() |
|
self:DoneLines() |
|
|
|
local lines = {} |
|
local xPoints = {} |
|
local xPointsRoof = {} |
|
local yPoints = {} |
|
local yPointsRoof = {} |
|
|
|
local zOrigin = self:CalcSnappedZ() |
|
local p = self.position |
|
local x, y = p:xyz() |
|
local sx = abs(self.size:x()) |
|
local sy = abs(self.size:y()) |
|
local sz = abs(self.size:z()) |
|
|
|
for inX = 0, sx - 1 do |
|
for inY = 0, sy - 1 do |
|
xPoints[inY] = xPoints[inY] or pstr("") |
|
yPoints[inX] = yPoints[inX] or pstr("") |
|
|
|
xPointsRoof[inY] = xPointsRoof[inY] or pstr("") |
|
yPointsRoof[inX] = yPointsRoof[inX] or pstr("") |
|
|
|
local xx, yy, zz, ox, oy, oz |
|
|
|
xx = x + inX * voxelSizeX + halfVoxelSizeX |
|
if inX == 0 then |
|
ox = xx - halfVoxelSizeX + offsetFromVoxelEdge |
|
elseif inX == sx - 1 then |
|
ox = xx + halfVoxelSizeX - offsetFromVoxelEdge |
|
else |
|
ox = xx |
|
end |
|
|
|
yy = y + inY * voxelSizeY + halfVoxelSizeY |
|
if inY == 0 then |
|
oy = yy - halfVoxelSizeY + offsetFromVoxelEdge |
|
elseif inY == sy - 1 then |
|
oy = yy + halfVoxelSizeY - offsetFromVoxelEdge |
|
else |
|
oy = yy |
|
end |
|
|
|
zz = zOrigin + offsetFromVoxelEdge |
|
oz = zz + sz * voxelSizeZ - offsetFromVoxelEdge*2 |
|
|
|
if inX == 0 then |
|
|
|
xPoints[inY]:AppendVertex(ox, yy, oz, self.wireframeColor) |
|
end |
|
xPoints[inY]:AppendVertex(ox, yy, zz, self.wireframeColor) |
|
if sx == 1 then |
|
xPointsRoof[inY]:AppendVertex(ox, yy, oz, self.wireframeColor) |
|
ox = xx + halfVoxelSizeX - offsetFromVoxelEdge |
|
xPoints[inY]:AppendVertex(ox, yy, zz, self.wireframeColor) |
|
end |
|
if inX == sx - 1 then |
|
|
|
xPoints[inY]:AppendVertex(ox, yy, oz, self.wireframeColor) |
|
end |
|
|
|
if inY == 0 then |
|
|
|
yPoints[inX]:AppendVertex(xx, oy, oz, self.wireframeColor) |
|
end |
|
yPoints[inX]:AppendVertex(xx, oy, zz, self.wireframeColor) |
|
if sy == 1 then |
|
yPointsRoof[inX]:AppendVertex(xx, oy, oz, self.wireframeColor) |
|
oy = yy + halfVoxelSizeY - offsetFromVoxelEdge |
|
yPoints[inX]:AppendVertex(xx, oy, zz, self.wireframeColor) |
|
end |
|
if inY == sy - 1 then |
|
|
|
yPoints[inX]:AppendVertex(xx, oy, oz, self.wireframeColor) |
|
end |
|
|
|
xPointsRoof[inY]:AppendVertex(ox, yy, oz, self.wireframeColor) |
|
yPointsRoof[inX]:AppendVertex(xx, oy, oz, self.wireframeColor) |
|
end |
|
end |
|
|
|
local visible = self.wireframe_visible |
|
local function SetVisibilityHelper(line) |
|
if not visible then |
|
line:ClearEnumFlags(const.efVisible) |
|
end |
|
end |
|
|
|
for inX = 0, sx - 1 do |
|
local line = PlaceObject("Polyline") |
|
SetVisibilityHelper(line) |
|
line:SetPos(p) |
|
SetLineMesh(line, yPoints[inX]) |
|
table.insert(lines, line) |
|
self:Attach(line) |
|
line = PlaceObject("Polyline") |
|
SetVisibilityHelper(line) |
|
line:SetPos(p) |
|
SetLineMesh(line, yPointsRoof[inX]) |
|
table.insert(lines, line) |
|
self:Attach(line) |
|
end |
|
|
|
for inY = 0, sy - 1 do |
|
local line = PlaceObject("Polyline") |
|
SetVisibilityHelper(line) |
|
line:SetPos(p) |
|
SetLineMesh(line, xPoints[inY]) |
|
table.insert(lines, line) |
|
self:Attach(line) |
|
line = PlaceObject("Polyline") |
|
SetVisibilityHelper(line) |
|
line:SetPos(p) |
|
SetLineMesh(line, xPointsRoof[inY]) |
|
table.insert(lines, line) |
|
self:Attach(line) |
|
end |
|
|
|
self.lines = lines |
|
end |
|
|
|
function Volume:GetBiggestEncompassingRoom(func, ...) |
|
|
|
local biggestRoom = self |
|
if self.box then |
|
local sizex, sizey = self.box:sizexyz() |
|
local biggestRoomSize = sizex + sizey |
|
EnumVolumes(self.box, function(o, ...) |
|
local szx, szy = o.box:sizexyz() |
|
local size = szx + szy |
|
if size > biggestRoomSize then |
|
if not func or func(o, ...) then |
|
biggestRoom = o |
|
biggestRoomSize = size |
|
end |
|
end |
|
end, ...) |
|
end |
|
return biggestRoom |
|
end |
|
|
|
local function MakeSlabInvulnerable(o, val) |
|
o.forceInvulnerableBecauseOfGameRules = val |
|
o.invulnerable = val |
|
SetupObjInvulnerabilityColorMarkingOnValueChanged(o) |
|
end |
|
|
|
function Volume:MakeOwnedSlabsInvulnerable() |
|
self:ForEachSpawnedObj(function(o) |
|
MakeSlabInvulnerable(o, true) |
|
if IsKindOf(o, "SlabWallObject") then |
|
local os = o.owned_slabs |
|
if os then |
|
for _, oo in ipairs(os) do |
|
MakeSlabInvulnerable(oo, true) |
|
end |
|
end |
|
end |
|
end) |
|
end |
|
|
|
function Volume:MakeOwnedSlabsVulnerable() |
|
local floorsInvul = self.floor == 1 |
|
self:ForEachSpawnedObj(function(o) |
|
if not floorsInvul or not IsKindOf(o, "FloorSlab") then |
|
MakeSlabInvulnerable(o, false) |
|
if IsKindOf(o, "SlabWallObject") then |
|
local os = o.owned_slabs |
|
if os then |
|
for _, oo in ipairs(os) do |
|
MakeSlabInvulnerable(oo, false) |
|
end |
|
end |
|
end |
|
end |
|
end) |
|
end |
|
|
|
function Volume:Destroy() |
|
|
|
end |
|
|
|
function ShowVolumes(bShow, volume_class, max_floor, fn) |
|
MapClearEnumFlags(const.efVisible, "map", "Volume") |
|
if not bShow or not volume_class then return end |
|
MapSetEnumFlags(const.efVisible, "map", volume_class, function(volume, max_floor, fn) |
|
if volume.floor <= max_floor then |
|
fn(volume) |
|
return true |
|
end |
|
end, max_floor or max_int, fn or empty_func) |
|
end |
|
|
|
function SelectVolume(pt) |
|
|
|
local start = ScreenToGame(pt) |
|
local pos = cameraRTS.GetPos() |
|
local dir = start - pos |
|
dir = dir * 1000 |
|
local dir2 = start + dir |
|
local camFloor = cameraTac.GetFloor() + 1 |
|
|
|
|
|
|
|
|
|
|
|
return MapFindMin("map", "Volume", nil, nil, nil, nil, nil, nil, function(volume, dir2, camFloor) |
|
if HideFloorsAboveThisOne then |
|
if volume.floor > HideFloorsAboveThisOne then |
|
return false |
|
end |
|
end |
|
|
|
local p1, p2 = ClipSegmentWithBox3D(start, dir2, volume.box) |
|
if p1 then |
|
|
|
|
|
return p1:Dist2(start) |
|
end |
|
|
|
return false |
|
end, start, dir2, camFloor) or false, start, dir2 |
|
end |
|
|
|
local lastSelectedVolume = false |
|
local function SetSelectedVolumeAndFireEvents(vol) |
|
if vol ~= SelectedVolume then |
|
local oldVolume = SelectedVolume |
|
SelectedVolume = vol |
|
if oldVolume then |
|
lastSelectedVolume = oldVolume |
|
if IsValid(oldVolume) then |
|
oldVolume.wall_text_markers_visible = false |
|
oldVolume:SetPosMarkersVisible(false) |
|
end |
|
Msg("VolumeDeselected", oldVolume) |
|
end |
|
if SelectedVolume then |
|
if SelectedVolume ~= lastSelectedVolume then |
|
SelectedVolume.selected_wall = false |
|
ObjModified(SelectedVolume) |
|
end |
|
|
|
SelectedVolume.wall_text_markers_visible = true |
|
SelectedVolume:SetPosMarkersVisible(true) |
|
editor.ClearSel() |
|
end |
|
Msg("VolumeSelected", SelectedVolume) |
|
end |
|
end |
|
|
|
function SetSelectedVolume(vol) |
|
SetSelectedVolumeAndFireEvents(vol) |
|
if GedRoomEditor then |
|
GedRoomEditorObjList = GedRoomEditor:ResolveObj("root") |
|
CreateRealTimeThread(function() |
|
GedRoomEditor:SetSelection("root", table.find(GedRoomEditorObjList, SelectedVolume)) |
|
end) |
|
end |
|
end |
|
|
|
local doorId = "Door" |
|
local windowId = "Window" |
|
local doorTemplate = "%s_%s" |
|
local Doors_WidthNames = { "Single", "Double" } |
|
local Windows_WidthNames = { "Single", "Double", "Triple" } |
|
|
|
function DoorsDropdown() |
|
return function() |
|
local ret = { {name = "", id = ""} } |
|
|
|
for j = 1, #Doors_WidthNames do |
|
local name = string.format(doorTemplate, doorId, Doors_WidthNames[j]) |
|
local data = {mat = false, width = j, height = 3} |
|
table.insert(ret, {name = name, id = data}) |
|
end |
|
|
|
return ret |
|
end |
|
end |
|
|
|
function WindowsDropdown() |
|
return function() |
|
local ret = { {name = "", id = ""} } |
|
|
|
for j = 1, #Windows_WidthNames do |
|
local name = string.format(doorTemplate, windowId, Windows_WidthNames[j]) |
|
local data = {mat = false, width = j, height = 2} |
|
table.insert(ret, {name = name, id = data}) |
|
end |
|
|
|
return ret |
|
end |
|
end |
|
|
|
function GetDecalPresetData() |
|
return Presets.RoomDecalData.Default |
|
end |
|
|
|
function DecalsDropdown() |
|
return function() |
|
local ret = { {name = "", id = ""} } |
|
local presetData = GetDecalPresetData() |
|
for _, entry in ipairs(presetData) do |
|
local data = { entity = entry.id, } |
|
table.insert(ret, {name = entry.id, id = data}) |
|
end |
|
return ret |
|
end |
|
end |
|
|
|
local function GetAllWindowEntitiesForMaterial(obj) |
|
local material = type(obj) == "string" and obj or obj.linked_obj and obj.linked_obj.material or obj.material |
|
local ret = { false } |
|
for w = 0, 3 do |
|
for h = 1, 3 do |
|
for v = 1, 10 do |
|
local e = SlabWallObjectName(material, h, w, v, false) |
|
if IsValidEntity(e) then |
|
ret[#ret + 1] = { name = e, value = {entity = e, height = h, width = w, subvariant = v, material = material} } |
|
end |
|
end |
|
end |
|
end |
|
|
|
return ret |
|
end |
|
|
|
local function GetAllDoorEntitiesForMaterial(obj) |
|
local material = type(obj) == "string" and obj or obj.linked_obj and obj.linked_obj.material or obj.material |
|
local ret = { false } |
|
for w = 1, 3 do |
|
for h = 3, 4 do |
|
for v = 1, 10 do |
|
local e = SlabWallObjectName(material, h, w, v, true) |
|
if IsValidEntity(e) then |
|
ret[#ret + 1] = { name = e, value = {entity = e, height = h, width = w, subvariant = v, material = material} } |
|
end |
|
|
|
if v == 1 then |
|
e = SlabWallObjectName(material, h, w, nil, true) |
|
if IsValidEntity(e) then |
|
ret[#ret + 1] = { name = e, value = {entity = e, height = h, width = w, subvariant = v, material = material} } |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
return ret |
|
end |
|
|
|
local function SelectedWallNoEdit(self) |
|
return self.selected_wall == false |
|
end |
|
|
|
slabDirToAngle = { |
|
North = 270 * 60, |
|
South = 90 * 60, |
|
West = 180 * 60, |
|
East = 0, |
|
} |
|
|
|
slabAngleToDir = { |
|
[270 * 60] = "North", |
|
[90 * 60] = "South", |
|
[180 * 60] = "West", |
|
[0] = "East", |
|
} |
|
|
|
slabCornerAngleToDir = { |
|
[270 * 60] = "East", |
|
[90 * 60] = "West", |
|
[180 * 60] = "North", |
|
[0] = "South", |
|
} |
|
|
|
function _RoomVisibilityCategoryNoEdit() |
|
return RoomVisibilityCategoryNoEdit() |
|
end |
|
|
|
function RoomVisibilityCategoryNoEdit() |
|
return true |
|
end |
|
|
|
local VisibilityStateItems = { |
|
"Closed", |
|
"Hidden", |
|
"Open" |
|
} |
|
|
|
function SlabMaterialComboItemsWithNone() |
|
return PresetGroupCombo("SlabPreset", "SlabMaterials", nil, noneWallMat) |
|
end |
|
|
|
function SlabMaterialComboItemsOnly() |
|
return function() |
|
local f1 = SlabMaterialComboItemsWithNone() |
|
local ret = f1() |
|
table.remove(ret, 1) |
|
return ret |
|
end |
|
end |
|
|
|
function SlabMaterialComboItemsWithDefault() |
|
return function() |
|
local f1 = SlabMaterialComboItemsWithNone() |
|
local ret = f1() |
|
table.insert(ret, 2, defaultWallMat) |
|
return ret |
|
end |
|
end |
|
|
|
DefineClass.Room = { |
|
__parents = { "Volume", "EditorSubVariantObject" }, |
|
flags = { gofWarped = true }, |
|
|
|
properties = { |
|
{ category = "General", name = "Doors And Windows Are Blocked", id = "doors_windows_blocked", editor = "bool", default = false, }, |
|
{ category = "General", id = "name", name = "Name", editor = "text", default = false, help = "Default 'Room <handle>', renameable." }, |
|
|
|
{ category = "General", id = "size_z", name = "Height (z)", editor = "number", default = defaultVolumeVoxelHeight, min = 0, max = maxRoomVoxelSizeZ, dont_save = true}, |
|
{ category = "General", id = "size_x", name = "Width (x)", editor = "number", default = 1, min = 1, max = maxRoomVoxelSizeX, dont_save = true}, |
|
{ category = "General", id = "size_y", name = "Depth (y)", editor = "number", default = 1, min = 1, max = maxRoomVoxelSizeY, dont_save = true}, |
|
{ category = "General", id = "move_x", name = "Move EW (x)", editor = "number", default = 0, dont_save = true}, |
|
{ category = "General", id = "move_y", name = "Move NS (y)", editor = "number", default = 0, dont_save = true}, |
|
{ category = "General", id = "move_z", name = "Move UD (z)", editor = "number", default = 0, dont_save = true}, |
|
|
|
{ category = "Materials", id = "wall_mat", name = "Wall Material", editor = "preset_id", preset_class = "SlabPreset", preset_group = "SlabMaterials", extra_item = noneWallMat, default = "Planks", |
|
buttons = { |
|
{name = "Reset", func = "ResetWallMaterials"}, |
|
}, |
|
}, |
|
{ category = "Materials", id = "outer_colors", name = "Outer Color Modifier", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, }, |
|
{ category = "Materials", id = "inner_wall_mat", name = "Inner Wall Material", editor = "preset_id", preset_class = "SlabIndoorMaterials", extra_item = noneWallMat, default = "Planks", }, |
|
|
|
{ category = "Materials", id = "inner_colors", name = "Inner Color Modifier", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, }, |
|
{ category = "Materials", id = "north_wall_mat", name = "North Wall Material", editor = "dropdownlist", items = SlabMaterialComboItemsWithDefault, default = defaultWallMat, |
|
buttons = { |
|
{name = "Select", func = "ViewNorthWallFromOutside"}, |
|
} |
|
}, |
|
{ category = "Materials", id = "south_wall_mat", name = "South Wall Material", editor = "dropdownlist", items = SlabMaterialComboItemsWithDefault, default = defaultWallMat, |
|
buttons = { |
|
{name = "Select", func = "ViewSouthWallFromOutside"}, |
|
} |
|
}, |
|
{ category = "Materials", id = "east_wall_mat", name = "East Wall Material", editor = "dropdownlist", items = SlabMaterialComboItemsWithDefault, default = defaultWallMat, |
|
buttons = { |
|
{name = "Select", func = "ViewEastWallFromOutside"}, |
|
} |
|
}, |
|
{ category = "Materials", id = "west_wall_mat", name = "West Wall Material", editor = "dropdownlist", items = SlabMaterialComboItemsWithDefault, default = defaultWallMat, |
|
buttons = { |
|
{name = "Select", func = "ViewWestWallFromOutside"}, |
|
} |
|
}, |
|
{ category = "Materials", id = "floor_mat", name = "Floor Material", editor = "preset_id", preset_class = "SlabPreset", preset_group = "FloorSlabMaterials", extra_item = noneWallMat, default = "Planks", }, |
|
{ category = "Materials", id = "floor_colors", name = "Floor Color Modifier", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, }, |
|
{ category = "Materials", id = "Warped", name = "Warped", editor = "bool", default = true }, |
|
|
|
{ category = "Materials", id = "selected_wall_buttons", name = "selected wall buttons", editor = "buttons", default = false, dont_save = true, read_only = true, |
|
no_edit = SelectedWallNoEdit, |
|
buttons = { |
|
{name = "Clear Wall Selection", func = "ClearSelectedWall"}, |
|
{name = "Delete Doors", func = "UIDeleteDoors"}, |
|
{name = "Delete Windows", func = "UIDeleteWindows"}, |
|
}, |
|
}, |
|
|
|
{ category = "Materials", id = "place_decal", name = "Place Decal", editor = "choice", items = DecalsDropdown, default = "", no_edit = SelectedWallNoEdit,}, |
|
|
|
{ category = "General", id = "spawned_doors", editor = "objects", no_edit = true,}, |
|
{ category = "General", id = "spawned_windows", editor = "objects", no_edit = true,}, |
|
{ category = "General", id = "spawned_decals", editor = "objects", no_edit = true,}, |
|
|
|
{ category = "General", id = "spawned_floors", editor = "objects", no_edit = true}, |
|
{ category = "General", id = "spawned_walls", editor = "objects", no_edit = true,}, |
|
{ category = "General", id = "spawned_corners", editor = "objects", no_edit = true,}, |
|
|
|
{ category = "Not Room Specific", id = "hide_floors_editor", editor = "number", default = 100, name = "Hide Floors Above", dont_save = true}, |
|
|
|
|
|
{ category = "Visibility", name = "Visibility State", id = "visibility_state", editor = "choice", items = VisibilityStateItems, dont_save = true, no_edit = _RoomVisibilityCategoryNoEdit }, |
|
{ category = "Visibility", name = "Focused", id = "is_focused", editor = "bool", default = false, dont_save = true, no_edit = _RoomVisibilityCategoryNoEdit }, |
|
|
|
{ category = "Ignore None Material", name = "Wall", id = "none_wall_mat_does_not_affect_nbrs", editor = "bool", default = false, help = "By default, setting a wall material to none will hide overlapping walls, tick this for it to stop happening. Affects all walls of a room." }, |
|
{ category = "Ignore None Material", name = "Roof Wall", id = "none_roof_wall_mat_does_not_affect_nbrs", editor = "bool", default = false, help = "Same as walls (see above), but for walls that are part of the roof - roof walls." }, |
|
{ category = "Ignore None Material", name = "Floor", id = "none_floor_mat_does_not_affect_nbrs", editor = "bool", default = false, help = "By default, setting a floor material to none will hide overlapping floors, tick this for it to stop happening. Affects all floors of a room." }, |
|
}, |
|
|
|
auto_add_in_editor = true, |
|
|
|
spawned_walls = false, |
|
spawned_corners = false, |
|
spawned_floors = false, |
|
spawned_doors = false, |
|
spawned_windows = false, |
|
spawned_decals = false, |
|
|
|
selected_wall = false, |
|
|
|
next_visibility_state = false, |
|
visibility_state = false, |
|
open_state_collapsed_walls = false, |
|
outside_border = false, |
|
nametag = false, |
|
} |
|
|
|
local function moveHelper(self, key, old_v, x, y, z, ignore_collision) |
|
if IsChangingMap() then return end |
|
self:InternalAlignObj(true) |
|
if not ignore_collision and self:CheckCollision() then |
|
self[key] = old_v |
|
self:InternalAlignObj(true) |
|
print("Could not move room due to collision with other room!") |
|
return false |
|
else |
|
self:MoveAllSpawnedObjs(x, y, z) |
|
Volume.FinishAlign(self) |
|
Msg("RoomMoved", self, x, y, z) |
|
return true |
|
end |
|
end |
|
|
|
function sign(v) |
|
return v ~= 0 and abs(v) / v or 0 |
|
end |
|
|
|
function moveHelperHelper(r, delta) |
|
local old = r.position |
|
delta = delta + halfVoxelPt |
|
r.position = SnapVolumePos(old + delta) |
|
return moveHelper(r, "position", old, delta:x() / voxelSizeX, delta:y() / voxelSizeY, delta:z() / voxelSizeZ) |
|
end |
|
|
|
function Room:EditorExit() |
|
if IsValid(self.nametag) then |
|
self.nametag:ClearEnumFlags(const.efVisible) |
|
end |
|
end |
|
|
|
function Room:EditorEnter() |
|
if IsValid(self.nametag) and self:GetEnumFlags(const.efVisible) ~= 0 then |
|
self.nametag:SetEnumFlags(const.efVisible) |
|
end |
|
end |
|
|
|
function Room:Setname(n) |
|
self.name = n |
|
if not IsValid(self.nametag) then |
|
self.nametag = PlaceObject("TextEditor") |
|
self:Attach(self.nametag) |
|
self.nametag:SetAttachOffset((axis_z * 3 * voxelSizeZ) / 4096) |
|
|
|
if not IsEditorActive() or self:GetEnumFlags(const.efVisible) == 0 then |
|
self.nametag:ClearEnumFlags(const.efVisible) |
|
end |
|
end |
|
self.nametag:SetText(self.name) |
|
end |
|
|
|
local movedRooms = false |
|
function Room_RecalcRoofsOfMovedRooms() |
|
if not movedRooms then return end |
|
for i = 1, #movedRooms do |
|
local room = movedRooms[i] |
|
if IsValid(room) then |
|
room:RecalcRoof() |
|
room:UpdateRoofVfxControllers() |
|
end |
|
end |
|
movedRooms = false |
|
end |
|
|
|
function Room:DelayedRecalcRoof() |
|
movedRooms = table.create_add_unique(movedRooms, self) |
|
if LocalStorage.FilteredCategories["Roofs"] and not XEditorUndo.undoredo_in_progress then |
|
DelayedCall(200, Room_RecalcRoofsOfMovedRooms) |
|
end |
|
end |
|
|
|
|
|
OnMsg.EditorObjectOperationEnding = Room_RecalcRoofsOfMovedRooms |
|
|
|
function Room:AlignObj(pos, angle) |
|
if pos then |
|
assert(IsEditorActive()) |
|
local offset = pos - self:GetPos() |
|
local box = self.box |
|
local didMove = false |
|
if abs(offset:x()) / voxelSizeX > 0 or abs(offset:y()) / voxelSizeY > 0 or abs(offset:z()) / voxelSizeZ > 0 then |
|
didMove = moveHelperHelper(self, offset) |
|
end |
|
if didMove then |
|
ObjModified(self) |
|
assert(self:GetGameFlags(const.gofPermanent) ~= 0) |
|
box = AddRects(box, self.box) |
|
ComputeSlabVisibilityInBox(box) |
|
DelayedCall(500, BuildBuildingsData) |
|
self:DelayedRecalcRoof() |
|
end |
|
else |
|
self:InternalAlignObj() |
|
end |
|
end |
|
|
|
function Room:GetEditorLabel() |
|
return self.name or self.class |
|
end |
|
|
|
function InsertMaterialProperties(name, count) |
|
assert(count >= 1 and count <= 4) |
|
for i = 1, count do |
|
table.insert(Room.properties, { |
|
id = name .. "color" .. count, |
|
editor = "color", |
|
alpha = false, |
|
}) |
|
table.insert(Room.properties, { |
|
id = name .. "metallic" .. count, |
|
editor = "number", |
|
}) |
|
end |
|
end |
|
|
|
|
|
local room_NSWE_lists = { |
|
"spawned_walls", |
|
"spawned_corners", |
|
"spawned_doors", |
|
"spawned_windows", |
|
"spawned_decals", |
|
} |
|
|
|
local room_NSWE_lists_no_DoorsWindows = { |
|
"spawned_walls", |
|
"spawned_corners", |
|
} |
|
|
|
local room_regular_lists = { |
|
"spawned_floors", |
|
"roof_objs", |
|
} |
|
|
|
local room_regular_list_sides = { |
|
"Floor", |
|
false |
|
} |
|
|
|
function ForEachInTable(t, f, ...) |
|
for i = 1, #(t or "") do |
|
local o = t[i] |
|
if IsValid(o) then |
|
f(o, ...) |
|
end |
|
end |
|
end |
|
|
|
function Room:UnlockAllSlabs() |
|
self:UnlockFloor() |
|
self:UnlockAllWalls() |
|
self:UnlockRoof() |
|
end |
|
|
|
function Room:UnlockFloor() |
|
ForEachInTable(self.spawned_floors, Slab.UnlockSubvariant) |
|
end |
|
|
|
function Room:UnlockAllWalls() |
|
for side, t in pairs(self.spawned_walls or empty_table) do |
|
ForEachInTable(t, Slab.UnlockSubvariant) |
|
end |
|
for side, t in pairs(self.spawned_corners or empty_table) do |
|
ForEachInTable(t, Slab.UnlockSubvariant) |
|
end |
|
ForEachInTable(self.roof_objs, function(o) |
|
if not IsKindOf(o, "RoofSlab") then |
|
o:UnlockSubvariant() |
|
end |
|
end) |
|
end |
|
|
|
function Room:UnlockRoof() |
|
ForEachInTable(self.roof_objs, function(o) |
|
if IsKindOf(o, "RoofSlab") then |
|
o:UnlockSubvariant() |
|
end |
|
end) |
|
end |
|
|
|
sideToCornerSides = { |
|
East = { "East", "South" }, |
|
South = { "West", "South" }, |
|
West = { "West", "North" }, |
|
North = { "East", "North" }, |
|
} |
|
|
|
function Room:UnlockWallSide(side) |
|
ForEachInTable(self.spawned_walls and self.spawned_walls[side], Slab.UnlockSubvariant) |
|
local css = sideToCornerSides[side] |
|
for _, cs in ipairs(css) do |
|
ForEachInTable(self.spawned_corners and self.spawned_corners[cs], Slab.UnlockSubvariant) |
|
end |
|
|
|
ForEachInTable(self.roof_objs, function(o, side) |
|
if o.side == side and not IsKindOf(o, "RoofSlab") then |
|
o:UnlockSubvariant() |
|
end |
|
end ,side) |
|
end |
|
|
|
function Room:ForEachSpawnedObjNoDoorsWindows(func, ...) |
|
return self:_ForEachSpawnedObj(room_NSWE_lists_no_DoorsWindows, room_regular_lists, func, ...) |
|
end |
|
|
|
function Room:ForEachSpawnedObj(func, ...) |
|
return self:_ForEachSpawnedObj(room_NSWE_lists, room_regular_lists, func, ...) |
|
end |
|
|
|
function Room:_ForEachSpawnedObj(NSWE_lists, regular_lists, func, ...) |
|
for i = 1, #NSWE_lists do |
|
for side, objs in NSEW_pairs(self[NSWE_lists[i]] or empty_table) do |
|
for j = 1, #objs do |
|
if IsValid(objs[j]) then func(objs[j], ...) end |
|
end |
|
end |
|
end |
|
|
|
for i = 1, #regular_lists do |
|
local lst = self[regular_lists[i]] or "" |
|
for j = 1, #lst do |
|
if IsValid(lst[j]) then func(lst[j], ...) end |
|
end |
|
end |
|
end |
|
|
|
function Room:GetEditorRelatedObjects() |
|
local ret = {} |
|
for i = 1, #room_NSWE_lists do |
|
for side, objs in NSEW_pairs(self[room_NSWE_lists[i]] or empty_table) do |
|
for _, obj in ipairs(objs) do |
|
if obj then |
|
ret[#ret + 1] = obj |
|
if obj:HasMember("owned_objs") and obj.owned_objs then |
|
table.iappend(ret, obj.owned_objs) |
|
end |
|
if obj:HasMember("owned_slabs") and obj.owned_slabs then |
|
table.iappend(ret, obj.owned_slabs) |
|
end |
|
end |
|
end |
|
end |
|
end |
|
for i = 1, #room_regular_lists do |
|
table.iappend(ret, self[room_regular_lists[i]] or empty_table) |
|
end |
|
Msg("GatherRoomRelatedObjects", self, ret) |
|
return ret |
|
end |
|
|
|
function Room:SetWarped(warped, force) |
|
CObject.SetWarped(self, warped) |
|
if force or not IsChangingMap() then |
|
self:ForEachSpawnedObj(function(obj) |
|
obj:SetWarped(warped) |
|
end) |
|
end |
|
end |
|
|
|
local function copyWallObjs(t, offset, room) |
|
local ret = {} |
|
for side, objs in NSEW_pairs(t or empty_table) do |
|
ret[side] = {} |
|
for i = 1, #objs do |
|
local o = objs[i] |
|
local no = PlaceObject(o.class) |
|
no.floor = room.floor |
|
no.width = o.width |
|
no.height = o.height |
|
no.material = o.material |
|
no:SetPos(o:GetPos() + offset) |
|
no:SetAngle(o:GetAngle()) |
|
table.insert(ret[side], no) |
|
no:UpdateEntity() |
|
end |
|
end |
|
|
|
return ret |
|
end |
|
|
|
local function copyDecals(t, offset, room) |
|
local ret = {} |
|
for side, objs in NSEW_pairs(t or empty_table) do |
|
ret[side] = {} |
|
for i = 1, #objs do |
|
local o = objs[i] |
|
local no = PlaceObject(o.class) |
|
no.floor = room.floor |
|
no:SetPos(o:GetPos() + offset) |
|
no:SetAngle(o:GetAngle()) |
|
no.restriction_box = Offset(o.restriction_box, offset) |
|
table.insert(ret[side], no) |
|
end |
|
end |
|
|
|
return ret |
|
end |
|
|
|
function Room:OnSetdoors_windows_blocked() |
|
self:ForEachSpawnedWallObj(function(o, val) |
|
o:SetlockpickState(val and "blocked" or "closed") |
|
end, self.doors_windows_blocked) |
|
end |
|
|
|
function Room:OnSetnone_roof_wall_mat_does_not_affect_nbrs() |
|
self:ComputeRoomVisibility() |
|
end |
|
|
|
function Room:OnSetnone_wall_mat_does_not_affect_nbrs() |
|
self:ComputeRoomVisibility() |
|
end |
|
|
|
function Room:OnCopied(from, offset) |
|
Volume.OnCopied(self, from, offset) |
|
self:CreateAllSlabs() |
|
|
|
self.spawned_doors = copyWallObjs(from.spawned_doors, offset, self) |
|
self.spawned_windows = copyWallObjs(from.spawned_windows, offset, self) |
|
self.spawned_decals = copyDecals(from.spawned_decals, offset, self) |
|
end |
|
|
|
function Room:OnAfterEditorNew(parent, ged, is_paste) |
|
|
|
self.adjacent_rooms = nil |
|
self:AlignObj() |
|
self:CreateAllSlabs() |
|
end |
|
|
|
function Room:OnEditorSetProperty(prop_id, old_value, ged) |
|
if not IsValid(self) then return end |
|
local f = rawget(Room, string.format("OnSet%s", prop_id)) |
|
if f then |
|
f(self, self[prop_id], old_value) |
|
DelayedCall(500, BuildBuildingsData) |
|
end |
|
end |
|
|
|
function Room:OnSethide_floors_editor() |
|
assert(IsEditorActive()) |
|
HideFloorsAboveThisOne = rawget(self, "hide_floors_editor") |
|
HideFloorsAbove(HideFloorsAboveThisOne) |
|
end |
|
|
|
function Room:Gethide_floors_editor() |
|
return HideFloorsAboveThisOne |
|
end |
|
|
|
function Room:OnSetwireframe_visible() |
|
self:ToggleGeometryVisible() |
|
end |
|
|
|
function Room:OnSetwall_text_markers_visible() |
|
self:TogglePosMarkersVisible() |
|
end |
|
|
|
function Room:OnSetinner_wall_mat(val, oldVal) |
|
if val == "" then |
|
val = noneWallMat |
|
self.inner_wall_mat = val |
|
end |
|
if (val == noneWallMat or oldVal == noneWallMat) and val ~= oldVal then |
|
self:UnlockAllWalls() |
|
end |
|
self:SetInnerMaterialToSlabs("North") |
|
self:SetInnerMaterialToSlabs("South") |
|
self:SetInnerMaterialToSlabs("West") |
|
self:SetInnerMaterialToSlabs("East") |
|
self:SetInnerMaterialToRoofObjs() |
|
end |
|
|
|
function Room:OnSetinner_colors(val, oldVal) |
|
self:SetInnerMaterialToSlabs("North") |
|
self:SetInnerMaterialToSlabs("South") |
|
self:SetInnerMaterialToSlabs("West") |
|
self:SetInnerMaterialToSlabs("East") |
|
self:SetInnerMaterialToRoofObjs() |
|
end |
|
|
|
function Room:OnSetouter_colors(val, oldVal) |
|
local function iterateNSEWTableAndSetColor(t) |
|
if not t then return end |
|
for side, list in NSEW_pairs(t) do |
|
for i = 1, #list do |
|
local o = list[i] |
|
if IsValid(o) then |
|
o:Setcolors(val) |
|
end |
|
end |
|
end |
|
end |
|
iterateNSEWTableAndSetColor(self.spawned_walls) |
|
|
|
for side, list in NSEW_pairs(self.spawned_corners) do |
|
for i = 1, #list do |
|
local o = list[i] |
|
if IsValid(o) then |
|
o:SetColorFromRoom() |
|
end |
|
end |
|
end |
|
|
|
for side, list in NSEW_pairs(self.spawned_windows or empty_table) do |
|
for i = 1, #list do |
|
list[i]:UpdateManagedSlabs() |
|
list[i]:RefreshColors() |
|
end |
|
end |
|
|
|
for side, list in NSEW_pairs(self.spawned_doors or empty_table) do |
|
for i = 1, #list do |
|
list[i]:RefreshColors() |
|
end |
|
end |
|
|
|
if self.roof_objs then |
|
for i=1,#self.roof_objs do |
|
local o = self.roof_objs[i] |
|
if IsValid(o) and not IsKindOf(o, "RoofSlab") then |
|
o:Setcolors(val) |
|
end |
|
end |
|
end |
|
end |
|
|
|
function Room:OnSetfloor_colors(val, oldVal) |
|
for i = 1, #(self.spawned_floors or "") do |
|
local o = self.spawned_floors[i] |
|
if IsValid(o) then |
|
o:Setcolors(val) |
|
end |
|
end |
|
end |
|
|
|
function Room:OnSetfloor_mat(val) |
|
self:UnlockFloor() |
|
self:CreateFloor() |
|
end |
|
|
|
function Room:ResetWallMaterials() |
|
local wm = defaultWallMat |
|
local change = self.north_wall_mat ~= wm |
|
self.north_wall_mat = wm |
|
change = change or self.south_wall_mat ~= wm |
|
self.south_wall_mat = wm |
|
change = change or self.west_wall_mat ~= wm |
|
self.west_wall_mat = wm |
|
change = change or self.east_wall_mat ~= wm |
|
self.east_wall_mat = wm |
|
|
|
if change then |
|
ObjModified(self) |
|
self:UnlockAllWalls() |
|
self:CreateAllWalls() |
|
self:RecreateRoof() |
|
end |
|
end |
|
|
|
local function FireWallChangedEventsHelper(self, side, val, oldVal) |
|
local wasWall = oldVal ~= noneWallMat and (oldVal ~= defaultWallMat or self.wall_mat ~= noneWallMat) |
|
local isWall = val ~= noneWallMat and (val ~= defaultWallMat or self.wall_mat ~= noneWallMat) |
|
if not wasWall and isWall then |
|
Msg("RoomAddedWall", self, side) |
|
elseif wasWall and not isWall then |
|
Msg("RoomRemovedWall", self, side) |
|
end |
|
end |
|
|
|
function Room:OnSetnorth_wall_mat(val, oldVal) |
|
self:UnlockWallSide("North") |
|
self:CreateWalls("North", val) |
|
self:RecreateNECornerBeam() |
|
self:RecreateNWCornerBeam() |
|
self:RecreateRoof() |
|
FireWallChangedEventsHelper(self, "North", val, oldVal) |
|
self:CheckWallSizes() |
|
end |
|
|
|
function Room:OnSetsouth_wall_mat(val, oldVal) |
|
self:UnlockWallSide("South") |
|
self:CreateWalls("South", val) |
|
self:RecreateSECornerBeam() |
|
self:RecreateSWCornerBeam() |
|
self:RecreateRoof() |
|
FireWallChangedEventsHelper(self, "South", val, oldVal) |
|
self:CheckWallSizes() |
|
end |
|
|
|
function Room:OnSetwest_wall_mat(val, oldVal) |
|
self:UnlockWallSide("West") |
|
self:CreateWalls("West", val) |
|
self:RecreateSWCornerBeam() |
|
self:RecreateNWCornerBeam() |
|
self:RecreateRoof() |
|
FireWallChangedEventsHelper(self, "West", val, oldVal) |
|
self:CheckWallSizes() |
|
end |
|
|
|
function Room:OnSeteast_wall_mat(val, oldVal) |
|
self:UnlockWallSide("East") |
|
self:CreateWalls("East", val) |
|
self:RecreateSECornerBeam() |
|
self:RecreateNECornerBeam() |
|
self:RecreateRoof() |
|
FireWallChangedEventsHelper(self, "East", val, oldVal) |
|
self:CheckWallSizes() |
|
end |
|
|
|
function Room:SetWallMaterial(val) |
|
local ov = self.wall_mat |
|
self.wall_mat = val |
|
if IsChangingMap() then return end |
|
self:OnSetwall_mat(val, ov) |
|
end |
|
|
|
function Room:OnSetwall_mat(val, oldVal) |
|
if val == "" then |
|
val = noneWallMat |
|
self.wall_mat = val |
|
end |
|
self:UnlockAllWalls() |
|
self:CreateAllWalls() |
|
self:RecreateRoof() |
|
|
|
local wasWall = oldVal ~= noneWallMat |
|
local isWall = val ~= noneWallMat |
|
local ev = false |
|
if wasWall and not isWall then |
|
ev = "RoomRemovedWall" |
|
elseif not wasWall and isWall then |
|
ev = "RoomAddedWall" |
|
end |
|
|
|
if ev then |
|
if self.south_wall_mat == defaultWallMat then |
|
Msg(ev, self, "South") |
|
end |
|
if self.north_wall_mat == defaultWallMat then |
|
Msg(ev, self, "North") |
|
end |
|
if self.west_wall_mat == defaultWallMat then |
|
Msg(ev, self, "West") |
|
end |
|
if self.east_wall_mat == defaultWallMat then |
|
Msg(ev, self, "East") |
|
end |
|
end |
|
end |
|
|
|
function SizeSetterHelper(self, old_v) |
|
if IsChangingMap() then return end |
|
local oldBox = self.box |
|
self:InternalAlignObj(true) |
|
if self:CheckCollision() then |
|
self.size = old_v |
|
self:InternalAlignObj(true) |
|
return false |
|
else |
|
self:Resize(old_v, self.size, oldBox) |
|
Volume.FinishAlign(self) |
|
Msg("RoomResized", self, old_v) |
|
return true |
|
end |
|
end |
|
|
|
function Room:OnSetsize_x(val) |
|
local old_v = self.size |
|
self.size = point(val, self.size:y(), self.size:z()) |
|
SizeSetterHelper(self, old_v) |
|
end |
|
|
|
function Room:OnSetsize_y(val) |
|
local old_v = self.size |
|
self.size = point(self.size:x(), val, self.size:z()) |
|
SizeSetterHelper(self, old_v) |
|
end |
|
|
|
function Room:OnSetsize_z(val) |
|
local old_v = self.size |
|
self.size = point(self.size:x(), self.size:y(), val) |
|
SizeSetterHelper(self, old_v) |
|
if val == 0 then |
|
self:DeleteAllWallObjs() |
|
self:DeleteAllFloors() |
|
self:DeleteAllCornerObjs() |
|
end |
|
end |
|
|
|
function Room:Getsize_x() |
|
return self.size:x() |
|
end |
|
|
|
function Room:Getsize_y() |
|
return self.size:y() |
|
end |
|
|
|
function Room:Getsize_z() |
|
return self.size:z() |
|
end |
|
|
|
function Room:Getmove_x() |
|
local x = WorldToVoxel(self.position) |
|
return x |
|
end |
|
|
|
function Room:Getmove_y() |
|
local _, y = WorldToVoxel(self.position) |
|
return y |
|
end |
|
|
|
function Room:Getmove_z() |
|
local x, y, z = WorldToVoxel(self.position) |
|
return z |
|
end |
|
|
|
function Room:OnSetz_offset(val, old_v) |
|
moveHelper(self, "z_offset", old_v, 0, 0, val - old_v) |
|
end |
|
|
|
function Room:OnSetmove_x(val) |
|
local old_v = self.position |
|
local x, y, z = WorldToVoxel(self.position) |
|
self.position = SnapVolumePos(VoxelToWorld(val, y, z, true)) |
|
moveHelper(self, "position", old_v, val - x, 0, 0) |
|
end |
|
|
|
function Room:OnSetmove_y(val) |
|
local old_v = self.position |
|
local x, y, z = WorldToVoxel(self.position) |
|
self.position = SnapVolumePos(VoxelToWorld(x, val, z, true)) |
|
moveHelper(self, "position", old_v, 0, val - y, 0) |
|
end |
|
|
|
function Room:OnSetmove_z(val) |
|
local old_v = self.position |
|
local x, y, z = WorldToVoxel(self.position) |
|
self.position = SnapVolumePos(VoxelToWorld(x, y, val, true)) |
|
moveHelper(self, "position", old_v, 0, 0, val - z) |
|
end |
|
|
|
|
|
local dirToComparitor = { |
|
South = function(o1, o2) |
|
local x1, _, _ = o1:GetPosXYZ() |
|
local x2, _, _ = o2:GetPosXYZ() |
|
return x1 < x2 |
|
end, |
|
|
|
North = function(o1, o2) |
|
local x1, _, _ = o1:GetPosXYZ() |
|
local x2, _, _ = o2:GetPosXYZ() |
|
return x2 < x1 |
|
end, |
|
|
|
West = function(o1, o2) |
|
local _, y1, _ = o1:GetPosXYZ() |
|
local _, y2, _ = o2:GetPosXYZ() |
|
return y1 < y2 |
|
end, |
|
|
|
East = function(o1, o2) |
|
local _, y1, _ = o1:GetPosXYZ() |
|
local _, y2, _ = o2:GetPosXYZ() |
|
return y2 < y1 |
|
end, |
|
} |
|
|
|
function Room:SortWallObjs(objs, dir) |
|
table.sort(objs, dirToComparitor[dir]) |
|
end |
|
|
|
|
|
function Room:CalculateRestrictionBox(dir, wallPos, wallSize, height, width) |
|
local xofs, nxofs = 0, 0 |
|
local yofs, nyofs = 0, 0 |
|
width = Max(width, 1) |
|
|
|
if dir == "North" or dir == "South" then |
|
xofs = (wallSize / 2 - width * voxelSizeX / 2) |
|
nxofs = xofs |
|
if width % 2 == 0 then |
|
local m = (dir == "South" and -1 or 1) |
|
xofs = xofs + m * voxelSizeX / 2 |
|
nxofs = nxofs - m * voxelSizeX / 2 |
|
end |
|
else |
|
yofs = (wallSize / 2 - width * voxelSizeY / 2) |
|
nyofs = yofs |
|
if width % 2 == 0 then |
|
local m = (dir == "West" and -1 or 1) |
|
yofs = yofs + m * voxelSizeX / 2 |
|
nyofs = nyofs - m * voxelSizeX / 2 |
|
end |
|
end |
|
|
|
local maxZ = wallPos:z() + (self.size:z() * voxelSizeZ - height * voxelSizeZ) |
|
return box(wallPos:x() - nxofs, wallPos:y() - nyofs, wallPos:z(), wallPos:x() + xofs, wallPos:y() + yofs, maxZ) |
|
end |
|
|
|
function Room:FindSlabObjPos(dir, width, height) |
|
local sizeX, sizeY = self.size:x(), self.size:y() |
|
if dir == "North" or dir == "South" then |
|
if width > sizeX then |
|
print("Obj is too big") |
|
return false |
|
end |
|
else |
|
if width > sizeY then |
|
print("Obj is too big") |
|
return false |
|
end |
|
end |
|
|
|
local z = self:CalcZ() + (3 - height) * voxelSizeZ |
|
local angle = 0 |
|
local sx, sy = self.position:x(), self.position:y() |
|
local offsx = 0 |
|
local offsy = 0 |
|
local max = 0 |
|
|
|
if dir == "North" then |
|
angle = 270 * 60 |
|
offsx = voxelSizeX |
|
sx = sx + halfVoxelSizeX |
|
max = sizeX |
|
elseif dir == "East" then |
|
angle = 0 |
|
offsy = voxelSizeY |
|
sx = sx + sizeX * voxelSizeX |
|
sy = sy + halfVoxelSizeY |
|
max = sizeY |
|
elseif dir == "South" then |
|
angle = 90 * 60 |
|
offsx = voxelSizeX |
|
sy = sy + sizeY * voxelSizeY |
|
sx = sx + halfVoxelSizeX |
|
max = sizeX |
|
elseif dir == "West" then |
|
angle = 180 * 60 |
|
offsy = voxelSizeY |
|
sy = sy + halfVoxelSizeY |
|
max = sizeY |
|
end |
|
|
|
local iStart = width == 3 and 1 or 0 |
|
|
|
for i = iStart, max - 1 do |
|
local x = sx + offsx * i |
|
local y = sy + offsy * i |
|
|
|
local newPos = point(x, y, z) |
|
local canPlace = not IntersectWallObjs(nil, newPos, width, height, angle) |
|
|
|
if canPlace then |
|
return newPos |
|
end |
|
end |
|
|
|
return false |
|
end |
|
|
|
function Room:NewSlabWallObj(obj, class) |
|
class = class or SlabWallObject |
|
return class:new(obj) |
|
end |
|
|
|
function Room:ForEachSpawnedWindow(func, ...) |
|
for _, t in sorted_pairs(self.spawned_windows or empty_table) do |
|
for i = #t, 1, -1 do |
|
func(t[i], ...) |
|
end |
|
end |
|
end |
|
|
|
function Room:ForEachSpawnedDoor(func, ...) |
|
for _, t in sorted_pairs(self.spawned_doors or empty_table) do |
|
for i = #t, 1, -1 do |
|
func(t[i], ...) |
|
end |
|
end |
|
end |
|
|
|
function Room:ForEachSpawnedWallObj(func, ...) |
|
self:ForEachSpawnedDoor(func, ...) |
|
self:ForEachSpawnedWindow(func, ...) |
|
end |
|
|
|
function Room:PlaceWallObj(val, side, class) |
|
local dir = side or self.selected_wall |
|
assert(dir) |
|
if not dir then return end |
|
|
|
local freePos = self:FindSlabObjPos(dir, val.width, val.height) |
|
|
|
if not freePos then |
|
print("No free pos found!") |
|
return |
|
end |
|
local wallPos, wallSize, center = self:GetWallPos(dir) |
|
local obj = self:NewSlabWallObj({ |
|
entity = false, room = self, material = val.mat or "Planks", |
|
building_class = val.building_class or nil, building_template = val.building_template or nil, |
|
side = dir |
|
}, |
|
class.class) |
|
local a = slabDirToAngle[dir] |
|
local zPosOffset = (3 - val.height) * voxelSizeZ |
|
local vx, vy, vz, va = WallWorldToVoxel(freePos:x(), freePos:y(), wallPos:z() + zPosOffset, a) |
|
local pos = point(WallVoxelToWorld(vx, vy, vz, va)) |
|
obj.room = self |
|
obj.floor = self.floor |
|
obj.subvariant = 1 |
|
obj:SetPos(pos) |
|
obj:SetAngle(a) |
|
obj:SetProperty("width", val.width) |
|
obj:SetProperty("height", val.height) |
|
obj:AlignObj() |
|
obj:UpdateEntity() |
|
|
|
local container, nestedList |
|
if val.is_door or obj:IsDoor() then |
|
self.spawned_doors = self.spawned_doors or {} |
|
self.spawned_doors[dir] = self.spawned_doors[dir] or {} |
|
container = self.spawned_doors |
|
else |
|
self.spawned_windows = self.spawned_windows or {} |
|
self.spawned_windows[dir] = self.spawned_windows[dir] or {} |
|
container = self.spawned_windows |
|
end |
|
|
|
if container then |
|
table.insert(container[dir], obj) |
|
end |
|
|
|
if Platform.editor and IsEditorActive() then |
|
editor.ClearSel() |
|
editor.AddToSel({obj}) |
|
end |
|
|
|
return obj |
|
end |
|
|
|
|
|
function Room:CalculateDecalRestrictionBox(dir, wallPos, wallSize) |
|
local xofs, nxofs = 0, 0 |
|
local yofs, nyofs = 0, 0 |
|
if dir == "North" or dir == "South" then |
|
xofs = wallSize / 2 |
|
nxofs = xofs |
|
|
|
wallPos = wallPos:SetY(wallPos:y() + 100 * (dir == "North" and -1 or 1)) |
|
else |
|
yofs = wallSize / 2 |
|
nyofs = yofs |
|
|
|
wallPos = wallPos:SetX(wallPos:x() + 100 * (dir == "West" and -1 or 1)) |
|
end |
|
local maxZ = wallPos:z() + (self.size:z() * voxelSizeZ) + 1 |
|
return box(wallPos:x() - nxofs, wallPos:y() - nyofs, wallPos:z(), wallPos:x() + xofs, wallPos:y() + yofs, maxZ) |
|
end |
|
|
|
DefineClass.RoomDecal = { |
|
__parents = { "AlignedObj", "Decal", "Shapeshifter", "Restrictor", "HideOnFloorChange" }, |
|
properties = { |
|
{ category = "General", id = "entity", editor = "text", default = false, no_edit = true }, |
|
}, |
|
flags = { cfAlignObj = true, cfDecal = true, efCollision = false, gofPermanent = true, }, |
|
} |
|
|
|
function RoomDecal:AlignObj(pos, angle, axis) |
|
pos = pos or self:GetPos() |
|
local x, y, z = self:RestrictXYZ(pos:xyz()) |
|
|
|
self:SetPos(x, y, z) |
|
self:SetAxisAngle(axis or self:GetAxis(), angle or self:GetAngle()) |
|
end |
|
|
|
function RoomDecal:ChangeEntity(val) |
|
Shapeshifter.ChangeEntity(self, val) |
|
self.entity = val |
|
end |
|
|
|
function RoomDecal:GameInit() |
|
if IsChangingMap() and self.entity then |
|
Shapeshifter.ChangeEntity(self, self.entity) |
|
end |
|
end |
|
|
|
function RoomDecal:Done() |
|
local safe = rawget(self, "safe_deletion") |
|
if not safe then |
|
|
|
local box = self.restriction_box |
|
if box then |
|
local passed = {} |
|
MapForEach(box:grow(100, 100, 0), "WallSlab", function(s) |
|
local side = s.side |
|
local room = s.room |
|
if room then |
|
local id = xxhash(room.handle, side) |
|
if not passed[id] then |
|
passed[id] = true |
|
local t = room.spawned_decals[side] |
|
local t_idx = table.find(t, self) |
|
if t_idx then |
|
table.remove(t, t_idx) |
|
ObjModified(room) |
|
return "break" |
|
end |
|
end |
|
end |
|
end) |
|
else |
|
local b = self:GetObjectBBox() |
|
local success = false |
|
b = b:grow(guim, guim, guim) |
|
EnumVolumes(b, function(r) |
|
local t = r.spawned_decals |
|
for side, tt in pairs(t or empty_table) do |
|
local t_idx = table.find(tt, self) |
|
if t_idx then |
|
table.remove(tt, t_idx) |
|
ObjModified(r) |
|
success = true |
|
return "break" |
|
end |
|
end |
|
end) |
|
|
|
if not success then |
|
assert(false, "RoomDecal not safely deleted, ref in room remains!") |
|
end |
|
end |
|
end |
|
end |
|
|
|
function Room:Setplace_decal(val) |
|
local dir = self.selected_wall |
|
if not dir then return end |
|
|
|
local wallPos, wallSize, center = self:GetWallPos(dir) |
|
local a = slabDirToAngle[dir] |
|
|
|
local obj = RoomDecal:new() |
|
obj.floor = self.floor |
|
obj:ChangeEntity(val.entity) |
|
obj:SetAngle(a) |
|
local xOffs = 0 |
|
local yOffs = 0 |
|
if dir == "East" then |
|
obj:SetAxis(axis_y) |
|
obj:SetAngle(90 * 60) |
|
xOffs = 100 |
|
elseif dir == "West" then |
|
obj:SetAxis(axis_y) |
|
obj:SetAngle(-90 * 60) |
|
xOffs = -100 |
|
elseif dir == "North" then |
|
obj:SetAxis(axis_x) |
|
obj:SetAngle(90 * 60) |
|
yOffs = -100 |
|
elseif dir == "South" then |
|
obj:SetAxis(axis_x) |
|
obj:SetAngle(-90 * 60) |
|
yOffs = 100 |
|
end |
|
obj:SetPos(wallPos + point(xOffs, yOffs, voxelSizeZ * self.size:z() / 2)) |
|
obj.restriction_box = self:CalculateDecalRestrictionBox(dir, wallPos, wallSize) |
|
|
|
|
|
self.spawned_decals = self.spawned_decals or {} |
|
self.spawned_decals[dir] = self.spawned_decals[dir] or {} |
|
table.insert(self.spawned_decals[dir], obj) |
|
|
|
editor.ClearSel() |
|
editor.AddToSel({obj}) |
|
|
|
self.place_decal = "temp" |
|
ObjModified(self) |
|
self.place_decal = "" |
|
ObjModified(self) |
|
end |
|
|
|
function Room:DeleteWallObjHelper(d) |
|
d:RestoreAffectedSlabs() |
|
DoneObject(d) |
|
ObjModified(self) |
|
end |
|
|
|
function Room:DeleteWallObjs(container, dir) |
|
if not dir then |
|
self:DeleteWallObjs("North", container) |
|
self:DeleteWallObjs("South", container) |
|
self:DeleteWallObjs("East", container) |
|
self:DeleteWallObjs("West", container) |
|
self:DeleteAllFloors() |
|
self:DeleteAllCornerObjs() |
|
else |
|
local t = container and container[dir] |
|
for i = #(t or empty_table), 1, -1 do |
|
if IsValid(t[i]) then |
|
self:DeleteWallObjHelper(t[i]) |
|
end |
|
t[i] = nil |
|
end |
|
ObjModified(self) |
|
end |
|
end |
|
|
|
function Room:RebuildAllSlabs() |
|
self:DeleteAllSlabs() |
|
self:CreateAllSlabs() |
|
self:RecreateRoof("force") |
|
end |
|
|
|
function Room:DoneObjectsInNWESTable(t) |
|
for k, v in NSEW_pairs(t or empty_table) do |
|
|
|
while #v > 0 do |
|
local idx = #v |
|
DoneObject(v[idx]) |
|
v[idx] = nil |
|
end |
|
end |
|
end |
|
|
|
function Room:DeleteAllSlabs() |
|
SuspendPassEdits("Room:DeleteAllSpawnedObjs") |
|
self:DeleteAllWallObjs() |
|
self:DeleteAllCornerObjs() |
|
self:DeleteAllFloors() |
|
self:DeleteRoofObjs() |
|
ResumePassEdits("Room:DeleteAllSpawnedObjs") |
|
end |
|
|
|
function Room:DeleteAllSpawnedObjs() |
|
SuspendPassEdits("Room:DeleteAllSpawnedObjs") |
|
self:DeleteAllWallObjs() |
|
self:DeleteAllCornerObjs() |
|
self:DeleteAllFloors() |
|
self:DeleteRoofObjs() |
|
self:DoneObjectsInNWESTable(self.spawned_doors) |
|
self:DoneObjectsInNWESTable(self.spawned_windows) |
|
self:DoneObjectsInNWESTable(self.spawned_decals) |
|
ResumePassEdits("Room:DeleteAllSpawnedObjs") |
|
end |
|
|
|
function Room:DeleteAllFloors() |
|
SuspendPassEdits("Room:DeleteAllFloors") |
|
DoneObjects(self.spawned_floors, "clear") |
|
ResumePassEdits("Room:DeleteAllFloors") |
|
Msg("RoomDestroyedFloor", self) |
|
end |
|
|
|
function Room:DeleteAllCornerObjs() |
|
for k, v in NSEW_pairs(self.spawned_corners or empty_table) do |
|
DoneObjects(v, "clear") |
|
end |
|
end |
|
|
|
function Room:DeleteAllWallObjs() |
|
SuspendPassEdits("Room:DeleteAllWallObjs") |
|
for k, v in NSEW_pairs(self.spawned_walls or empty_table) do |
|
DoneObjects(v, "clear") |
|
end |
|
ResumePassEdits("Room:DeleteAllWallObjs") |
|
end |
|
|
|
function Room:HasWall(mat) |
|
return mat ~= noneWallMat and (mat ~= defaultWallMat or self.wall_mat ~= noneWallMat) |
|
end |
|
|
|
function Room:HasWallOnSide(side) |
|
return self:GetWallMatHelperSide(side) ~= noneWallMat |
|
end |
|
|
|
function Room:HasAllWalls() |
|
for _, side in ipairs(CardinalDirectionNames) do |
|
if not self:HasWallOnSide(side) then |
|
return false |
|
end |
|
end |
|
|
|
return true |
|
end |
|
|
|
function Room:RecreateNWCornerBeam() |
|
local mat = self.north_wall_mat |
|
if mat == noneWallMat then |
|
mat = self.west_wall_mat |
|
end |
|
self:CreateCornerBeam("North", mat) |
|
end |
|
|
|
function Room:RecreateSWCornerBeam() |
|
local mat = self.west_wall_mat |
|
if mat == noneWallMat then |
|
mat = self.south_wall_mat |
|
end |
|
self:CreateCornerBeam("West", mat) |
|
end |
|
|
|
function Room:RecreateNECornerBeam() |
|
local mat = self.east_wall_mat |
|
if mat == noneWallMat then |
|
mat = self.north_wall_mat |
|
end |
|
self:CreateCornerBeam("East", mat) |
|
end |
|
|
|
function Room:RecreateSECornerBeam() |
|
local mat = self.south_wall_mat |
|
if mat == noneWallMat then |
|
mat = self.east_wall_mat |
|
end |
|
self:CreateCornerBeam("South", mat) |
|
end |
|
|
|
function Room:CreateAllWalls() |
|
SuspendPassEdits("Room:CreateAllWalls") |
|
self:CreateWalls("North", self.north_wall_mat) |
|
self:CreateWalls("South", self.south_wall_mat) |
|
self:CreateWalls("West", self.west_wall_mat) |
|
self:CreateWalls("East", self.east_wall_mat) |
|
self:CheckWallSizes() |
|
ResumePassEdits("Room:CreateAllWalls") |
|
end |
|
|
|
function Room:CreateAllSlabs() |
|
SuspendPassEdits("Room:CreateAllSlabs") |
|
self:CreateAllWalls() |
|
self:CreateFloor() |
|
self:CreateAllCorners() |
|
if not self.being_placed then |
|
self:RecreateRoof() |
|
end |
|
self:SetWarped(self:GetWarped(), true) |
|
ResumePassEdits("Room:CreateAllSlabs") |
|
end |
|
|
|
function Room:RefreshFloorCombatStatus() |
|
local floorsAreCO = g_Classes.CombatObject and IsKindOf(FloorSlab, "CombatObject") |
|
if not floorsAreCO then return end |
|
|
|
local flr = self.floor |
|
local val = not self:IsRoofOnly() and flr == 1 |
|
for i = 1, #(self.spawned_floors or "") do |
|
local f = self.spawned_floors[i] |
|
if IsValid(f) then |
|
f.impenetrable = val |
|
f.invulnerable = val |
|
f.forceInvulnerableBecauseOfGameRules = val |
|
end |
|
end |
|
end |
|
|
|
function Room:CreateFloor(mat, startI, startJ) |
|
mat = mat or self.floor_mat |
|
self.spawned_floors = self.spawned_floors or {} |
|
local objs = self.spawned_floors |
|
|
|
local gz = self:CalcZ() |
|
local sx, sy = self.position:x(), self.position:y() |
|
local sizeX, sizeY, sizeZ = self.size:xyz() |
|
|
|
if sizeZ <= 0 then |
|
self:DeleteAllFloors() |
|
print("<color 0 255 38>Removed floor because it is a zero height room. </color>") |
|
return |
|
end |
|
|
|
sx = sx + halfVoxelSizeX |
|
sy = sy + halfVoxelSizeY |
|
startI = startI or 0 |
|
startJ = startJ or 0 |
|
|
|
if self:GetGameFlags(const.gofPermanent) ~= 0 then |
|
local floorBBox = box(sx, sy, gz, sx + voxelSizeX * (sizeX - 1), sy + voxelSizeY * (sizeY - 1), gz + 1) |
|
ComputeSlabVisibilityInBox(floorBBox) |
|
end |
|
|
|
SuspendPassEdits("Room:CreateFloor") |
|
local insertElements = startJ ~= 0 and #objs < sizeX * sizeY |
|
local floorsAreCO = g_Classes.CombatObject and IsKindOf(FloorSlab, "CombatObject") |
|
local floorsAreInvulnerable = floorsAreCO and not self:IsRoofOnly() and self.floor == 1 |
|
for xOffset = startI, sizeX - 1 do |
|
for yOffset = xOffset == startI and startJ or 0, sizeY - 1 do |
|
local x = sx + xOffset * voxelSizeX |
|
local y = sy + yOffset * voxelSizeY |
|
local idx = xOffset * sizeY + yOffset + 1 |
|
|
|
if insertElements then |
|
if #objs < idx then |
|
objs[idx] = false |
|
else |
|
table.insert(objs, idx, false) |
|
end |
|
|
|
insertElements = insertElements and #objs < sizeX * sizeY |
|
end |
|
|
|
local floor = objs[idx] |
|
if not IsValid(floor) then |
|
floor = FloorSlab:new{floor = self.floor, material = mat, side = "Floor", room = self} |
|
floor:SetPos(x, y, gz) |
|
floor:AlignObj() |
|
floor:UpdateEntity() |
|
floor:Setcolors(self.floor_colors) |
|
objs[idx] = floor |
|
else |
|
floor:SetPos(x, y, gz) |
|
if floor.material ~= mat then |
|
floor.material = mat |
|
floor:UpdateEntity() |
|
else |
|
floor:UpdateSimMaterialId() |
|
end |
|
|
|
end |
|
|
|
floor.floor = self.floor |
|
if floorsAreCO then |
|
floor.impenetrable = floorsAreInvulnerable |
|
floor.invulnerable = floorsAreInvulnerable |
|
floor.forceInvulnerableBecauseOfGameRules = floorsAreInvulnerable |
|
end |
|
end |
|
end |
|
ResumePassEdits("Room:CreateFloor") |
|
Msg("RoomCreatedFloor", self, mat) |
|
end |
|
|
|
function Room:CreateCornerBeam(dir, mat) |
|
self.spawned_corners = self.spawned_corners or {North = {}, South = {}, West = {}, East = {}} |
|
local objs = self.spawned_corners[dir] |
|
|
|
if mat == defaultWallMat then |
|
mat = self.wall_mat |
|
end |
|
|
|
local gz = self:CalcSnappedZ() |
|
local sx, sy = self.position:x(), self.position:y() |
|
local sizeX, sizeY = self.size:x(), self.size:y() |
|
if dir == "South" or dir == "West" then |
|
sy = sy + sizeY * voxelSizeY |
|
end |
|
|
|
if dir == "South" or dir == "East" then |
|
sx = sx + sizeX * voxelSizeX |
|
end |
|
|
|
local count = self.size:z() + 1 |
|
if count < #objs then |
|
for i = #objs, count + 1, -1 do |
|
DoneObject(objs[i]) |
|
objs[i] = nil |
|
end |
|
end |
|
|
|
local isPermanent = self:GetGameFlags(const.gofPermanent) ~= 0 |
|
local sz = self.size:z() |
|
|
|
if sz > 0 then |
|
for j = 0, sz do |
|
local z = gz + voxelSizeZ * Min(j, self.size:z() - 1) |
|
local pt = point(sx, sy, z) |
|
|
|
local obj = objs[j + 1] |
|
if not IsValid(obj) then |
|
obj = PlaceObject("RoomCorner", {room = self, side = dir, floor = self.floor, material = mat}) |
|
objs[j + 1] = obj |
|
end |
|
obj.isPlug = j == self.size:z() |
|
obj:SetPos(pt) |
|
obj.material = mat |
|
obj.invulnerable = false |
|
obj.forceInvulnerableBecauseOfGameRules = false |
|
if not isPermanent then |
|
obj:UpdateEntity() |
|
end |
|
end |
|
end |
|
|
|
if isPermanent then |
|
local box = box(sx, sy, gz, sx, sy, gz + voxelSizeZ * (self.size:z() - 1)) |
|
ComputeSlabVisibilityInBox(box) |
|
end |
|
end |
|
|
|
function Room:GetWallMatHelperSide(side) |
|
local m = dirToWallMatMember[side] |
|
return m and self:GetWallMatHelper(self[m]) or nil |
|
end |
|
|
|
function Room:GetWallMatHelper(mat) |
|
return mat == defaultWallMat and self.wall_mat or mat |
|
end |
|
|
|
function Room:RecalcAllRestrictionBoxes(dir, containers) |
|
local wallPos, wallSize, center = self:GetWallPos(dir) |
|
for j = 1, #(containers or empty_table) do |
|
local container = containers[j] |
|
local t = container and container[dir] |
|
|
|
|
|
for i = #(t or ""), 1, -1 do |
|
if type(t[i]) == "boolean" then |
|
table.remove(t, i) |
|
print("once", "Found badly saved decals/windows/doors!") |
|
end |
|
end |
|
|
|
if t and IsKindOf(t[1], "RoomDecal") then |
|
for i = 1, #(t or empty_table) do |
|
local o = t[i] |
|
if o then |
|
o.restriction_box = self:CalculateDecalRestrictionBox(dir, wallPos, wallSize) |
|
o:AlignObj() |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
function Room:Resize(oldSize, newSize, oldBox) |
|
if oldSize == newSize then |
|
return |
|
end |
|
|
|
SuspendPassEdits("Room:Resize") |
|
local delta = newSize - oldSize |
|
|
|
local offsetY = delta:y() * voxelSizeY |
|
local offsetX = delta:x() * voxelSizeX |
|
local offsetZ = delta:z() * voxelSizeZ |
|
local sx, sy = self.position:x(), self.position:y() |
|
sx = sx + halfVoxelSizeX |
|
sy = sy + halfVoxelSizeY |
|
local sizeX, sizeY = newSize:x(), newSize:y() |
|
|
|
local function moveObjs(objs) |
|
if not objs then return end |
|
for i = 1, #objs do |
|
local o = objs[i] |
|
if IsValid(o) then |
|
local x, y, z = o:GetPosXYZ() |
|
o:SetPos(x + offsetX, y + offsetY, z + offsetZ) |
|
end |
|
end |
|
end |
|
|
|
local function moveObjX(o) |
|
local x, y, z = o:GetPosXYZ() |
|
o:SetPos(x + offsetX, y, z) |
|
if IsKindOf(o, "SlabWallObject") then |
|
o:UpdateManagedObj() |
|
end |
|
end |
|
|
|
local function moveObjsX(objs) |
|
if not objs then return end |
|
for i = 1, #objs do |
|
local o = objs[i] |
|
if IsValid(o) then |
|
moveObjX(o) |
|
end |
|
end |
|
end |
|
|
|
local function moveObjY(o) |
|
local x, y, z = o:GetPosXYZ() |
|
o:SetPos(x, y + offsetY, z) |
|
if IsKindOf(o, "SlabWallObject") then |
|
o:UpdateManagedObj() |
|
end |
|
end |
|
|
|
local function moveObjsY(objs) |
|
if not objs then return end |
|
for i = 1, #objs do |
|
local o = objs[i] |
|
if IsValid(o) then |
|
moveObjY(o) |
|
end |
|
end |
|
end |
|
|
|
local function moveObjsZ(objs) |
|
if not objs then return end |
|
for i = 1, #objs do |
|
local o = objs[i] |
|
if IsValid(o) then |
|
local x, y, z = o:GetPosXYZ() |
|
o:SetPos(x, y, z + offsetZ) |
|
end |
|
end |
|
end |
|
|
|
if delta:y() ~= 0 then |
|
|
|
moveObjsY(self.spawned_walls and self.spawned_walls.South) |
|
moveObjsY(self.spawned_doors and self.spawned_doors.South) |
|
moveObjsY(self.spawned_windows and self.spawned_windows.South) |
|
if self.spawned_corners then |
|
moveObjsY(self.spawned_corners.South) |
|
moveObjsY(self.spawned_corners.West) |
|
end |
|
local containers = {self.spawned_doors, self.spawned_windows, self.spawned_decals} |
|
self:RecalcAllRestrictionBoxes("East", containers) |
|
self:RecalcAllRestrictionBoxes("West", containers) |
|
self:RecalcAllRestrictionBoxes("South", containers) |
|
end |
|
if delta:x() ~= 0 then |
|
|
|
moveObjsX(self.spawned_walls and self.spawned_walls.East) |
|
moveObjsX(self.spawned_doors and self.spawned_doors.East) |
|
moveObjsX(self.spawned_windows and self.spawned_windows.East) |
|
if self.spawned_corners then |
|
moveObjsX(self.spawned_corners.South) |
|
moveObjsX(self.spawned_corners.East) |
|
end |
|
local containers = {self.spawned_doors, self.spawned_windows, self.spawned_decals} |
|
self:RecalcAllRestrictionBoxes("North", containers) |
|
self:RecalcAllRestrictionBoxes("East", containers) |
|
self:RecalcAllRestrictionBoxes("South", containers) |
|
end |
|
|
|
if delta:z() ~= 0 then |
|
if delta:z() > 0 then |
|
local move = delta:x() ~= 0 or delta:y() ~= 0 |
|
self:CreateWalls("South", self.south_wall_mat, nil, not move and oldSize:z(), nil, nil, move) |
|
self:CreateWalls("North", self.north_wall_mat, nil, not move and oldSize:z(), nil, nil, move) |
|
self:CreateWalls("East", self.east_wall_mat, nil, not move and oldSize:z(), nil, nil, move) |
|
self:CreateWalls("West", self.west_wall_mat, nil, not move and oldSize:z(), nil, nil, move) |
|
else |
|
self:DestroyWalls("East", nil, oldSize, nil, newSize:z()) |
|
self:DestroyWalls("West", nil, oldSize, nil, newSize:z()) |
|
self:DestroyWalls("North", nil, oldSize, nil, newSize:z()) |
|
self:DestroyWalls("South", nil, oldSize, nil, newSize:z()) |
|
end |
|
end |
|
|
|
if delta:y() ~= 0 then |
|
if delta:y() < 0 then |
|
local count = abs(delta:y()) |
|
self:DestroyWalls("East", count, oldSize:SetZ(newSize:z())) |
|
self:DestroyWalls("West", count, oldSize:SetZ(newSize:z())) |
|
|
|
|
|
local floors = self.spawned_floors |
|
for i = oldSize:x() - 1, 0, -1 do |
|
for j = oldSize:y() - 1, newSize:y(), -1 do |
|
local idx = (i * oldSize:y()) + j + 1 |
|
local o = floors[idx] |
|
if o then |
|
DoneObject(o) |
|
table.remove(floors, idx) |
|
end |
|
end |
|
end |
|
else |
|
if self.spawned_walls then |
|
local ew = self.spawned_walls.East |
|
local ww = self.spawned_walls.West |
|
if ew then |
|
self:CreateWalls("East", self.east_wall_mat, newSize:y() - delta:y()) |
|
end |
|
if ww then |
|
self:CreateWalls("West", self.west_wall_mat, newSize:y() - delta:y()) |
|
end |
|
end |
|
|
|
self:CreateFloor(self.floor_mat, 0, oldSize:y()) |
|
end |
|
end |
|
|
|
if delta:x() ~= 0 then |
|
if delta:x() < 0 then |
|
local count = abs(delta:x()) |
|
self:DestroyWalls("South", count, oldSize:SetZ(newSize:z())) |
|
self:DestroyWalls("North", count, oldSize:SetZ(newSize:z())) |
|
|
|
local floors = self.spawned_floors |
|
local nc = newSize:x() * newSize:y() |
|
local lc = #floors - nc |
|
for i = 1, lc do |
|
local idx = #floors |
|
local f = floors[idx] |
|
DoneObject(f) |
|
floors[idx] = nil |
|
end |
|
else |
|
if self.spawned_walls then |
|
local sw = self.spawned_walls.South |
|
local nw = self.spawned_walls.North |
|
if sw then |
|
self:CreateWalls("South", self.south_wall_mat, newSize:x() - delta:x()) |
|
end |
|
if nw then |
|
self:CreateWalls("North", self.north_wall_mat, newSize:x() - delta:x()) |
|
end |
|
end |
|
|
|
self:CreateFloor(self.floor_mat, oldSize:x()) |
|
end |
|
end |
|
|
|
if oldSize:z() > 0 and newSize:z() <= 0 then |
|
self:DeleteAllFloors() |
|
self:DestroyCorners() |
|
elseif oldSize:z() <= 0 and newSize:z() > 0 then |
|
self:CreateFloor(self.floor_mat) |
|
end |
|
|
|
self:RecreateNECornerBeam() |
|
self:RecreateSECornerBeam() |
|
self:RecreateNWCornerBeam() |
|
self:RecreateSWCornerBeam() |
|
|
|
if not self.being_placed then |
|
self:RecreateRoof() |
|
end |
|
|
|
ResumePassEdits("Room:Resize") |
|
self:CheckWallSizes() |
|
end |
|
|
|
function Room:DestroyCorners() |
|
for side, t in NSEW_pairs(self.spawned_corners or empty_table) do |
|
DoneObjects(t) |
|
self.spawned_corners[side] = {} |
|
end |
|
end |
|
|
|
function Room:MoveAllSpawnedObjs(dvx, dvy, dvz) |
|
local offsetX = dvx * voxelSizeX |
|
local offsetY = dvy * voxelSizeY |
|
local offsetZ = dvz * voxelSizeZ |
|
|
|
if offsetX == 0 and offsetY == 0 and offsetZ == 0 then |
|
return |
|
end |
|
|
|
SuspendPassEdits("Room:MoveAllSpawnedObjs") |
|
local function move(o) |
|
if not IsValid(o) then return end |
|
local x, y, z = o:GetPosXYZ() |
|
o:SetPos(x + offsetX, y + offsetY, z + offsetZ) |
|
|
|
end |
|
|
|
local function move_window(o) |
|
if not IsValid(o) then return end |
|
local x, y, z = o:GetPosXYZ() |
|
o:SetPos(x + offsetX, y + offsetY, z + offsetZ) |
|
o:AlignObj() |
|
end |
|
|
|
local function iterateNSWETable(t, m) |
|
m = m or move |
|
for _, st in NSEW_pairs(t or empty_table) do |
|
for i = 1, #st do |
|
local o = st[i] |
|
m(o) |
|
end |
|
end |
|
end |
|
|
|
for i = 1, #(self.spawned_floors or empty_table) do |
|
move(self.spawned_floors[i]) |
|
end |
|
|
|
iterateNSWETable(self.spawned_walls) |
|
iterateNSWETable(self.spawned_corners) |
|
for i = 1, #(self.roof_objs or empty_table) do |
|
move(self.roof_objs[i]) |
|
end |
|
|
|
iterateNSWETable(self.spawned_doors) |
|
iterateNSWETable(self.spawned_windows, move_window) |
|
iterateNSWETable(self.spawned_decals) |
|
|
|
local containers = {self.spawned_doors, self.spawned_windows, self.spawned_decals} |
|
self:RecalcAllRestrictionBoxes("East", containers) |
|
self:RecalcAllRestrictionBoxes("West", containers) |
|
self:RecalcAllRestrictionBoxes("South", containers) |
|
self:RecalcAllRestrictionBoxes("North", containers) |
|
|
|
ResumePassEdits("Room:MoveAllSpawnedObjs") |
|
end |
|
|
|
function Room:DestroyWalls(dir, count, size, startJ, endJ) |
|
local objs = self.spawned_walls and self.spawned_walls[dir] |
|
local wnd = self.spawned_windows and self.spawned_windows[dir] |
|
local doors = self.spawned_doors and self.spawned_doors[dir] |
|
local len = 0 |
|
local offsX = 0 |
|
local flatOffsX = 0 |
|
local offsY = 0 |
|
local flatOffsY = 0 |
|
local sx, sy = self.position:x(), self.position:y() |
|
local mat |
|
sx = sx + halfVoxelSizeX |
|
sy = sy + halfVoxelSizeY |
|
size = size or self.size |
|
|
|
if dir == "North" then |
|
len = size:x() |
|
mat = self:GetWallMatHelper(self.north_wall_mat) |
|
offsX = voxelSizeX |
|
flatOffsY = -voxelSizeY / 2 |
|
elseif dir == "East" then |
|
len = size:y() |
|
mat = self:GetWallMatHelper(self.east_wall_mat) |
|
flatOffsX = voxelSizeX / 2 |
|
offsY = voxelSizeY |
|
sx = sx + (size:x() - 1) * voxelSizeX |
|
elseif dir == "South" then |
|
len = size:x() |
|
mat = self:GetWallMatHelper(self.south_wall_mat) |
|
offsX = voxelSizeX |
|
flatOffsY = voxelSizeY / 2 |
|
sy = sy + (size:y() - 1) * voxelSizeY |
|
elseif dir == "West" then |
|
len = size:y() |
|
mat = self:GetWallMatHelper(self.west_wall_mat) |
|
flatOffsX = -voxelSizeX / 2 |
|
offsY = voxelSizeY |
|
end |
|
startJ = startJ or size:z() |
|
endJ = endJ or 0 |
|
count = count or len |
|
local gz = self:CalcZ() |
|
|
|
SuspendPassEdits("Room:DestroyWalls") |
|
|
|
self:SortWallObjs(doors or empty_table, dir) |
|
self:SortWallObjs(wnd or empty_table, dir) |
|
|
|
for i = len - 1, len - count, -1 do |
|
for j = startJ - 1, endJ, -1 do |
|
if endJ == 0 then |
|
|
|
local px = sx + i * offsX + flatOffsX |
|
local py = sy + i * offsY + flatOffsY |
|
local pz = gz + j * voxelSizeZ |
|
local p = point(px, py, pz) |
|
end |
|
|
|
if objs and #objs > 0 then |
|
local idx = i * size:z() + j + 1 |
|
local o = objs[idx] |
|
DoneObject(o) |
|
if #objs >= idx then |
|
table.remove(objs, idx) |
|
end |
|
end |
|
end |
|
end |
|
|
|
local containers = {self.spawned_decals} |
|
self:RecalcAllRestrictionBoxes(dir, containers) |
|
self:TouchWallsAndWindows(dir) |
|
ResumePassEdits("Room:DestroyWalls") |
|
Msg("RoomDestroyedWall", self, dir) |
|
end |
|
|
|
function Room:GetWallSlabPos(dir, idx) |
|
local x, y, z = self.position:xyz() |
|
local sizeX, sizeY, sizeZ = self.size:xyz() |
|
|
|
assert(voxelSizeX == voxelSizeY) |
|
local offs = ((idx - 1) / sizeZ) * voxelSizeX + halfVoxelSizeX |
|
z = z + ((idx - 1) % sizeZ) * voxelSizeZ |
|
|
|
if dir == "North" then |
|
x = x + offs |
|
elseif dir == "South" then |
|
x = x + offs |
|
y = y + sizeY * voxelSizeY |
|
elseif dir == "West" then |
|
y = y + offs |
|
else |
|
y = y + offs |
|
x = x + sizeX * voxelSizeX |
|
end |
|
|
|
return x, y, z |
|
end |
|
|
|
function Room:TestAllWallPositions() |
|
self:TestWallPositions("North") |
|
self:TestWallPositions("South") |
|
self:TestWallPositions("West") |
|
self:TestWallPositions("East") |
|
end |
|
|
|
function Room:TestWallPositions(dir) |
|
self.spawned_walls = self.spawned_walls or {North = {}, South = {}, East = {}, West = {}} |
|
dir = dir or "North" |
|
local objs = self.spawned_walls[dir] |
|
|
|
local gz = self:CalcZ() |
|
local angle = 0 |
|
local sx, sy = self.position:x(), self.position:y() |
|
|
|
local size = self.size |
|
local sizeX, sizeY, sizeZ = size:x(), size:y(), size:z() |
|
local offsx = 0 |
|
local offsy = 0 |
|
|
|
local endI = (dir == "North" or dir == "South") and sizeX or sizeY |
|
local endJ = sizeZ |
|
local startI = 0 |
|
local startJ = 0 |
|
|
|
if dir == "North" then |
|
angle = 270 * 60 |
|
offsx = voxelSizeX |
|
sx = sx + halfVoxelSizeX |
|
elseif dir == "East" then |
|
angle = 0 |
|
offsy = voxelSizeY |
|
sx = sx + sizeX * voxelSizeX |
|
sy = sy + halfVoxelSizeY |
|
elseif dir == "South" then |
|
angle = 90 * 60 |
|
offsx = voxelSizeX |
|
sy = sy + sizeY * voxelSizeY |
|
sx = sx + halfVoxelSizeX |
|
elseif dir == "West" then |
|
angle = 180 * 60 |
|
offsy = voxelSizeY |
|
sy = sy + halfVoxelSizeY |
|
end |
|
|
|
local insertElements = startJ ~= 0 and #objs < ((dir == "North" or dir == "South") and sizeX or sizeY) * sizeZ |
|
|
|
for i = startI, endI - 1 do |
|
for j = startJ, endJ - 1 do |
|
local px = sx + i * offsx |
|
local py = sy + i * offsy |
|
local z = gz + j * voxelSizeZ |
|
local idx = i * sizeZ + j + 1 |
|
|
|
local s = objs[idx] |
|
if not s or s:GetPos() ~= point(px, py, z) then |
|
print(dir, idx) |
|
end |
|
end |
|
end |
|
end |
|
|
|
function Room:CreateWalls(dir, mat, startI, startJ, endI, endJ, move) |
|
self.spawned_walls = self.spawned_walls or {North = {}, South = {}, East = {}, West = {}} |
|
mat = mat or "Planks" |
|
dir = dir or "North" |
|
local objs = self.spawned_walls[dir] |
|
|
|
if mat == defaultWallMat then |
|
mat = self.wall_mat |
|
end |
|
|
|
local oppositeDir = nil |
|
local gz = self:CalcZ() |
|
local angle = 0 |
|
local sx, sy = self.position:x(), self.position:y() |
|
local size = self.size |
|
local sizeX, sizeY, sizeZ = size:x(), size:y(), size:z() |
|
local offsx = 0 |
|
local offsy = 0 |
|
|
|
endI = endI or (dir == "North" or dir == "South") and sizeX or sizeY |
|
endJ = endJ or sizeZ |
|
startI = startI or 0 |
|
startJ = startJ or 0 |
|
|
|
if dir == "North" then |
|
angle = 270 * 60 |
|
offsx = voxelSizeX |
|
sx = sx + halfVoxelSizeX |
|
oppositeDir = "South" |
|
elseif dir == "East" then |
|
angle = 0 |
|
offsy = voxelSizeY |
|
sx = sx + sizeX * voxelSizeX |
|
sy = sy + halfVoxelSizeY |
|
oppositeDir = "West" |
|
elseif dir == "South" then |
|
angle = 90 * 60 |
|
offsx = voxelSizeX |
|
sy = sy + sizeY * voxelSizeY |
|
sx = sx + halfVoxelSizeX |
|
oppositeDir = "North" |
|
elseif dir == "West" then |
|
angle = 180 * 60 |
|
offsy = voxelSizeY |
|
sy = sy + halfVoxelSizeY |
|
oppositeDir = "East" |
|
end |
|
|
|
if self:GetGameFlags(const.gofPermanent) ~= 0 then |
|
local wallBBox = self:GetWallBox(dir) |
|
ComputeSlabVisibilityInBox(wallBBox) |
|
end |
|
|
|
SuspendPassEdits("Room:CreateWalls") |
|
local insertElements = startJ ~= 0 and #objs < ((dir == "North" or dir == "South") and sizeX or sizeY) * sizeZ |
|
local forceUpdate = self.last_wall_recreate_seed ~= self.seed |
|
local isLoadingMap = IsChangingMap() |
|
local affectedRooms = {} |
|
|
|
for i = startI, endI - 1 do |
|
for j = startJ, endJ - 1 do |
|
local px = sx + i * offsx |
|
local py = sy + i * offsy |
|
local z = gz + j * voxelSizeZ |
|
local idx = i * sizeZ + j + 1 |
|
local m = mat |
|
|
|
if insertElements then |
|
if idx > #objs then |
|
objs[idx] = false |
|
else |
|
table.insert(objs, idx, false) |
|
end |
|
end |
|
|
|
local wall = objs[idx] |
|
if not IsValid(wall) then |
|
wall = WallSlab:new{floor = self.floor, material = m, room = self, side = dir, |
|
variant = self.inner_wall_mat ~= noneWallMat and "OutdoorIndoor" or "Outdoor", indoor_material_1 = self.inner_wall_mat} |
|
wall:SetAngle(angle) |
|
wall:SetPos(px, py, z) |
|
wall:AlignObj() |
|
wall:UpdateEntity() |
|
wall:UpdateVariantEntities() |
|
|
|
wall:Setcolors(self.outer_colors) |
|
wall:Setinterior_attach_colors(self.inner_colors) |
|
wall.invulnerable = false |
|
wall.forceInvulnerableBecauseOfGameRules = false |
|
objs[idx] = wall |
|
else |
|
if move then |
|
|
|
wall:SetAngle(angle) |
|
local op = wall:GetPos() |
|
wall:SetPos(px, py, z) |
|
if op ~= wall:GetPos() and wall.wall_obj then |
|
local o = wall.wall_obj |
|
o:RestoreAffectedSlabs() |
|
end |
|
wall:AlignObj() |
|
end |
|
wall:UpdateSimMaterialId() |
|
end |
|
|
|
if not isLoadingMap then |
|
if forceUpdate or wall.material ~= m or wall.indoor_material_1 ~= self.inner_wall_mat then |
|
wall.material = m |
|
wall.indoor_material_1 = self.inner_wall_mat |
|
wall:UpdateEntity() |
|
wall:UpdateVariantEntities() |
|
end |
|
end |
|
end |
|
end |
|
|
|
affectedRooms[self] = nil |
|
for room, isMySide in pairs(affectedRooms) do |
|
if IsValid(room) then |
|
local d = isMySide and dir or oppositeDir |
|
room:TouchWallsAndWindows(d) |
|
room:TouchCorners(d) |
|
end |
|
end |
|
|
|
self:TouchWallsAndWindows(dir) |
|
self:TouchCorners(dir) |
|
ResumePassEdits("Room:CreateWalls") |
|
Msg("RoomCreatedWall", self, dir, mat) |
|
end |
|
|
|
local postComputeBatch = false |
|
function Room:SetInnerMaterialToRoofObjs() |
|
local objs = self.roof_objs |
|
if not objs or #objs <= 0 then return end |
|
|
|
local passedSWO = {} |
|
local col = self.inner_colors |
|
for i = 1, #objs do |
|
local o = objs[i] |
|
if IsValid(o) and IsKindOf(o, "WallSlab") then |
|
if o.indoor_material_1 ~= self.inner_wall_mat then |
|
o.indoor_material_1 = self.inner_wall_mat |
|
o:UpdateVariantEntities() |
|
end |
|
o:Setinterior_attach_colors(col) |
|
|
|
local swo = o.wall_obj |
|
if swo and not passedSWO[swo] then |
|
passedSWO[swo] = true |
|
end |
|
end |
|
end |
|
|
|
if next(passedSWO) then |
|
|
|
postComputeBatch = postComputeBatch or {} |
|
postComputeBatch[#postComputeBatch + 1] = passedSWO |
|
end |
|
|
|
ComputeSlabVisibilityInBox(self.roof_box) |
|
end |
|
|
|
function Room:SetInnerMaterialToSlabs(dir) |
|
local objs = self.spawned_walls and self.spawned_walls[dir] |
|
local gz = self:CalcZ() |
|
local sizeX, sizeY = self.size:x(), self.size:y() |
|
local endI = (dir == "North" or dir == "South") and sizeX or sizeY |
|
local passedSWO = {} |
|
local wallBBox = box() |
|
local col = self.inner_colors |
|
if objs then |
|
for i = 0, endI - 1 do |
|
for j = 0, self.size:z() - 1 do |
|
local idx = i * self.size:z() + j + 1 |
|
local o = objs[idx] |
|
if IsValid(o) then |
|
wallBBox = Extend(wallBBox, o:GetPos()) |
|
if o.indoor_material_1 ~= self.inner_wall_mat then |
|
o.indoor_material_1 = self.inner_wall_mat |
|
o:UpdateVariantEntities() |
|
end |
|
o:Setinterior_attach_colors(col) |
|
|
|
local swo = o.wall_obj |
|
if swo and not passedSWO[swo] then |
|
passedSWO[swo] = true |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
objs = self.spawned_corners[dir] |
|
for i = 1, #(objs or "") do |
|
if IsValid(objs[i]) then |
|
objs[i]:SetColorFromRoom() |
|
end |
|
end |
|
|
|
if next(passedSWO) then |
|
|
|
postComputeBatch = postComputeBatch or {} |
|
postComputeBatch[#postComputeBatch + 1] = passedSWO |
|
end |
|
|
|
if self:GetGameFlags(const.gofPermanent) ~= 0 then |
|
ComputeSlabVisibilityInBox(wallBBox) |
|
end |
|
self:CreateAllCorners() |
|
end |
|
|
|
function OnMsg.SlabVisibilityComputeDone() |
|
if not postComputeBatch then return end |
|
|
|
local allPassed = {} |
|
for i, batch in ipairs(postComputeBatch) do |
|
for swo, _ in pairs(batch) do |
|
if not allPassed[swo] then |
|
allPassed[swo] = true |
|
swo:UpdateManagedSlabs() |
|
swo:UpdateManagedObj() |
|
swo:RefreshColors() |
|
end |
|
end |
|
end |
|
postComputeBatch = false |
|
end |
|
|
|
function TouchWallsAndWindowsHelper(objs) |
|
if not objs then return end |
|
table.validate(objs) |
|
for i = #(objs or empty_table), 1, -1 do |
|
local o = objs[i] |
|
o:AlignObj() |
|
if o.room == false then |
|
DoneObject(o) |
|
else |
|
o:UpdateSimMaterialId() |
|
end |
|
end |
|
end |
|
|
|
function Room:TouchWallsAndWindows(side) |
|
TouchWallsAndWindowsHelper(self.spawned_doors and self.spawned_doors[side]) |
|
TouchWallsAndWindowsHelper(self.spawned_windows and self.spawned_windows[side]) |
|
end |
|
|
|
function Room:TouchCorners(side) |
|
if side == "North" then |
|
self:RecreateNECornerBeam() |
|
self:RecreateNWCornerBeam() |
|
elseif side == "South" then |
|
self:RecreateSECornerBeam() |
|
self:RecreateSWCornerBeam() |
|
elseif side == "East" then |
|
self:RecreateSECornerBeam() |
|
self:RecreateNECornerBeam() |
|
elseif side == "West" then |
|
self:RecreateSWCornerBeam() |
|
self:RecreateNWCornerBeam() |
|
end |
|
end |
|
|
|
function GedOpViewRoom(socket, obj) |
|
if IsValid(obj) then |
|
Room.CenterCameraOnMe(nil, obj) |
|
else |
|
print("No room selected.") |
|
end |
|
end |
|
|
|
function GedOpNewVolume(socket, obj) |
|
print("Use f3 -> map -> new room or ctrl+shift+n instead. This method is no longer supported.") |
|
end |
|
|
|
function Room:SelectWall(side) |
|
self:ClearBoldedMarker() |
|
self.selected_wall = side |
|
local m = self.text_markers[side] |
|
m:SetTextStyle("EditorTextBold") |
|
m:SetColor(RGB(0, 255, 0)) |
|
ObjModified(self) |
|
end |
|
|
|
function Room:ViewNorthWallFromOutside() |
|
self:SelectWall("North") |
|
self:ViewWall("North") |
|
ObjModified(self) |
|
end |
|
|
|
function Room:ViewSouthWallFromOutside() |
|
self:SelectWall("South") |
|
self:ViewWall("South") |
|
ObjModified(self) |
|
end |
|
|
|
function Room:ViewWestWallFromOutside() |
|
self:SelectWall("West") |
|
self:ViewWall("West") |
|
ObjModified(self) |
|
end |
|
|
|
function Room:ViewEastWallFromOutside() |
|
self:SelectWall("East") |
|
self:ViewWall("East") |
|
ObjModified(self) |
|
end |
|
|
|
function Room:ClearBoldedMarker() |
|
if self.selected_wall then |
|
local m = self.text_markers[self.selected_wall] |
|
m:SetTextStyle("EditorText") |
|
m:SetColor(RGB(255, 0, 0)) |
|
end |
|
end |
|
|
|
function Room:ClearSelectedWall() |
|
self:ClearBoldedMarker() |
|
self.selected_wall = false |
|
ObjModified(self) |
|
end |
|
|
|
function GetSelectedRoom() |
|
|
|
return SelectedRooms and SelectedRooms[1] or SelectedVolume |
|
end |
|
|
|
function SelectedRoomClearSelectedWall() |
|
local r = GetSelectedRoom() |
|
if IsValid(r) then |
|
r:ClearSelectedWall() |
|
print("Cleared selected wall") |
|
else |
|
print("No selected room found!") |
|
end |
|
end |
|
|
|
function SelectedRoomSelectWall(side) |
|
local r = GetSelectedRoom() |
|
if IsValid(r) then |
|
r:SelectWall(side) |
|
print(string.format("Selected wall %s of room %s", side, r.name)) |
|
else |
|
print("No selected room found!") |
|
end |
|
end |
|
|
|
function SelectedRoomResetWallMaterials() |
|
local r = GetSelectedRoom() |
|
if IsValid(r) then |
|
if r.selected_wall then |
|
local side = r.selected_wall |
|
local matMember = string.format("%s_wall_mat", string.lower(side)) |
|
local curMat = r[matMember] |
|
if curMat ~= defaultWallMat then |
|
r[matMember] = defaultWallMat |
|
local matPostSetter = string.format("OnSet%s", matMember) |
|
r[matPostSetter](r, defaultWallMat, curMat) |
|
end |
|
else |
|
r:ResetWallMaterials() |
|
print(string.format("Reset wall materials.")) |
|
end |
|
else |
|
print("No selected room found!") |
|
end |
|
end |
|
|
|
function Room:CycleWallMaterial(delta, side) |
|
local mats |
|
local matMember = "wall_mat" |
|
if side then |
|
mats = SlabMaterialComboItemsWithDefault()() |
|
matMember = string.format("%s_wall_mat", string.lower(side)) |
|
else |
|
mats = SlabMaterialComboItemsWithNone()() |
|
end |
|
local matPostSetter = string.format("OnSet%s", matMember) |
|
local curMat = self[matMember] |
|
local idx = table.find(mats, curMat) or 1 |
|
local newIdx = idx + delta |
|
if newIdx > #mats then |
|
newIdx = 1 |
|
elseif newIdx <= 0 then |
|
newIdx = #mats |
|
end |
|
local newMat = mats[newIdx] |
|
self[matMember] = newMat |
|
self[matPostSetter](self, newMat, curMat) |
|
print(string.format("Changed wall material of room %s side %s new material %s", self.name, side or "all", newMat)) |
|
end |
|
|
|
function Room:CycleEntity(delta) |
|
local sw = self.selected_wall |
|
if not sw then |
|
self:CycleWallMaterial(delta) |
|
return |
|
end |
|
self:CycleWallMaterial(delta, sw) |
|
end |
|
|
|
function Room:UIDeleteDoors() |
|
self:DeleteWallObjs(self.spawned_doors, self.selected_wall) |
|
end |
|
|
|
function Room:UIDeleteWindows() |
|
self:DeleteWallObjs(self.spawned_windows, self.selected_wall) |
|
end |
|
|
|
local decalIdPrefix = "decal_lst_" |
|
function Room:UIDeleteDecal(gedRoot, prop_id) |
|
local sh = string.gsub(prop_id, decalIdPrefix, "") |
|
local h = tonumber(sh) |
|
|
|
local t = self.spawned_decals[self.selected_wall] |
|
local idx = table.find(t, "handle", h) |
|
if idx then |
|
local d = t[idx] |
|
table.remove(t, idx) |
|
rawset(d, "safe_deletion", true) |
|
DoneObject(d) |
|
ObjModified(self) |
|
end |
|
end |
|
|
|
function Room:UISelectDecal(gedRoot, prop_id) |
|
local sh = string.gsub(prop_id, decalIdPrefix, "") |
|
local h = tonumber(sh) |
|
|
|
local t = self.spawned_decals[self.selected_wall] |
|
local idx = table.find(t, "handle", h) |
|
if idx then |
|
local d = t[idx] |
|
if d then |
|
editor.ClearSel() |
|
editor.AddToSel({d}) |
|
end |
|
end |
|
end |
|
|
|
function Room:GetWallPos(dir, zOffset) |
|
local wallSize, wallPos |
|
local wsx = self.size:x() * voxelSizeX |
|
local wsy = self.size:y() * voxelSizeY |
|
local pos = self:GetPos() |
|
if zOffset then |
|
pos = pos:SetZ(pos:z() + zOffset) |
|
end |
|
|
|
if dir == "North" then |
|
wallPos = point(pos:x(), pos:y() - wsy / 2, pos:z()) |
|
wallSize = wsx |
|
elseif dir == "South" then |
|
wallPos = point(pos:x(), pos:y() + wsy / 2, pos:z()) |
|
wallSize = wsx |
|
elseif dir == "West" then |
|
wallPos = point(pos:x() - wsx / 2, pos:y(), pos:z()) |
|
wallSize = wsy |
|
elseif dir == "East" then |
|
wallPos = point(pos:x() + wsx / 2, pos:y(), pos:z()) |
|
wallSize = wsy |
|
end |
|
|
|
return wallPos, wallSize, pos |
|
end |
|
|
|
function Room:ViewWall(dir, inside) |
|
dir = dir or "North" |
|
local wallPos, wallSize, pos = self:GetWallPos(dir, self.size:z() * voxelSizeZ / 2) |
|
|
|
|
|
local fovX = camera.GetFovX() |
|
local a = (180 * 60 - fovX) / 2 |
|
local wallWidth = wallSize |
|
local s = MulDivRound(wallWidth, sin(a), sin(fovX)) |
|
local x = wallWidth / 2 |
|
local dist = sqrt(s * s - x * x) |
|
|
|
local fovY = camera.GetFovY() |
|
a = (180 * 60 - fovY) / 2 |
|
local wallHeight = self.size:z() * voxelSizeZ |
|
s = MulDivRound(wallHeight, sin(a), sin(fovY)) |
|
x = wallHeight / 2 |
|
dist = Max(sqrt(s * s - x * x), dist) |
|
|
|
local offset |
|
if inside then |
|
offset = pos - wallPos |
|
else |
|
offset = wallPos - pos |
|
end |
|
|
|
dist = dist + 3 * guim |
|
offset = SetLen(offset, dist) |
|
offset = offset:SetZ(offset:z() + self.size:z() * voxelSizeZ * 3) |
|
|
|
local cPos, cLookAt, cType = GetCamera() |
|
local cam = _G[string.format("camera%s", cType)] |
|
cam.SetCamera(wallPos + offset, wallPos, 1000, "Cubic out") |
|
|
|
if rawget(terminal, "BringToTop") then |
|
return terminal.BringToTop() |
|
end |
|
end |
|
|
|
function Room.CenterCameraOnMe(_, self) |
|
local cPos, cLookAt, cType = GetCamera() |
|
local cOffs = cPos - cLookAt |
|
local mPos = self:GetPos() |
|
local cam = _G[string.format("camera%s", cType)] |
|
if cType == "Max" then |
|
local len = 20*guim * ((Max(self.size:x(), self.size:y()) / 10) + 1) |
|
cOffs = SetLen(cOffs, len) |
|
end |
|
cam.SetCamera(mPos + cOffs, mPos, 1000, "Cubic out") |
|
|
|
if rawget(terminal, "BringToTop") then |
|
return terminal.BringToTop() |
|
end |
|
end |
|
|
|
local defaultDecalProp = { category = "Materials", id = decalIdPrefix, name = "Decal ", editor = "text", default = "", read_only = true, |
|
buttons = { |
|
{name = "Delete", func = "UIDeleteDecal"}, |
|
{name = "Select", func = "UISelectDecal"}, |
|
},} |
|
|
|
local function AddDecalPropsFromContainerHelper(self, props, container, idx, defaultProp) |
|
for i = 1, #container do |
|
local np = table.copy(defaultProp) |
|
local obj = container[i] |
|
np.id = string.format("%s%s", np.id, obj.handle) |
|
np.name = string.format("%s%s", np.name, obj:GetEntity()) |
|
|
|
table.insert(props, idx + i, np) |
|
end |
|
end |
|
|
|
function Room:GetProperties() |
|
local decals = self.spawned_decals and self.spawned_decals[self.selected_wall] |
|
|
|
if #(decals or empty_table) > 0 then |
|
local p = table.copy(self.properties) |
|
|
|
if decals then |
|
local idx = table.find(p, "id", "place_decal") |
|
AddDecalPropsFromContainerHelper(self, p, decals, idx, defaultDecalProp) |
|
end |
|
|
|
return p |
|
else |
|
return self.properties |
|
end |
|
end |
|
|
|
function Room:GenerateName() |
|
return string.format("Room %d%s", self.handle, self:IsRoofOnly() and " - Roof only" or "") |
|
end |
|
|
|
function Room:Init() |
|
self.name = self.name or self:GenerateName() |
|
|
|
if self.auto_add_in_editor then |
|
self:AddInEditor() |
|
end |
|
end |
|
|
|
function ComputeVisibilityOfNearbyShelters() |
|
|
|
end |
|
|
|
function Room:ClearRoomAdjacencyData() |
|
self:ClearAdjacencyData() |
|
end |
|
|
|
function Room:RoomDestructor() |
|
Msg("RoomDone", self) |
|
local wasPermanent = self:GetGameFlags(const.gofPermanent) ~= 0 |
|
self:ClearGameFlags(const.gofPermanent) |
|
self:DeleteAllSpawnedObjs() |
|
self:ClearRoomAdjacencyData() |
|
|
|
if GedRoomEditor then |
|
table.remove_entry(GedRoomEditorObjList, self) |
|
ObjModified(GedRoomEditorObjList) |
|
if SelectedVolume == self then |
|
SetSelectedVolumeAndFireEvents(false) |
|
|
|
GedRoomEditor:UnbindObjs("SelectedObject") |
|
ObjModified(GedRoomEditorObjList) |
|
end |
|
end |
|
|
|
if wasPermanent then |
|
ComputeSlabVisibilityInBox(self.box) |
|
ComputeVisibilityOfNearbyShelters(self.box) |
|
end |
|
self["RoomDestructor"] = empty_func |
|
end |
|
|
|
function Room:ComputeRoomVisibility() |
|
if self:GetGameFlags(const.gofPermanent) ~= 0 then |
|
ComputeSlabVisibilityInBox(self.box) |
|
end |
|
end |
|
|
|
function Room:OnEditorDelete() |
|
self:VolumeDestructor() |
|
self:RoomDestructor() |
|
end |
|
|
|
function Room:Done() |
|
self:RoomDestructor() |
|
self.spawned_walls = nil |
|
self.spawned_corners = nil |
|
self.spawned_floors = nil |
|
self.spawned_doors = nil |
|
self.spawned_windows = nil |
|
self.spawned_decals = nil |
|
end |
|
|
|
function Room:AddInEditor() |
|
if GedRoomEditor then |
|
table.insert_unique(GedRoomEditorObjList, self) |
|
ObjModified(GedRoomEditorObjList) |
|
end |
|
end |
|
|
|
function WallObjToNestedListEntry(d, cls) |
|
cls = cls or "DoorNestedListEntry" |
|
local entry = PlaceObject(cls) |
|
entry.linked_obj = d |
|
entry.width = d.width |
|
entry.material = d.material |
|
entry.subvariant = d.subvariant or 1 |
|
return entry |
|
end |
|
|
|
function Room:TestCorners() |
|
for k, v in NSEW_pairs(self.spawned_corners or empty_table) do |
|
for i = 1, #v do |
|
if not IsValid(v[i]) then |
|
print(k, v[i]) |
|
end |
|
end |
|
end |
|
end |
|
|
|
function Room:AssignPropValuesToMySlabs() |
|
|
|
local reposition = RepositionWallSlabsOnLoad |
|
for i = 1, #room_NSWE_lists do |
|
for side, objs in NSEW_pairs(self[room_NSWE_lists[i]] or empty_table) do |
|
local isDecals = "spawned_decals" == room_NSWE_lists[i] |
|
local isCorners = "spawned_corners" == room_NSWE_lists[i] |
|
local isWalls = "spawned_walls" == room_NSWE_lists[i] |
|
local isFloors = "spawned_floors" == room_NSWE_lists[i] |
|
local isWindows = "spawned_windows" == room_NSWE_lists[i] |
|
local isDoors = "spawned_doors" == room_NSWE_lists[i] |
|
for j = 1, #objs do |
|
local obj = objs[j] |
|
if obj then |
|
obj.room = self |
|
obj.side = side |
|
obj.floor = self.floor |
|
|
|
if not isDecals then |
|
obj.invulnerable = obj.forceInvulnerableBecauseOfGameRules |
|
end |
|
|
|
if isCorners and j == #objs then |
|
obj.isPlug = true |
|
end |
|
if isWalls or isCorners then |
|
obj:DelayedUpdateEntity() |
|
end |
|
if isWalls then |
|
obj:DelayedUpdateVariantEntities() |
|
end |
|
|
|
if reposition and isWalls then |
|
obj:SetPos(self:GetWallSlabPos(side, j)) |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
local floorsAreCO = g_Classes.CombatObject and IsKindOf(FloorSlab, "CombatObject") |
|
for i = 1, #room_regular_lists do |
|
local isFloors = room_regular_lists[i] == "spawned_floors" |
|
local t = self[room_regular_lists[i]] or empty_table |
|
local side = room_regular_list_sides[i] |
|
for j = #t, 1, -1 do |
|
local o = t[j] |
|
if o then |
|
o.room = self |
|
o.side = side |
|
o.floor = self.floor |
|
o.invulnerable = o.forceInvulnerableBecauseOfGameRules |
|
|
|
if isFloors then |
|
o:DelayedUpdateEntity() |
|
end |
|
end |
|
end |
|
end |
|
end |
|
|
|
function Room:CreateAllCorners() |
|
self:RecreateNECornerBeam() |
|
self:RecreateNWCornerBeam() |
|
self:RecreateSWCornerBeam() |
|
self:RecreateSECornerBeam() |
|
end |
|
|
|
|
|
function RecreateAllCornersAndColors() |
|
MapForEach("map", "RoomCorner", DoneObject) |
|
MapForEach("map", "Room", function(room) |
|
room:RecreateWalls() |
|
room:RecreateFloor() |
|
room:RecreateRoof() |
|
room:OnSetouter_colors(room.outer_colors) |
|
room:OnSetinner_colors(room.inner_colors) |
|
end) |
|
end |
|
|
|
function RefreshAllRoomColors() |
|
MapForEach("map", "Room", function(room) |
|
room:OnSetouter_colors(room.outer_colors) |
|
room:OnSetinner_colors(room.inner_colors) |
|
end) |
|
end |
|
|
|
function Room:SaveFixups() |
|
local hasCeiling = type(self.roof_objs) == "table" and IsKindOf(self.roof_objs[#self.roof_objs], "CeilingSlab") or false |
|
if not self.build_ceiling and hasCeiling then |
|
|
|
|
|
while IsKindOf(self.roof_objs[#self.roof_objs], "CeilingSlab") do |
|
local o = self.roof_objs[#self.roof_objs] |
|
self.roof_objs[#self.roof_objs] = nil |
|
DoneObject(o) |
|
end |
|
end |
|
end |
|
|
|
function Room:Getlocked_slabs_count() |
|
local total, locked = 0, 0 |
|
|
|
local function iterateAndCount(t) |
|
for i = 1, #(t or "") do |
|
local slab = t[i] |
|
if IsValid(slab) and slab.isVisible then |
|
total = total + 1 |
|
locked = locked + (slab.subvariant ~= -1 and 1 or 0) |
|
end |
|
end |
|
end |
|
|
|
local function iterateAndCountNSEW(objs) |
|
for side, t in NSEW_pairs(objs or empty_table) do |
|
iterateAndCount(t) |
|
end |
|
end |
|
|
|
iterateAndCountNSEW(self.spawned_walls) |
|
local ws = string.format("%d/%d walls", locked, total) |
|
locked, total = 0, 0 |
|
iterateAndCountNSEW(self.spawned_corners) |
|
local cs = string.format("%d/%d corners", locked, total) |
|
locked, total = 0, 0 |
|
iterateAndCount(self.spawned_floors) |
|
local fs = string.format("%d/%d floors", locked, total) |
|
locked, total = 0, 0 |
|
iterateAndCount(self.roof_objs) |
|
local rs = string.format("%d/%d roof objs", locked, total) |
|
|
|
return string.format("%s; %s; %s; %s;", ws, cs, fs, rs) |
|
end |
|
|
|
function Room:LockAllSlabsToCurrentSubvariants() |
|
|
|
|
|
|
|
local function iterateAndSet(t) |
|
for i = 1, #(t or "") do |
|
local slab = t[i] |
|
if IsValid(slab) and slab.isVisible then |
|
slab:LockSubvariantToCurrentEntSubvariant() |
|
end |
|
end |
|
end |
|
|
|
local function iterateAndSetNSEW(objs) |
|
for side, t in NSEW_pairs(objs or empty_table) do |
|
iterateAndSet(t) |
|
end |
|
end |
|
|
|
iterateAndSetNSEW(self.spawned_walls) |
|
iterateAndSetNSEW(self.spawned_corners) |
|
iterateAndSet(self.spawned_floors) |
|
iterateAndSet(self.roof_objs) |
|
ObjModified(self) |
|
end |
|
|
|
local function extractCpyId(str) |
|
local r = string.gmatch(str, "copy%d+")() |
|
return r and tonumber(string.gmatch(r, "%d+")()) or 0 |
|
end |
|
|
|
function Room:GenerateNameWithCpyTag() |
|
local n = self.name |
|
local pid = extractCpyId(n) |
|
local topId = pid |
|
EnumVolumes(function(v, n, find, sub) |
|
local hn = v.name |
|
local mn = n |
|
if #hn < #mn then |
|
mn = string.sub(mn, 1, #hn) |
|
elseif #hn > #mn then |
|
hn = string.sub(hn, 1, #mn) |
|
end |
|
|
|
if hn == mn then |
|
local hpid = extractCpyId(v.name) |
|
if hpid > topId then |
|
topId = hpid |
|
end |
|
end |
|
end, string.gsub(n, " copy%d+", ""), string.find, string.sub) |
|
|
|
local tag = string.format("copy%d", tonumber(topId) + 1) |
|
if pid == 0 then |
|
return string.format("%s %s", self.name, tag) |
|
else |
|
return string.gsub(self.name, "copy%d+", tag) |
|
end |
|
end |
|
|
|
function Room:PostLoad(reason) |
|
if reason == "paste" then |
|
self:Setname(self:GenerateNameWithCpyTag()) |
|
end |
|
|
|
SuspendPassEdits("Room:PostLoad") |
|
|
|
self:SaveFixups() |
|
self:InternalAlignObj() |
|
self:SetWarped(self:GetWarped(), true) |
|
self:AssignPropValuesToMySlabs() |
|
|
|
self:RecalcRoof() |
|
self:ComputeRoomVisibility() |
|
|
|
ResumePassEdits("Room:PostLoad") |
|
end |
|
|
|
function Room:OnWallObjDeletedOutsideOfGedRoomEditor(obj) |
|
local dir = slabAngleToDir[obj:GetAngle()] |
|
local t = self[obj:IsDoor() and string.format("placed_doors_nl_%s", string.lower(dir)) or string.format("placed_windows_nl_%s", string.lower(dir))] |
|
local container = obj:IsDoor() and self.spawned_doors or self.spawned_windows |
|
if container then |
|
for i = 1, #(t or empty_table) do |
|
if t[i].linked_obj == obj then |
|
DoneObject(t[i]) |
|
table.remove(t, i) |
|
table.remove_entry(container[dir], obj) |
|
return |
|
end |
|
end |
|
elseif Platform.developer then |
|
local cs = obj:IsDoor() and "spawned_doors" or "spawned_windows" |
|
local dirFound |
|
local r = MapGetFirst("map", "Room", function(o, cs, obj, et) |
|
for side, t in sorted_pairs(o[cs] or et) do |
|
if table.find(t or et, obj) then |
|
dirFound = side |
|
return true |
|
end |
|
end |
|
end, cs, obj, empty_table) |
|
|
|
if r then |
|
print(string.format("Wall obj was found in room %s, side %s container", r.name, dirFound)) |
|
else |
|
print("Wall obj was not found in any room container") |
|
end |
|
|
|
assert(false, string.format("Room %s had no contaier initialized for wall obj with entity %s, deduced side %s, member side %s", self.name, obj.entity, dir, obj.side)) |
|
end |
|
end |
|
|
|
local dirs = { "North", "East", "South", "West" } |
|
function rotate_direction(direction, angle) |
|
local idx = table.find(dirs, direction) |
|
if not idx then return direction end |
|
idx = idx + angle / (90 * 60) |
|
if idx > 4 then idx = idx - 4 end |
|
return dirs[idx] |
|
end |
|
|
|
function Room:EditorRotate(center, axis, angle, last_angle) |
|
angle = angle - last_angle |
|
if axis:z() < 0 then angle = -angle end |
|
angle = (angle + 360 * 60 + 45 * 60 ) / (90 * 60) * (90 * 60) |
|
while angle >= 360 * 60 do angle = angle - 360 * 60 end |
|
if axis:z() == 0 or angle == 0 then return end |
|
|
|
|
|
local a = center + Rotate(self.box:min() - center, angle) |
|
local b = center + Rotate(self.box:max() - center, angle) |
|
self.box = boxdiag(a, b) |
|
self.position = self.box:min() |
|
local x, y, z = self.box:size():xyz() |
|
self.size = point(x / voxelSizeX, y / voxelSizeY, z / voxelSizeZ) |
|
|
|
|
|
if self:GetRoofType() == "Gable" then |
|
if angle == 90 * 60 or angle == 270 * 60 then |
|
self.roof_direction = self.roof_direction == GableRoofDirections[1] and GableRoofDirections[2] or GableRoofDirections[1] |
|
end |
|
elseif self:GetRoofType() == "Shed" then |
|
self.roof_direction = rotate_direction(self.roof_direction, angle) |
|
end |
|
|
|
|
|
self:ForEachSpawnedObj(function(obj, center, angle) |
|
local new_angle = 0 |
|
if not IsKindOf(obj, "FloorAlignedObj") then |
|
new_angle = obj:GetAngle() + angle |
|
end |
|
obj:SetPosAngle(center + Rotate(obj:GetPos() - center, angle), new_angle) |
|
obj.side = rotate_direction(obj.side, angle) |
|
if obj:IsKindOf("SlabWallObject") then obj:UpdateManagedObj() end |
|
end, center, angle) |
|
|
|
|
|
local d = table.copy(dirs) |
|
while angle >= 90 * 60 do |
|
d[1], d[2], d[3], d[4] = d[2], d[3], d[4], d[1] |
|
angle = angle - 90 * 60 |
|
end |
|
for i = 1, #room_NSWE_lists do |
|
local lists = self[room_NSWE_lists[i]] |
|
if lists then |
|
lists[d[1]], lists[d[2]], lists[d[3]], lists[d[4]] = lists.North, lists.East, lists.South, lists.West |
|
end |
|
end |
|
self:InternalAlignObj() |
|
self:RecreateRoof() |
|
end |
|
|
|
function OnMsg.GedClosing(ged_id) |
|
if GedRoomEditor and GedRoomEditor.ged_id == ged_id then |
|
GedRoomEditor = false |
|
GedRoomEditorObjList = false |
|
end |
|
end |
|
|
|
function OnMsg.GedOnEditorSelect(obj, selected, editor) |
|
if editor == GedRoomEditor then |
|
SetSelectedVolumeAndFireEvents(selected and obj or false) |
|
end |
|
end |
|
|
|
function OpenGedRoomEditor() |
|
CreateRealTimeThread(function() |
|
if not IsValid(GedRoomEditor) then |
|
GedRoomEditorObjList = MapGet("map", "Room") or {} |
|
table.sortby_field(GedRoomEditorObjList, "name") |
|
table.sortby_field(GedRoomEditorObjList, "structure") |
|
GedRoomEditor = OpenGedApp("GedRoomEditor", GedRoomEditorObjList) or false |
|
end |
|
end) |
|
end |
|
|
|
function OnMsg.ChangeMap() |
|
if GedRoomEditor then |
|
GedRoomEditor:Send("rfnClose") |
|
GedRoomEditor = false |
|
end |
|
end |
|
|
|
|
|
|
|
DefineClass.SlabPreset = { |
|
__parents = { "Preset", }, |
|
properties = { |
|
{ id = "Group", no_edit = false, }, |
|
}, |
|
HasSortKey = false, |
|
PresetClass = "SlabPreset", |
|
NoInstances = true, |
|
EditorMenubarName = "Slab Presets", |
|
EditorMenubar = "Editors.Art", |
|
} |
|
|
|
DefineClass.SlabMaterialSubvariant = { |
|
__parents = {"PropertyObject"}, |
|
properties = { |
|
{ id = "suffix", name = "Suffix", editor = "text", default = "01" }, |
|
{ id = "chance", name = "Chance", editor = "number", default = 100 }, |
|
}, |
|
} |
|
|
|
|
|
|
|
|
|
function TouchAllRoomCorners() |
|
MapForEach("map", "Room", function(o) |
|
o:TouchCorners("North") |
|
o:TouchCorners("South") |
|
o:TouchCorners("West") |
|
o:TouchCorners("East") |
|
end) |
|
end |
|
|
|
DefineClass.HideOnFloorChange = { |
|
__parents = { "Object" }, |
|
properties = { |
|
{ id = "floor", name = "Floor", editor = "number", min = -10, max = 100, default = 1, dont_save = function (obj) return obj.room end }, |
|
}, |
|
room = false, |
|
invisible_reasons = false, |
|
} |
|
|
|
function HideOnFloorChange:Getfloor() |
|
local room = self.room |
|
return room and room.floor or self.floor |
|
end |
|
|
|
HideSlab = false |
|
|
|
function HideFloorsAbove(floor, fnHide) |
|
SuspendPassEdits("HideFloorsAbove") |
|
HideFloorsAboveC(floor, fnHide or HideSlab or nil) |
|
Msg("FloorsHiddenAbove", floor, fnHide) |
|
ResumePassEdits("HideFloorsAbove") |
|
end |
|
|
|
function CountRoomSlabs() |
|
local t = 0 |
|
MapForEach("map", "Room", function(o) |
|
t = t + (o.size:x() + o.size:y()) * 2 * o.size:z() |
|
end) |
|
|
|
return t |
|
end |
|
|
|
function CountMirroredSlabs() |
|
local t, tm = 0, 0 |
|
|
|
MapForEach("map", "WallSlab", function(o) |
|
if o:CanMirror() and o:GetEnumFlags(const.efVisible) ~= 0 then |
|
if o:GetGameFlags(const.gofMirrored) ~= 0 then |
|
tm = tm + 1 |
|
else |
|
t = t + 1 |
|
end |
|
end |
|
end) |
|
|
|
return t, tm |
|
end |
|
|
|
function BuildBuildingsData() |
|
end |
|
|
|
function DbgWindowDoorOwnership() |
|
MapForEach("map", "SlabWallObject", function(o) |
|
if o.room then |
|
DbgAddVector(o:GetPos(), o.room:GetPos() - o:GetPos()) |
|
else |
|
DbgAddVector(o:GetPos()) |
|
end |
|
end) |
|
end |
|
|