Download CommonLua/Libs/Volumes/Volume.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 120 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Libs/Volumes/Volume.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Libs/Volumes/Volume.lua
-
curl -L -o Volume.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Libs/Volumes/Volume.lua
120 kB
| NSEW_pairs = sorted_pairs -- todo: make an iterator that explicitly visits them in a predefined order | |
| -- EnumVolumes([class, ] { box, | obj, | point, | x, y | x, y, z, }) - returns an array of the volumes interesecting the box/point | |
| -- EnumVolumes(..., "smallest") - returns the smallest volume (by surface) interesecting the box/point | |
| -- EnumVolumes(..., filter, ...) - returns an array with all volumes interesecting the box/point for which filter(volume, ...) returned true | |
| local gofPermanent = const.gofPermanent | |
| if FirstLoad then | |
| GedRoomEditor = false | |
| GedRoomEditorObjList = false | |
| g_RoomCornerTaskList = {} | |
| SelectedVolume = false | |
| VolumeCollisonEnabled = false | |
| HideFloorsAboveThisOne = false | |
| --moves wall slabs on load to their expected pos | |
| --it seems that lvl designers commonly misplace wall slabs which causes weird glitches, this should fix that | |
| 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 | |
| -- the map is considered in bottom-right quadrant, which means that (0, 0) is north, west | |
| 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 }, | |
| --bottom left corner of room in world coords | |
| { 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, --{ [floor] = {room, room, room} } | |
| being_placed = false, | |
| enable_collision = true, --with other rooms | |
| EditorView = Untranslated("<opt(u(structure),'',' - ')><u(name)>"), | |
| light_vol_obj = false, | |
| entity = "InvisibleObject", | |
| editor_force_excluded = true, -- exclude from Map editor palette (causes crash) | |
| } | |
| 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() | |
| --todo: if platform.somethingorother, | |
| 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() | |
| --dont mess with visibility flags | |
| self:ChangeEntity("RoomHelper") | |
| end | |
| function Volume:EditorExit() | |
| --dont mess with visibility flags | |
| 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()) --this is here so vfx can properly catch ground below rooms | |
| 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 | |
| --save compat | |
| 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) --essentialy calcz but makes assumptions about floor size | |
| return ground_level + (f - 1) * (roomHeight * voxelSizeZ) | |
| end | |
| function Volume:Move(pos) | |
| --bottom left corner of the voxel at pos will be the new positon | |
| 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 --nothing to align | |
| 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() | |
| -- must be called inside an undo op, otherwise delayed updating may cause changes not captured by undo | |
| assert(XEditorUndo:AssertOpCapture()) | |
| local adjacent_rooms = self.adjacent_rooms | |
| local new_adjacent_rooms = {} | |
| local mb = self.box | |
| local events = {} | |
| --DbgAddBox(mb) | |
| 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 | |
| --TODO: use +roof box for sides, -roof box for ceiling/floor | |
| local hb = o.box | |
| --DbgAddBox(hb, RGB(0, 255, 0)) | |
| local ib = IntersectRects(hb, mb) | |
| --DbgAddBox(ib, RGB(255, 0, 0)) | |
| 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 = {} | |
| --restore previously affected walls | |
| 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 | |
| --corner adj, rebuild corner | |
| local p = ib:min() | |
| if p:x() == mb:minx() then --west | |
| if p:y() == mb:miny() then --north | |
| 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 --east | |
| 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 | |
| --east/west adjacency | |
| if mb:maxx() == ib:maxx() then | |
| --my east, his west | |
| table.insert(events, {self, "East"}) | |
| table.insert(events, {o, "West"}) | |
| table.insert(hisNewData[2], "West") | |
| table.insert(myNewData[2], "East") | |
| else | |
| --my west, his east | |
| 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 | |
| --nort/south adjacency | |
| if mb:maxy() == ib:maxy() then | |
| --my north, his south | |
| table.insert(events, {self, "South"}) | |
| table.insert(events, {o, "North"}) | |
| table.insert(hisNewData[2], "North") | |
| table.insert(myNewData[2], "South") | |
| else | |
| --my south, his north | |
| table.insert(events, {self, "North"}) | |
| table.insert(events, {o, "South"}) | |
| table.insert(hisNewData[2], "South") | |
| table.insert(myNewData[2], "North") | |
| end | |
| else | |
| --rooms intersect | |
| if (ib:maxx() == mb:maxx() or ib:minx() == mb:maxx()) | |
| and mb:maxx() == hb:maxx() then | |
| --east | |
| 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 | |
| --west | |
| 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 | |
| --south | |
| 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 | |
| --north | |
| 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 | |
| --floor | |
| 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 | |
| --roof | |
| 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 | |
| --adjacency removed | |
| 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 | |
| --print(o.name, s) | |
| o:OnAdjacencyChanged(s) | |
| end | |
| end | |
| end | |
| table.clear(AdjacencyEvents) | |
| end | |
| -- make sure all changes to rooms are completed before we finish capturing undo data | |
| 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) | |
| --room selection | |
| if RoomSelectionMode then | |
| --if 1 slab is selected? | |
| 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 --don't do further analysis this pass | |
| end | |
| end | |
| --selected rooms | |
| 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 | |
| --deselect | |
| 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 | |
| --wall | |
| 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 | |
| --wall | |
| xPoints[inY]:AppendVertex(ox, yy, oz, self.wireframeColor) | |
| end | |
| if inY == 0 then | |
| --wall | |
| 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 | |
| --wall | |
| 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, ...) | |
| --this presumes no wall crossing | |
| 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() | |
| --so shift + d in f3 doesn't kill these | |
| 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) -- in screen coordinates, terminal.GetMousePos() | |
| -- enumerate all visible volumes on the map and select the one under the mouse point 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 | |
| --DbgAddCircle(start, 100) | |
| --DbgAddVector(start, pos - start) | |
| --DbgAddVector(start, dir*1000, RGB(0, 255, 0)) | |
| 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 | |
| --return distance to intersection between the camera ray and volume box | |
| local p1, p2 = ClipSegmentWithBox3D(start, dir2, volume.box) | |
| if p1 then | |
| --DbgAddCircle(p1, 100) | |
| --DbgAddCircle(p2, 100, RGB(0, 0, 255)) | |
| 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 --only deselect wall if another vol is selected | |
| 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}, | |
| --materials | |
| { 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,}, --todo: kill | |
| { 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}, | |
| --bacon specific? | |
| { 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, -- for Room editor | |
| spawned_walls = false, -- {["North"] = {}, etc.} | |
| spawned_corners = false, | |
| spawned_floors = false, | |
| spawned_doors = false, | |
| spawned_windows = false, | |
| spawned_decals = false, | |
| selected_wall = false, -- false, "North", "South", etc. | |
| next_visibility_state = false, | |
| visibility_state = false, -- when defined purely as a prop, something strips it. | |
| 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) -- this moves box | |
| 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) -- x, y, z - move delta in voxels | |
| 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 | |
| -- make sure all changes to roofs are completed before we finish capturing undo data | |
| 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 -- see RoomRoof:GetPivots | |
| } | |
| 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) --both walls and corners + roof walls n corners in one | |
| 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) | |
| --undo deletion from ged | |
| self.adjacent_rooms = nil | |
| self:AlignObj() | |
| self:CreateAllSlabs() | |
| end | |
| function Room:OnEditorSetProperty(prop_id, old_value, ged) | |
| if not IsValid(self) then return end --undo on deleted obj | |
| 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 --wall piece might be deleted by lvl designer | |
| 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 | |
| --todo: wall added/removed msgs | |
| 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) --old_v == old self.size | |
| 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 | |
| --left to right sort on selected wall | |
| 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 | |
| --for doors/windows | |
| 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 --functor may del | |
| func(t[i], ...) | |
| end | |
| end | |
| end | |
| function Room:ForEachSpawnedWallObj(func, ...) --doors and windows | |
| 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 | |
| --check for collision, pick pos | |
| 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 --door | |
| self.spawned_doors = self.spawned_doors or {} | |
| self.spawned_doors[dir] = self.spawned_doors[dir] or {} | |
| container = self.spawned_doors | |
| else --window | |
| 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 | |
| --decals dont have ref to the room they belong to, so the check is all weird | |
| 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) | |
| --DbgAddBox(obj.restriction_box, RGB(255, 0, 0)) | |
| 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 --can be killed from editor | |
| 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 | |
| --windows and doors are so clever that they remove themselves from these lists when deleted, which causes DoneObjects to sometimes fail | |
| 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) --nw | |
| end | |
| function Room:RecreateSWCornerBeam() | |
| local mat = self.west_wall_mat | |
| if mat == noneWallMat then | |
| mat = self.south_wall_mat | |
| end | |
| self:CreateCornerBeam("West", mat) --sw | |
| end | |
| function Room:RecreateNECornerBeam() | |
| local mat = self.east_wall_mat | |
| if mat == noneWallMat then | |
| mat = self.north_wall_mat | |
| end | |
| self:CreateCornerBeam("East", mat) --ne | |
| end | |
| function Room:RecreateSECornerBeam() | |
| local mat = self.south_wall_mat | |
| if mat == noneWallMat then | |
| mat = self.east_wall_mat | |
| end | |
| self:CreateCornerBeam("South", mat) --se | |
| 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 --zulu specific | |
| 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) --corner is next clockwise corner from dir | |
| 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() --corners rely on ComputeSlabVisibilityInBox to update their ents | |
| 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] | |
| --save fixup, idk how, sometimes decal lists have false entries | |
| 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 | |
| --south wall moves | |
| 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 | |
| --east wall moves | |
| 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 --needs to reorder slabs so let it go through the entire wall | |
| 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) | |
| --align should not be required, omitted for performance | |
| 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() --specifically so small windows realign managed slabs, t.t | |
| 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 | |
| -- move doors & windows after roof_objs, otherwise doors & windows won't find their "main_wall" | |
| 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 | |
| --clear wind/door | |
| 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 --dir == "East" | |
| 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 = oldSize or self.size | |
| 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 --Happens when resizing in (x or y) ~= 0 and z ~= 0 in the same time. Fill it in, second pass will fill in the missing ones without breaking integrity, probably.. | |
| 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 | |
| --sometimes if we change both z and x or y sizing at the same time we end up with the same number of slabs per wall but they need to be re-arranged. | |
| 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 | |
| --in some cases slabs need to recalibrate in computeslabvisibility, we need to pass after that | |
| 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 --can be gone | |
| objs[i]:SetColorFromRoom() | |
| end | |
| end | |
| if next(passedSWO) then | |
| --in some cases slabs need to recalibrate in computeslabvisibility, we need to pass after that | |
| 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() | |
| --find a selected room.. | |
| 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) | |
| --fit wall to screen | |
| 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 --some x margin so wall edge is not stuck to the screen edge | |
| offset = SetLen(offset, dist) | |
| offset = offset:SetZ(offset:z() + self.size:z() * voxelSizeZ * 3) --move eye up a little bit | |
| 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() | |
| --stub | |
| end | |
| function Room:ClearRoomAdjacencyData() | |
| self:ClearAdjacencyData() | |
| end | |
| function Room:RoomDestructor() | |
| Msg("RoomDone", self) | |
| local wasPermanent = self:GetGameFlags(const.gofPermanent) ~= 0 | |
| self:ClearGameFlags(const.gofPermanent) --this is to dodge this assert -> assert(false, "Passability rebuild provoked from destructor! Obj class: " .. (obj.class or "N/A")) | |
| self:DeleteAllSpawnedObjs() | |
| self:ClearRoomAdjacencyData() | |
| if GedRoomEditor then | |
| table.remove_entry(GedRoomEditorObjList, self) | |
| ObjModified(GedRoomEditorObjList) | |
| if SelectedVolume == self then | |
| SetSelectedVolumeAndFireEvents(false) | |
| --todo: this does not work to clear the props pane | |
| GedRoomEditor:UnbindObjs("SelectedObject") | |
| ObjModified(GedRoomEditorObjList) | |
| end | |
| end | |
| if wasPermanent then | |
| ComputeSlabVisibilityInBox(self.box) --since we are no longer gofPermanent, call directly | |
| 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() | |
| -- assign prop values that are convenient to have per slab but are not saved per slab | |
| 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 -- could be a decal from spawned_decals | |
| obj.invulnerable = obj.forceInvulnerableBecauseOfGameRules | |
| end | |
| if isCorners and j == #objs then | |
| obj.isPlug = true -- last one is always a plug | |
| end | |
| if isWalls or isCorners then | |
| obj:DelayedUpdateEntity() -- call this after room is set for the correct seed | |
| end | |
| if isWalls then | |
| obj:DelayedUpdateVariantEntities() -- default colors are not available 'till .room gets assigned, this will reapply them to attaches | |
| 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() -- these now have random ents as well, so rerandomize after seed is setup | |
| end | |
| end | |
| end | |
| end | |
| end | |
| function Room:CreateAllCorners() | |
| self:RecreateNECornerBeam() | |
| self:RecreateNWCornerBeam() | |
| self:RecreateSWCornerBeam() | |
| self:RecreateSECornerBeam() | |
| end | |
| -- used manually when resaving maps from old schema to new shcema | |
| 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 | |
| -- tweaked this default value, so now there are blds with disabled ceiling who have ceilings | |
| -- because we avoid touching roofs during load they remain | |
| 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() | |
| -- goes through all slabs and switches -1 subvariant val to their current subvariant. | |
| -- this will lock those variants in case of random generator changes | |
| 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() -- sets slab properties that are not saved - .room, .side, etc. | |
| 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 | |
| -- rotate room properties | |
| 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) | |
| -- rotate roof properties | |
| 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 | |
| -- rotate slabs | |
| 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) | |
| -- assign slabs to the proper lists after rotation | |
| 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 -- used only if defined | |
| 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 | |