Download CommonLua/Libs/Volumes/Slab.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 119 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Libs/Volumes/Slab.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Libs/Volumes/Slab.lua
-
curl -L -o Slab.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Libs/Volumes/Slab.lua
119 kB
| -- the map is considered in bottom-right quadrant, which means that (0, 0) is north, west | |
| local default_color = RGB(100, 100, 100) | |
| 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 no_mat = const.SlabNoMaterial | |
| local noneWallMat = no_mat | |
| local gofPermanent = const.gofPermanent | |
| local efVisible = const.efVisible | |
| const.SuppressMultipleRoofEdges = true | |
| DefineClass.Restrictor = { | |
| __parents = { "Object" }, | |
| restriction_box = false, | |
| } | |
| local iz = const.InvalidZ | |
| function Restrictor:Restrict() | |
| local b = self.restriction_box | |
| if not b then return end | |
| local x, y, z = self:GetPosXYZ() | |
| x, y, z = self:RestrictXYZ(x, y, z) | |
| self:SetPos(x, y, z) | |
| end | |
| function Restrictor:RestrictXYZ(x, y, z) | |
| local b = self.restriction_box | |
| if not b then return x, y, z end | |
| local minx, miny, minz, maxx, maxy, maxz = b:xyzxyz() | |
| x = Clamp(x, minx, maxx) | |
| y = Clamp(y, miny, maxy) | |
| if z ~= iz and minz ~= iz and maxz ~= iz then | |
| z = Clamp(z, minz, maxz) | |
| end | |
| return x, y, z | |
| end | |
| DefineClass.WallAlignedObj = { | |
| __parents = { "AlignedObj" }, | |
| } | |
| function WallAlignedObj:AlignObjAttached() | |
| local p = self:GetParent() | |
| assert(p) | |
| local ap = self:GetPos() + self:GetAttachOffset() | |
| local x, y, z, angle = WallWorldToVoxel(ap, self:GetAngle()) | |
| x, y, z = WallVoxelToWorld(x, y, z, angle) | |
| px, py, pz = p:GetPosXYZ() | |
| self:SetAttachOffset(x - px, y - py, z - pz) | |
| self:SetAngle(angle) --havn't tested with parents with angle ~= 0, might not work | |
| end | |
| function WallAlignedObj:AlignObj(pos, angle) | |
| local x, y, z | |
| if pos then | |
| x, y, z, angle = WallWorldToVoxel(pos, angle or self:GetAngle()) | |
| else | |
| x, y, z, angle = WallWorldToVoxel(self) | |
| end | |
| x, y, z = WallVoxelToWorld(x, y, z, angle) | |
| self:SetPosAngle(x, y, z, angle) | |
| end | |
| DefineClass.FloorAlignedObj = { | |
| __parents = { "AlignedObj" }, | |
| GetGridCoords = rawget(_G, "WorldToVoxel"), | |
| } | |
| function FloorAlignedObj:AlignObj(pos, angle) | |
| local x, y, z | |
| if pos then | |
| x, y, z, angle = WorldToVoxel(pos, angle or self:GetAngle()) | |
| else | |
| x, y, z, angle = WorldToVoxel(self) | |
| end | |
| x, y, z = VoxelToWorld(x, y, z) | |
| self:SetPosAngle(x, y, z, angle) | |
| end | |
| DefineClass.CornerAlignedObj = { | |
| __parents = { "AlignedObj" }, | |
| } | |
| function CornerAlignedObj:AlignObj(pos, angle) | |
| local x, y, z | |
| if pos then | |
| x, y, z, angle = CornerWorldToVoxel(pos, angle or self:GetAngle()) | |
| else | |
| x, y, z, angle = CornerWorldToVoxel(self) | |
| end | |
| x, y, z = CornerVoxelToWorld(x, y, z, angle) | |
| self:SetPosAngle(x, y, z, angle) | |
| end | |
| DefineClass.GroundAlignedObj = { | |
| __parents = { "AlignedObj" }, | |
| GetGridCoords = rawget(_G, "WorldToVoxel"), | |
| } | |
| function GroundAlignedObj:AlignObj(pos, angle) | |
| local x, y, z | |
| if pos then | |
| x, y, z, angle = WorldToVoxel(pos, angle or self:GetAngle()) | |
| if not pos:IsValidZ() then | |
| z = iz | |
| end | |
| else | |
| x, y, z, angle = WorldToVoxel(self) | |
| if not self:IsValidZ() then | |
| z = iz | |
| end | |
| end | |
| x, y, z = VoxelToWorld(x, y, z) | |
| self:SetPosAngle(x, y, z or iz, angle) | |
| end | |
| local function FloorsComboItems() | |
| local items = {} | |
| for i = -5, 10 do | |
| items[#items + 1] = tostring(i) | |
| end | |
| return items | |
| end | |
| function SlabMaterialComboItems() | |
| return PresetGroupCombo("SlabPreset", Slab.MaterialListClass, Slab.MaterialListFilter) | |
| end | |
| ---- | |
| -- CObject slab variant (no Lua object) | |
| DefineClass.CSlab = { | |
| __parents = { "EntityChangeKeepsFlags", "AlignedObj" }, | |
| flags = { efBuilding = true }, | |
| entity_base_name = "Slab", | |
| material = false, -- will store only temporally the value as the CObject Lua tables are subject to GC | |
| MaterialListClass = "SlabMaterials", | |
| MaterialListFilter = false, | |
| isVisible = true, | |
| always_visible = false, | |
| ApplyMaterialProps = empty_func, | |
| class_suppression_strenght = 0, | |
| variable_entity = true, | |
| } | |
| function CSlab:GetBaseEntityName() | |
| return string.format("%s_%s", self.entity_base_name, self.material) | |
| end | |
| function CSlab:GetSeed(max, const) | |
| assert(self:IsValidPos()) | |
| return BraidRandom(EncodeVoxelPos(self) + (const or 0), max) | |
| end | |
| function CSlab:ComposeEntityName() | |
| local base_entity = self:GetBaseEntityName() | |
| local material_preset = self:GetMaterialPreset() | |
| local subvariants = material_preset and material_preset.subvariants or empty_table | |
| if #subvariants > 0 then | |
| local seed = self:GetSeed() | |
| local remaining = subvariants | |
| while true do | |
| local subvariant, idx = table.weighted_rand(remaining, "chance", seed) | |
| if not subvariant then | |
| break | |
| end | |
| local entity = subvariant.suffix ~= "" and (base_entity .. "_" .. subvariant.suffix) or base_entity | |
| if IsValidEntity(entity) then | |
| return entity | |
| end | |
| remaining = remaining == subvariants and table.copy(subvariants) or remaining | |
| table.remove(remaining, idx) | |
| end | |
| end | |
| return IsValidEntity(base_entity) and base_entity or (base_entity .. "_01") | |
| end | |
| function CSlab:UpdateEntity() | |
| local name = self:ComposeEntityName() | |
| if IsValidEntity(name) then | |
| self:ChangeEntity(name) | |
| self:ApplyMaterialProps() | |
| elseif self.material ~= no_mat then | |
| self:ReportMissingSlabEntity(name) | |
| end | |
| end | |
| if Platform.developer and config.NoPassEditsOnSlabEntityChange then | |
| function CSlab:ChangeEntity(entity, ...) | |
| DbgSetErrorOnPassEdit(self, "%s: Entity %s --> %s", self.class, self:GetEntity(), entity) | |
| EntityChangeKeepsFlags.ChangeEntity(self, entity, ...) | |
| DbgClearErrorOnPassEdit(self) | |
| end | |
| end | |
| function CSlab:GetArtMaterialPreset() | |
| return CObject.GetMaterialPreset(self) | |
| end | |
| function CSlab:GetMaterialPreset() | |
| local material_list = Presets.SlabPreset[self.MaterialListClass] | |
| return material_list and material_list[self.material] | |
| end | |
| function CSlab:SetSuppressor(suppressor, initiator, reason) | |
| reason = reason or "suppressed" | |
| if suppressor then | |
| self:TurnInvisible(reason) | |
| else | |
| self:TurnVisible(reason) | |
| end | |
| return true | |
| end | |
| function CSlab:ShouldUpdateEntity(agent) | |
| return true | |
| end | |
| --presumably cslabs don't need reasons | |
| function CSlab:TurnInvisible(reason) | |
| self:ClearHierarchyEnumFlags(const.efVisible) | |
| end | |
| function CSlab:TurnVisible(reason) | |
| self:SetHierarchyEnumFlags(const.efVisible) | |
| end | |
| function CSlab:GetMaterialType() | |
| --this gets the combat or obj material, not to be confused with slab material.. | |
| local preset = self:GetMaterialPreset() | |
| return preset and preset.obj_material or self.material | |
| end | |
| ---- | |
| local function ListAddObj(list, obj) | |
| if not list[obj] then | |
| list[obj] = true | |
| list[#list + 1] = obj | |
| end | |
| end | |
| local function ListForEach(list, func, ...) | |
| for _, obj in ipairs(list or empty_table) do | |
| if IsValid(obj) then | |
| procall(obj[func], obj) | |
| end | |
| end | |
| end | |
| local DelayedUpdateEntSlabs = {} | |
| local DelayedUpdateVariantEntsSlabs = {} | |
| local DelayedAlignObj = {} | |
| function SlabUpdate() | |
| SuspendPassEdits("SlabUpdate", false) | |
| ListForEach(DelayedUpdateEntSlabs, "UpdateEntity") | |
| table.clear(DelayedUpdateEntSlabs) | |
| ListForEach(DelayedUpdateVariantEntsSlabs, "UpdateVariantEntities") | |
| table.clear(DelayedUpdateVariantEntsSlabs) | |
| ListForEach(DelayedAlignObj, "AlignObj") | |
| table.clear(DelayedAlignObj) | |
| ResumePassEdits("SlabUpdate") | |
| end | |
| local first = false | |
| function OnMsg.NewMapLoaded() | |
| first = true | |
| end | |
| function DelayedSlabUpdate() | |
| --assert(mapdata.GameLogic, "Thread will never resume on map with no GameLogic.") | |
| Wakeup(PeriodicRepeatThreads["DelayedSlabUpdate"]) | |
| end | |
| ---- | |
| --this class is used when copying/preserving props of slabs and room objs | |
| DefineClass.SlabPropHolder = { | |
| __parents = { "PropertyObject" }, | |
| --props we want stored : | |
| properties = { | |
| { id = "colors", name = "Colors", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, }, | |
| { id = "interior_attach_colors", name = "Interior Attach Color", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, help = "Color of the interior attach for ExIn materials.", no_edit = function(self) | |
| return self.variant == "Outdoor" | |
| end,}, | |
| { id = "exterior_attach_colors", name = "Exterior Attach Color", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, help = "Color of the exterior attach for InIn materials.", no_edit = function(self) | |
| return self.variant == "Outdoor" or self.variant == "OutdoorIndoor" | |
| end}, | |
| { name = "Subvariant", id = "subvariant", editor = "number", default = -1} | |
| }, | |
| } | |
| ---- | |
| DefineClass("SlabAutoResolve") | |
| DefineClass.Slab = { | |
| __parents = { "CSlab", "Object", "DestroyableSlab", "HideOnFloorChange", "ComponentExtraTransform", "EditorSubVariantObject", "Mirrorable", "SlabAutoResolve" }, | |
| flags = { gofPermanent = true, cofComponentColorizationMaterial = true, gofDetailClass0 = false, gofDetailClass1 = true }, | |
| properties = { | |
| category = "Slabs", | |
| { id = "buttons", name = "Buttons", editor = "buttons", default = false, dont_save = true, read_only = true, sort_order = -1, | |
| buttons = { | |
| {name = "Select Parent Room", func = "SelectParentRoom"}, | |
| }, | |
| }, | |
| { id = "material", name = "Material", editor = "preset_id", preset_class = "SlabPreset", preset_group = "SlabMaterials", extra_item = noneWallMat, default = "Planks", }, | |
| { id = "variant", name = "Variant", editor = "dropdownlist", items = PresetGroupCombo("SlabPreset", "SlabVariants"), default = "Outdoor", }, | |
| { id = "forceVariant", name = "Force Variant", editor = "dropdownlist", items = PresetGroupCombo("SlabPreset", "SlabVariants"), default = "", help = "Variants are picked automatically and settings to the variant prop are overriden by internal slab workings, use this prop to force this slab to this variant at all times."}, | |
| { id = "indoor_material_1", name = "Indoor Material 1", editor = "dropdownlist", items = PresetGroupCombo("SlabPreset", "SlabIndoorMaterials", false, no_mat), default = no_mat, no_edit = function(self) | |
| return self.variant == "Outdoor" | |
| end,}, | |
| { id = "indoor_material_2", name = "Indoor Material 2", editor = "dropdownlist", items = PresetGroupCombo("SlabPreset", "SlabIndoorMaterials", false, no_mat), default = no_mat, no_edit = function(self) | |
| return self.variant == "Outdoor" or self.variant == "OutdoorIndoor" | |
| end,}, | |
| { id = "colors", name = "Colors", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, }, | |
| { id = "colors1", name = "Colors 1", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, help = "Color of mat1 attach.", no_edit = function(self) | |
| return self.variant == "Outdoor" | |
| end, dont_save = true, no_edit = true}, --TODO: remove, save compat | |
| { id = "interior_attach_colors", name = "Interior Attach Color", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, help = "Color of the interior attach for ExIn materials.", no_edit = function(self) | |
| return self.variant == "Outdoor" | |
| end,}, | |
| { id = "colors2", name = "Colors 2", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, help = "Color of mat2 attach", no_edit = function(self) | |
| return self.variant == "Outdoor" or self.variant == "OutdoorIndoor" | |
| end, dont_save = true, no_edit = true}, --TODO: remove, save compat | |
| { id = "exterior_attach_colors", name = "Exterior Attach Color", editor = "nested_obj", base_class = "ColorizationPropSet", inclusive = true, default = false, help = "Color of the exterior attach for InIn materials.", no_edit = function(self) | |
| return self.variant == "Outdoor" or self.variant == "OutdoorIndoor" | |
| end,}, | |
| { id = "Walkable" }, | |
| { id = "ApplyToGrids" }, | |
| { id = "Collision" }, | |
| { id = "always_visible", name = "Always Visible", editor = "bool", help = "Ignores room slab logic for making slabs invisible. Only implemented for walls, other types upon request.", default = false }, | |
| { id = "ColorModifier", dont_save = true, read_only = true }, | |
| { id = "forceInvulnerableBecauseOfGameRules", name = "Invulnerable", editor = "bool", default = true, help = "In context of destruction."}, | |
| }, | |
| entity_base_name = "Slab", | |
| GetGridCoords = rawget(_G, "WorldToVoxel"), | |
| --in parent room | |
| variant_objects = false, | |
| isVisible = true, | |
| invisible_reasons = false, | |
| room = false, | |
| side = false, | |
| floor = 1, | |
| collision_allowed_mask = 0, | |
| colors_room_member = "outer_colors", | |
| room_container_name = false, | |
| invulnerable = true, | |
| subvariants_table_id = "subvariants", | |
| bad_entity = false, | |
| exterior_attach_colors_from_nbr = false, | |
| } | |
| function Slab:IsInvulnerable() | |
| --not checking IsObjInvulnerableDueToLDMark(self) because related setter is hidden for slabs; | |
| return self.invulnerable or TemporarilyInvulnerableObjs[self] | |
| end | |
| function Slab:GetColorsRoomMember() | |
| return self.colors_room_member | |
| end | |
| function SetupObjInvulnerabilityColorMarkingOnValueChanged(o) | |
| --stub | |
| end | |
| function Slab:SetforceInvulnerableBecauseOfGameRules(val) | |
| self.forceInvulnerableBecauseOfGameRules = val | |
| self.invulnerable = val | |
| SetupObjInvulnerabilityColorMarkingOnValueChanged(self) | |
| end | |
| function Slab:GetEntitySubvariant() | |
| local e = self:GetEntity() | |
| local strs = string.split(e, "_") | |
| return tonumber(strs[#strs]) | |
| end | |
| function Slab:SelectParentRoom() | |
| if IsValid(self.room) then | |
| editor.ClearSel() | |
| editor.AddToSel({self.room}) | |
| else | |
| print("This slab has no room.") | |
| end | |
| end | |
| function Slab:Init() | |
| -- all Slab operations must be captured by editor undo | |
| assert(EditorCursorObjs[self] or XEditorUndo:AssertOpCapture()) | |
| if IsValid(self.room) then | |
| self:SetWarped(self.room:GetWarped()) | |
| end | |
| end | |
| if Platform.developer then | |
| function Slab:Done() | |
| -- all Slab operations must be captured by editor undo | |
| assert(EditorCursorObjs[self] or XEditorUndo:AssertOpCapture()) | |
| end | |
| end | |
| function Slab:GameInit() | |
| self:UpdateSimMaterialId() | |
| end | |
| function Slab:OnEditorSetProperty(prop_id, old_value, ged) | |
| if prop_id == "material" or prop_id == "variant" then | |
| self.subvariant = -1 | |
| end | |
| if prop_id == "forceVariant" then | |
| self.variant = self.forceVariant | |
| self:DelayedUpdateEntity() | |
| self:DelayedUpdateVariantEntities() | |
| end | |
| end | |
| function Slab:SetWarped(val) | |
| --all attached objs as well, like wallpapers and such | |
| if val then | |
| self:SetHierarchyGameFlags(const.gofWarped) | |
| else | |
| self:ClearHierarchyGameFlags(const.gofWarped) | |
| end | |
| end | |
| function Slab:UpdateSimMaterialId() | |
| end | |
| function Slab:Setmaterial(val) | |
| self.material = val | |
| self:UpdateSimMaterialId() | |
| end | |
| function Slab:Setvariant(val) | |
| self.variant = val | |
| end | |
| function SlabAutoResolve:SelectionPropagate() | |
| return self.room | |
| end | |
| function SlabAutoResolve:CompleteElementConstruction() | |
| self:UpdateSimMaterialId() | |
| end | |
| --suppress saving of this prop | |
| function Slab:Setroom(val) | |
| self.room = val | |
| self:DelayedUpdateEntity() | |
| end | |
| function Slab:GetDefaultColor() | |
| local member = self:GetColorsRoomMember() | |
| return member and table.get(self.room, member) or false | |
| end | |
| function Slab:Setcolors(val) | |
| local def_color = self:GetDefaultColor() | |
| if not val or val == empty_table or val == ColorizationPropSet then | |
| val = def_color | |
| end | |
| self.colors = (val ~= def_color) and val:Clone() or nil | |
| self:SetColorization(val) | |
| SetSlabColorHelper(self, val) | |
| end | |
| function Slab:Setinterior_attach_colors(val) | |
| local def_color = self.room and self.room.inner_colors or false | |
| if not val or val == empty_table or val == ColorizationPropSet then | |
| val = def_color | |
| end | |
| self.interior_attach_colors = (val ~= def_color) and val:Clone() or nil | |
| if self.variant_objects and self.variant_objects[1] then | |
| SetSlabColorHelper(self.variant_objects[1], val) | |
| end | |
| end | |
| function Slab:GetExteriorAttachColor() | |
| return self.exterior_attach_colors or self.exterior_attach_colors_from_nbr or self.colors or self:GetDefaultColor() | |
| end | |
| function Slab:Setexterior_attach_colors_from_nbr(val) | |
| if val == empty_table then | |
| val = false | |
| end | |
| if val and (not self.exterior_attach_colors_from_nbr or not rawequal(self.exterior_attach_colors_from_nbr, val)) then | |
| val = val:Clone() | |
| end | |
| self.exterior_attach_colors_from_nbr = val | |
| if self.variant_objects and self.variant_objects[2] then | |
| SetSlabColorHelper(self.variant_objects[2], self:GetExteriorAttachColor()) | |
| end | |
| end | |
| function Slab:Setexterior_attach_colors(val) | |
| if val == empty_table then | |
| val = false | |
| end | |
| if val and (not self.exterior_attach_colors or not rawequal(self.exterior_attach_colors, val)) then | |
| val = val:Clone() | |
| end | |
| self.exterior_attach_colors = val | |
| if self.variant_objects and self.variant_objects[2] then | |
| SetSlabColorHelper(self.variant_objects[2], self:GetExteriorAttachColor()) | |
| end | |
| end | |
| function Slab:CanMirror() | |
| return true | |
| end | |
| local invisible_mask = const.cmDynInvisible & ~const.cmVisibility | |
| function Slab:TurnInvisible(reason) | |
| assert(not self.always_visible) | |
| self.invisible_reasons = table.create_set(self.invisible_reasons, reason, true) | |
| if self.isVisible or self:GetEnumFlags(efVisible) ~= 0 then | |
| self.isVisible = false | |
| local mask = collision.GetAllowedMask(self) | |
| self.collision_allowed_mask = mask ~= 0 and mask or nil | |
| self:ClearHierarchyEnumFlags(efVisible) | |
| collision.SetAllowedMask(self, invisible_mask) | |
| end | |
| end | |
| function Slab:TurnVisible(reason) | |
| local invisible_reasons = self.invisible_reasons | |
| if reason and invisible_reasons then | |
| invisible_reasons[reason] = nil | |
| end | |
| if not next(invisible_reasons) and not self.isVisible then | |
| self.isVisible = nil | |
| assert(self.isVisible) | |
| self:SetHierarchyEnumFlags(efVisible) | |
| collision.SetAllowedMask(self, self.collision_allowed_mask) | |
| self.collision_allowed_mask = nil | |
| self.invisible_reasons = nil | |
| end | |
| end | |
| local sx, sy, sz = const.SlabSizeX or guim, const.SlabSizeY or guim, const.SlabSizeZ or guim | |
| local slabgroupop_lastObjs = false | |
| local slabgroupop_lastRealTime = false | |
| local function slab_group_op_done(objs) | |
| local rt = RealTime() | |
| if objs == slabgroupop_lastObjs and slabgroupop_lastRealTime and slabgroupop_lastRealTime == rt then | |
| return true | |
| end | |
| slabgroupop_lastObjs = objs | |
| slabgroupop_lastRealTime = rt | |
| return false | |
| end | |
| local slab_sides = { "N", "W", "S", "E" } | |
| local slab_coord_limit = shift(1, 20) | |
| local function slab_hash(x, y, z, side) | |
| local s = table.find(slab_sides, side) or 0 | |
| assert(x < slab_coord_limit and y < slab_coord_limit and z < slab_coord_limit) | |
| return x + shift(y, 20) + shift(z, 40) + shift(s, 60) | |
| end | |
| function Slab:SetHeatMaterialIndex(matIndex) | |
| self:SetCustomData(9, matIndex) | |
| end | |
| function Slab:GetHeatMaterialIndex() | |
| return self:GetCustomData(9) | |
| end | |
| function Slab:RemoveDuplicates() | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| local gameFlags = is_permanent and gofPermanent or nil | |
| MapDelete(self, 0, self.class, nil, nil, gameFlags, function(o, self, is_permanent) | |
| return obj ~= self and (is_permanent or o:GetGameFlags(gofPermanent) == 0) and self:IsSameLocation(obj) | |
| end, self, is_permanent) | |
| end | |
| function Slab:EditorCallbackPlace(reason) | |
| if reason == "paste" or reason == "clone" or reason == "undo" then return end | |
| local x, y, z = self:GetPosXYZ() | |
| local surfz = terrain.GetHeight(x, y) | |
| z = z or (surfz + voxelSizeZ - 1) | |
| while z < surfz do | |
| z = z + sz | |
| end | |
| self:AlignObj(point(x, y, z)) | |
| self:UpdateEntity() | |
| end | |
| function Slab:IsSameLocation(obj) | |
| local x1, y1, z1 = self:GetPosXYZ() | |
| local x2, y2, z2 = obj:GetPosXYZ() | |
| return x1 == x2 and y1 == y2 and z1 == z2 | |
| end | |
| function Slab:GetWorldBBox() | |
| return GetSlabWorldBBox(self:GetPos(), 1, 1, self:GetAngle()) | |
| end | |
| function Slab:GetSeed(max, const) | |
| assert(self:IsValidPos()) | |
| return BraidRandom(EncodeVoxelPos(self) + (IsValid(self.room) and self.room.seed or 0) + (const or 0), max) | |
| end | |
| local cached_totals = {} | |
| local function ClearCachedTotals() | |
| cached_totals = {} | |
| end | |
| OnMsg.DoneMap = ClearCachedTotals | |
| OnMsg.DataReload = ClearCachedTotals | |
| OnMsg.PresetSave = ClearCachedTotals | |
| function GetMaterialSubvariants(svd, subvariants_id) | |
| if not svd then return false, 0 end | |
| local subvariants = not subvariants_id and svd.subvariants or subvariants_id and svd:HasMember(subvariants_id) and svd[subvariants_id] | |
| local key = xxhash(svd.class, svd.id, subvariants_id or "") | |
| local total = cached_totals[key] | |
| if not total then | |
| total = 0 | |
| for i = 1, #(subvariants or empty_table) do | |
| total = total + subvariants[i].chance | |
| end | |
| cached_totals[key] = total | |
| end | |
| return subvariants, total | |
| end | |
| function Slab:Setsubvariant(val) | |
| EditorSubVariantObject.Setsubvariant(self, val) | |
| self:DelayedUpdateEntity() | |
| end | |
| function Slab:ResetSubvariant() | |
| EditorSubVariantObject.ResetSubvariant(self, val) | |
| self:UpdateEntity() | |
| end | |
| variantToVariantName = { | |
| OutdoorIndoor = "ExIn", | |
| IndoorIndoor = "InIn", | |
| Outdoor = "ExEx", | |
| } | |
| function Slab:GetBaseEntityName() | |
| return string.format("%sExt_%s_Wall_%s", self.entity_base_name, self.material, variantToVariantName[self.variant]) | |
| end | |
| function GetRandomSubvariantEntity(random, subvariants, get_ent_func, ...) | |
| local t = 0 | |
| for i = 1, #subvariants do | |
| t = t + subvariants[i].chance | |
| if i == #subvariants or t > random then | |
| local ret = get_ent_func(subvariants[i].suffix, ...) | |
| while i > 1 and not IsValidEntity(ret) do | |
| --fallback to first valid ent in the set | |
| i = i - 1 | |
| ret = (subvariants[i].chance > 0 or i == 1) and get_ent_func(subvariants[i].suffix, ...) or false | |
| end | |
| return ret, subvariants[i].suffix | |
| end | |
| end | |
| end | |
| function Slab:GetSubvariantDigitStr(subvariants) | |
| local digit = self.subvariant | |
| if digit == -1 then | |
| return "01" | |
| end | |
| if subvariants then | |
| digit = ((digit - 1) % #subvariants) + 1 --assumes "01, 02, etc. suffixes | |
| end | |
| return digit < 10 and "0" .. tostring(digit) or tostring(digit) | |
| end | |
| function Slab:ComposeEntityName() | |
| local material_list = Presets.SlabPreset[self.MaterialListClass] | |
| local svd = material_list and material_list[self.material] | |
| local svdId = self.subvariants_table_id | |
| local baseEntity = self:GetBaseEntityName() | |
| if svd and svd[svdId] and #svd[svdId] > 0 then | |
| local subvariants, total = GetMaterialSubvariants(svd, svdId) | |
| if self.subvariant ~= -1 then --user selected subvar | |
| local digitStr = self:GetSubvariantDigitStr() | |
| local ret = string.format("%s_%s", baseEntity, digitStr) | |
| if not IsValidEntity(ret) then | |
| print("Reverting slab [" .. self.handle .. "] subvariant [" .. self.subvariant .. "] because no entity [" .. ret .. "] found. The slab has a subvariant set by the user (level-designer) which produces an invalid entity, this subvariant will be reverted back to a random subvariant. Re-saving the map will save the removed set subvariant and this message will no longer appear.") | |
| ret = false | |
| self.subvariant = -1 | |
| else | |
| return ret | |
| end | |
| end | |
| return GetRandomSubvariantEntity(self:GetSeed(total), subvariants, function(suffix, baseEntity) | |
| return string.format("%s_%s", baseEntity, suffix) | |
| end, baseEntity) | |
| else | |
| local digitStr = self:GetSubvariantDigitStr() | |
| local ent = string.format("%s_%s", baseEntity, digitStr) | |
| return IsValidEntity(ent) and ent or baseEntity | |
| end | |
| end | |
| function Slab:ComposeIndoorMaterialEntityName(mat) | |
| if self.destroyed_neighbours ~= 0 and self.destroyed_entity_side ~= 0 or | |
| self.is_destroyed and self.diagonal_ent_mask ~= 0 then | |
| return self:ComposeBrokenIndoorMaterialEntityName(mat) | |
| end | |
| local svd = (Presets.SlabPreset.SlabIndoorMaterials or empty_table)[mat] | |
| if svd and svd.subvariants and #svd.subvariants > 0 then | |
| local subvariants, total = GetMaterialSubvariants(svd) | |
| return GetRandomSubvariantEntity(self:GetSeed(total), subvariants, function(suffix, mat) | |
| return string.format("WallInt_%s_Wall_%s", mat, suffix) | |
| end, mat) | |
| else | |
| return string.format("WallInt_%s_Wall_01", mat) | |
| end | |
| end | |
| function SetSlabColorHelper(obj, colors) | |
| if obj:GetMaxColorizationMaterials() > 0 then | |
| obj:SetColorModifier(RGB(100, 100, 100)) | |
| if not colors then | |
| colors = obj:GetDefaultColorizationSet() | |
| end | |
| obj:SetColorization(colors, "ignore_his_max") | |
| else | |
| local color1 = (colors or ColorizationPropSet):GetEditableColor1() | |
| local r,g,b = GetRGB(color1) | |
| obj:SetColorModifier(RGB(r / 2, g / 2, b / 2)) | |
| end | |
| end | |
| function Slab:ShouldUseRoomMirroring() | |
| return true | |
| end | |
| function Slab:MirroringFromRoom() | |
| --called on update entity, deals with mirroring coming from parent room | |
| if not IsValid(self.room) then | |
| return | |
| end | |
| if not self:ShouldUseRoomMirroring() then | |
| return | |
| end | |
| local mirror = self:CanMirror() and self:GetSeed(100, 115249) < 50 | |
| self:SetMirrored(mirror) | |
| if mirror then | |
| -- interior objects can't be mirrored, so unmirror them... by mirroring them again | |
| for _, interior in ipairs(self.variant_objects or empty_table) do | |
| interior:SetMirrored(true) | |
| end | |
| end | |
| end | |
| function Slab:DelayedUpdateEntity() | |
| ListAddObj(DelayedUpdateEntSlabs, self) | |
| DelayedSlabUpdate() | |
| end | |
| function Slab:DelayedUpdateVariantEntities() | |
| ListAddObj(DelayedUpdateVariantEntsSlabs, self) | |
| DelayedSlabUpdate() | |
| end | |
| function Slab:DelayedAlignObj() | |
| ListAddObj(DelayedAlignObj, self) | |
| DelayedSlabUpdate() | |
| end | |
| function Slab:ForEachDestroyedAttach(f, ...) | |
| for k, v in pairs(rawget(self, "destroyed_attaches") or empty_table) do | |
| if IsValid(v) then | |
| f(v, ...) | |
| elseif type(v) == "table" then | |
| for i = 1, #v do | |
| local vi = v[i] | |
| if IsValid(vi) then | |
| f(vi, ...) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| function Slab:RefreshColors() | |
| local clrs = self.colors or self:GetDefaultColor() | |
| SetSlabColorHelper(self, clrs) | |
| self:ForEachDestroyedAttach(function(v, clrs) | |
| SetSlabColorHelper(v, clrs) | |
| end, clrs) | |
| end | |
| function Slab:DestroyAttaches(...) | |
| Object.DestroyAttaches(self, ...) | |
| self.variant_objects = nil | |
| end | |
| function Slab:UpdateDestroyedState() | |
| return false | |
| end | |
| function Slab:GetSubvariantFromEntity(e) | |
| e = e or self:GetEntity() | |
| local strs = string.split(e, "_") | |
| return tonumber(strs[#strs]) or 1 | |
| end | |
| function Slab:LockSubvariantToCurrentEntSubvariant() | |
| local e = self:GetEntity() | |
| if IsValidEntity(e) and e ~= "InvisibleObject" then | |
| self.subvariant = self:GetSubvariantFromEntity(e) | |
| end | |
| end | |
| function Slab:LockRandomSubvariantToCurrentEntSubvariant() | |
| if self.subvariant ~= -1 then return end | |
| self:LockSubvariantToCurrentEntSubvariant() | |
| end | |
| function Slab:UnlockSubvariant() | |
| self.subvariant = -1 | |
| end | |
| function Slab:SetVisible(value) | |
| Object.SetVisible(self, value) | |
| if value then | |
| self:TurnVisible("SetVisible") | |
| else | |
| self:TurnInvisible("SetVisible") | |
| end | |
| end | |
| function Slab:ResetVisibilityFlags() | |
| if self.isVisible then | |
| self:SetHierarchyEnumFlags(const.efVisible) | |
| else | |
| self:ClearHierarchyEnumFlags(const.efVisible) | |
| end | |
| end | |
| function Slab:UpdateEntity() | |
| self.bad_entity = nil | |
| if self.destroyed_neighbours ~= 0 or self.is_destroyed then | |
| if self:UpdateDestroyedState() then | |
| return | |
| end | |
| elseif self.destroyed_entity_side ~= 0 then | |
| --nbr got repaired | |
| self.destroyed_entity_side = 0 | |
| self.destroyed_entity = false | |
| self:RestorePreDestructionSubvariant() | |
| end | |
| local name = self:ComposeEntityName() | |
| if name == self:GetEntity() then | |
| self:UpdateSimMaterialId() | |
| self:MirroringFromRoom() | |
| elseif IsValidEntity(name) then | |
| self:UpdateSimMaterialId() | |
| self:ChangeEntity(name, "idle") | |
| self:MirroringFromRoom() | |
| self:RefreshColors() | |
| self:ApplyMaterialProps() | |
| --change ent resets flags, set them back | |
| self:ResetVisibilityFlags() | |
| if Platform.developer and IsEditorActive() and selo() == self then | |
| ObjModified(self) --fixes 0159218 | |
| end | |
| elseif self.material ~= no_mat then | |
| self:ReportMissingSlabEntity(name) | |
| end | |
| end | |
| DefineClass.SlabInteriorObject = { | |
| __parents = { "Object", "ComponentAttach" }, | |
| flags = { efCollision = false, efApplyToGrids = false, cofComponentColorizationMaterial = true } | |
| } | |
| function Slab:UpdateVariantEntities() | |
| end | |
| function Slab:SetProperty(id, value) | |
| EditorCallbackObject.SetProperty(self, id, value) | |
| if id == "material" then | |
| self:DelayedUpdateEntity() | |
| elseif id == "entity" or id == "variant" or id == "indoor_material_1" or id == "indoor_material_2" then | |
| self:DelayedUpdateEntity() | |
| self:DelayedUpdateVariantEntities() | |
| end | |
| end | |
| function Slab:GetContainerInRoom() | |
| local room = self.room | |
| if IsValid(room) then | |
| local container = self.room_container_name | |
| container = container and room[container] | |
| return container and container[self.side] or container | |
| end | |
| end | |
| function Slab:RemoveFromRoomContainer() | |
| local t = self:GetContainerInRoom() | |
| if t then | |
| local idx = table.find(t, self) | |
| if idx then | |
| t[idx] = false | |
| end | |
| end | |
| end | |
| function Slab:GetObjIdentifier() | |
| if not self.room or not IsValid(self.room) or not self.room_container_name then | |
| return CObject.GetObjIdentifier(self) | |
| end | |
| local idx = table.find(self:GetContainerInRoom(), self) | |
| assert(idx) | |
| return xxhash(CObject.GetObjIdentifier(self.room), self.room_container_name, self.side, idx) | |
| end | |
| function Slab:EditorCallbackDelete(reason) | |
| self:RemoveFromRoomContainer() | |
| -- delete hidden (supressed) slabs from other rooms that are on the same position | |
| if EditorCursorObjs[self] or not self.isVisible or reason == "undo" then | |
| return | |
| end | |
| MapForEach(self, 0, self.class, function(o, self) | |
| if o ~= self and not o.isVisible then | |
| o:RemoveFromRoomContainer() | |
| DoneObject(o) | |
| end | |
| end, self) | |
| end | |
| function Slab:GetEditorParentObject() | |
| return self.room | |
| end | |
| DefineClass.FloorSlab = { | |
| __parents = { "Slab", "FloorAlignedObj", "DestroyableFloorSlab" }, | |
| flags = { efPathSlab = true }, | |
| properties = { | |
| category = "Slabs", | |
| { id = "material", name = "Material", editor = "preset_id", preset_class = "SlabPreset", preset_group = "FloorSlabMaterials", extra_item = noneWallMat, default = "Planks", }, | |
| { id = "variant", name = "Variant", editor = "dropdownlist", items = PresetGroupCombo("SlabPreset", "SlabVariants"), default = "", no_edit = true }, | |
| }, | |
| entity = "Floor_Planks", | |
| entity_base_name = "Floor", | |
| MaterialListClass = "FloorSlabMaterials", | |
| colors_room_member = "floor_colors", | |
| room_container_name = "spawned_floors", | |
| } | |
| FloorSlab.MirroringFromRoom = empty_func | |
| function FloorSlab:CanMirror() | |
| return false | |
| end | |
| function FloorSlab:GetBaseEntityName() | |
| return string.format("%s_%s", self.entity_base_name, self.material) | |
| end | |
| DefineClass.CeilingSlab = { | |
| __parents = { "Slab", "FloorAlignedObj", "DestroyableFloorSlab" }, | |
| flags = { efWalkable = false, efCollision = false, efApplyToGrids = false, efPathSlab = false }, | |
| properties = { | |
| category = "Slabs", | |
| { id = "material", name = "Material", editor = "preset_id", preset_class = "SlabPreset", preset_group = "FloorSlabMaterials", extra_item = noneWallMat, default = "Planks", }, | |
| { id = "forceInvulnerableBecauseOfGameRules", name = "Invulnerable", editor = "bool", default = false, help = "In context of destruction."}, | |
| }, | |
| entity = "Floor_Planks", | |
| entity_base_name = "Floor", | |
| MaterialListClass = "FloorSlabMaterials", | |
| room_container_name = "roof_objs", | |
| class_suppression_strenght = -10, | |
| } | |
| function CeilingSlab:GetBaseEntityName() | |
| return string.format("%s_%s", self.entity_base_name, self.material) | |
| end | |
| DefineClass.BaseWallSlab = { | |
| __parents = { "CSlab" }, | |
| } | |
| DefineClass.WallSlab = { | |
| __parents = { "Slab", "BaseWallSlab", "WallAlignedObj", "ComponentAttach"}, | |
| entity_base_name = "Wall", | |
| wall_obj = false, -- SlabWallObject currently covering this wall | |
| GetGridCoords = rawget(_G, "WallWorldToVoxel"), | |
| room_container_name = "spawned_walls", | |
| class_suppression_strenght = 100, | |
| } | |
| function WallSlab:UpdateVariantEntities() | |
| if self.variant == "Outdoor" or (self.is_destroyed and self.diagonal_ent_mask == 0) then | |
| DoneObjects(self.variant_objects) | |
| self.variant_objects = nil | |
| elseif self.variant == "OutdoorIndoor" then | |
| if not self.variant_objects then | |
| local o1 = PlaceObject("SlabInteriorObject") | |
| self:Attach(o1) | |
| o1:SetAttachAngle(180 * 60) | |
| self.variant_objects = { o1 } | |
| else | |
| DoneObject(self.variant_objects[2]) | |
| self.variant_objects[2] = nil | |
| end | |
| if IsValid(self.variant_objects[1]) then | |
| local e = self:ComposeIndoorMaterialEntityName(self.indoor_material_1) | |
| if self.variant_objects[1]:GetEntity() ~= e then | |
| self.variant_objects[1]:ChangeEntity(e) | |
| end | |
| self:Setinterior_attach_colors(self.interior_attach_colors or self.room and self.room.inner_colors) | |
| end | |
| elseif self.variant == "IndoorIndoor" then | |
| if not self.variant_objects then | |
| local o1 = PlaceObject("SlabInteriorObject") | |
| self:Attach(o1) | |
| o1:SetAttachAngle(180 * 60) | |
| self.variant_objects = { o1 } | |
| end | |
| if not IsValid(self.variant_objects[2]) then | |
| local o1 = PlaceObject("SlabInteriorObject") | |
| self:Attach(o1) | |
| self.variant_objects[2] = o1 | |
| end | |
| if IsValid(self.variant_objects[1]) then | |
| local e = self:ComposeIndoorMaterialEntityName(self.indoor_material_1) | |
| if self.variant_objects[1]:GetEntity() ~= e then | |
| self.variant_objects[1]:ChangeEntity(e) | |
| end | |
| self:Setinterior_attach_colors(self.interior_attach_colors or self.room and self.room.inner_colors) | |
| end | |
| if IsValid(self.variant_objects[2]) then | |
| local e = self:ComposeIndoorMaterialEntityName(self.indoor_material_2) | |
| if self.variant_objects[2]:GetEntity() ~= e then | |
| self.variant_objects[2]:ChangeEntity(e) | |
| end | |
| SetSlabColorHelper(self.variant_objects[2], self:GetExteriorAttachColor()) | |
| end | |
| end | |
| self:SetWarped(IsValid(self.room) and self.room:GetWarped() or self:GetWarped()) --propagate warped state to variant_objs | |
| end | |
| function WallSlab:RefreshColors() | |
| local clrs = self.colors or self:GetDefaultColor() | |
| local iclrs = self.interior_attach_colors or self.room and self.room.inner_colors | |
| SetSlabColorHelper(self, clrs) | |
| self:ForEachDestroyedAttach(function(v, clrs, self) | |
| SetSlabColorHelper(v, clrs) | |
| --manage attaches of attaches colors | |
| local atts = v:GetAttaches() | |
| for i = 1, #(atts or "") do | |
| local a = atts[i] | |
| if not rawget(a, "editor_ignore") then | |
| local angle = a:GetAttachAngle() | |
| if angle == 0 then | |
| SetSlabColorHelper(a, self:GetExteriorAttachColor()) | |
| else | |
| SetSlabColorHelper(a, iclrs) | |
| end | |
| end | |
| end | |
| end, clrs, self) | |
| if self.variant_objects then | |
| if self.variant_objects[1] then | |
| SetSlabColorHelper(self.variant_objects[1], self.interior_attach_colors or self.room and self.room.inner_colors) | |
| end | |
| if self.variant_objects[2] then | |
| SetSlabColorHelper(self.variant_objects[2], self:GetExteriorAttachColor()) | |
| end | |
| end | |
| end | |
| function WallSlab:EditorCallbackPlace(reason) | |
| Slab.EditorCallbackPlace(self, reason) | |
| if not self.room then | |
| self.always_visible = true -- manually placed slabs can't be suppressed | |
| end | |
| end | |
| WallSlab.EditorCallbackPlaceCursor = WallSlab.EditorCallbackPlace | |
| function WallSlab:Done() | |
| self.wall_obj = nil --clear refs if any | |
| end | |
| function WallSlab:SetWallObj(obj) | |
| self.wall_obj = obj | |
| if self.always_visible then | |
| return | |
| end | |
| return self:SetSuppressor(obj, nil, "wall_obj") | |
| end | |
| function WallSlab:SetWallObjShadowOnly(shadow_only, clear_contour) | |
| local wall_obj = self.wall_obj | |
| if wall_obj then | |
| if (const.cmtVisible and (not CMT_IsObjVisible(wall_obj))) ~= shadow_only then | |
| wall_obj:SetShadowOnly(shadow_only) | |
| end | |
| if wall_obj.main_wall == self then | |
| wall_obj:SetManagedSlabsShadowOnly(shadow_only, clear_contour) | |
| end | |
| end | |
| end | |
| function WallSlab:EditorCallbackDelete(reason) | |
| Slab.EditorCallbackDelete(self, reason) | |
| if IsValid(self.wall_obj) and self.wall_obj.main_wall == self then | |
| DoneObject(self.wall_obj) | |
| end | |
| end | |
| function WallSlab:GetSide(angle) | |
| angle = angle or self:GetAngle() | |
| if angle == 0 then | |
| return "E" | |
| elseif angle == 90*60 then | |
| return "S" | |
| elseif angle == 180*60 then | |
| return "W" | |
| else | |
| return "N" | |
| end | |
| end | |
| function WallSlab:IsSameLocation(obj) | |
| local x1, y1, z1 = self:GetPosXYZ() | |
| local x2, y2, z2 = obj:GetPosXYZ() | |
| local side1 = self:GetSide() | |
| local side2 = obj:GetSide() | |
| return x1 == x2 and y1 == y2 and z1 == z2 and side1 == side2 | |
| end | |
| function WallSlab:ExtendWalls(objs) | |
| local visited, topmost = {}, {} | |
| -- filter out objects on the same grid (x, y), keeping the topmost only | |
| for _, obj in ipairs(objs) do | |
| local gx, gy, gz = obj:GetGridCoords() | |
| local side = obj:GetSide() | |
| local loc = slab_hash(gx, gy, 0, side) -- ignore z so the whole column gets the same hash | |
| if not visited[loc] then | |
| visited[loc] = true | |
| -- find the topmost wall in this location | |
| local idx = #topmost + 1 | |
| topmost[idx] = obj | |
| while true do | |
| local wall = GetWallSlab(gx, gy, gz + 1, side) | |
| if IsValid(wall) then | |
| gz = gz + 1 | |
| topmost[idx] = wall | |
| else | |
| break | |
| end | |
| end | |
| end | |
| end | |
| -- make an initial pass to make sure there's a space for the extension and the objects being extended are not being pushed up by subsequent ones | |
| for _, obj in ipairs(topmost) do | |
| local gx, gy, gz = obj:GetGridCoords() | |
| SlabsPushUp(gx, gy, gz + 1) | |
| end | |
| -- create the walls | |
| for _, obj in ipairs(topmost) do | |
| local x, y, z = obj:GetPosXYZ() | |
| local wall = WallSlab:new() | |
| wall:SetPosAngle(x, y, z + sz, obj:GetAngle()) | |
| wall:EditorCallbackClone(obj) -- copy relevant properties | |
| wall:AlignObj() | |
| wall:UpdateEntity() | |
| end | |
| end | |
| DefineClass.StairSlab = { | |
| __parents = { "Slab", "FloorAlignedObj" }, | |
| flags = { efPathSlab = true }, | |
| properties = { | |
| { category = "Slabs", id = "material", name = "Material", editor = "preset_id", preset_class = "SlabPreset", preset_group = "StairsSlabMaterials", extra_item = noneWallMat, default = "WoodScaff", }, | |
| { category = "Slab Tools", id = "autobuild_stair_up", editor = "buttons", buttons = {{name = "Extend Up", func = "UIExtendUp"}}, default = false, dont_save = true}, | |
| { category = "Slab Tools", id = "autobuild_stair_down", editor = "buttons", buttons = {{name = "Extend Down", func = "UIExtendDown" }}, default = false, dont_save = true}, | |
| { name = "Subvariant", id = "subvariant", editor = "number", default = 1, | |
| buttons = { | |
| { name = "Next", func = "CycleEntityBtn" }, | |
| }, | |
| }, | |
| { id = "variant" }, | |
| { id = "always_visible" }, | |
| { id = "forceInvulnerableBecauseOfGameRules", name = "Invulnerable", editor = "bool", default = true, help = "In context of destruction.", dont_save = true, no_edit = true}, | |
| }, | |
| entity = "Stairs_WoodScaff_01", --should be some sort of valid ent or place object new excludes it | |
| entity_base_name = "Stairs", | |
| MaterialListClass = "StairsSlabMaterials", | |
| hide_floor_slabs_above_in_range = 2, --how far above the slab origin will floor slabs be hidden | |
| } | |
| function StairSlab:IsInvulnerable() | |
| return true | |
| end | |
| --[[ | |
| function StairSlab:GameInit() | |
| self:PFConnect() | |
| end | |
| function StairSlab:Done() | |
| self:PFDisconnect() | |
| end | |
| ]] | |
| function StairSlab:GetBaseEntityName() | |
| return string.format("%s_%s", self.entity_base_name, self.material) | |
| end | |
| function StairSlab:GetStepZ() | |
| return GetStairsStepZ(self) | |
| end | |
| function StairSlab:GetExitOffset() | |
| local dx, dy | |
| local angle = self:GetAngle() | |
| if angle == 0 then | |
| dx, dy = 0, 1 | |
| elseif angle == 90*60 then | |
| dx, dy = -1, 0 | |
| elseif angle == 180*60 then | |
| dx, dy = 0, -1 | |
| else | |
| assert(angle == 270*60) | |
| dx, dy = 1, 0 | |
| end | |
| return dx, dy, 1 | |
| end | |
| function StairSlab:TraceConnectedStairs(zdir) | |
| assert(zdir == 1 or zdir == -1) | |
| local first, last | |
| local dx, dy = self:GetExitOffset() | |
| local angle = self:GetAngle() | |
| dx, dy = dx * zdir, dy * zdir -- reverse the direction if walking down | |
| local all = {} | |
| local gx, gy, gz = self:GetGridCoords() | |
| -- check for any stairs below heading toward this voxel | |
| -- repeat until the first(lowest one) is found | |
| local step = 1 | |
| while true do | |
| local obj = GetStairSlab(gx + step * dx, gy + step * dy, gz + step * zdir) | |
| if IsValid(obj) and obj:GetAngle() == angle and obj.floor == self.floor then | |
| first = first or obj | |
| last = obj | |
| all[step] = obj | |
| step = step + 1 | |
| else | |
| break | |
| end | |
| end | |
| return first, last, all | |
| end | |
| --[[ | |
| function StairSlab:PFConnect() | |
| local first, last, start_stair, end_stair | |
| -- trace down | |
| first, last = self:TraceConnectedStairs(-1) | |
| start_stair = last or self | |
| if first then | |
| pf.RemoveTunnel(first) | |
| end | |
| -- trace up | |
| first, last = self:TraceConnectedStairs(1) | |
| end_stair = last or self | |
| if first then | |
| pf.RemoveTunnel(first) | |
| end | |
| --DbgClearVectors() | |
| self:TunnelStairs(start_stair, end_stair) | |
| end | |
| function StairSlab:TunnelStairs(start_stair, end_stair) | |
| -- add a tunnel starting at Stairbottom spot of the first stair and ending on Stairtop spot of the last one | |
| local start_point = start_stair:GetSpotPos(start_stair:GetSpotBeginIndex("Stairbottom")) | |
| local exit_point = end_stair:GetSpotPos(end_stair:GetSpotBeginIndex("Stairtop")) | |
| local h = terrain.GetHeight(start_point) | |
| if h >= start_point:z() then | |
| start_point:SetInvalidZ() | |
| end | |
| local weight = 1 | |
| pf.AddTunnel(start_stair, start_point, exit_point, weight, -1, const.fTunnelActiveOnImpassable) | |
| --DbgAddVector(start_point, exit_point - start_point, const.clrGreen) | |
| end | |
| function StairSlab:PFDisconnect() | |
| DbgClearVectors() | |
| -- trace down | |
| local first, last = self:TraceConnectedStairs(-1) | |
| if last then | |
| pf.RemoveTunnel(last) | |
| self:TunnelStairs(last, first) | |
| else | |
| -- no stairs below, tunnel starts from self | |
| pf.RemoveTunnel(self) | |
| end | |
| -- trace up | |
| local first, last = self:TraceConnectedStairs(1) | |
| if first and last then | |
| -- already disconnected, no need to further remove tunnels | |
| self:TunnelStairs(first, last) | |
| end | |
| end | |
| ]] | |
| function StairSlab:UIExtendUp(parent, prop_id, ged) | |
| if slab_group_op_done(parent or {self}) then | |
| return | |
| end | |
| -- trace up from 'self' | |
| local first, last = self:TraceConnectedStairs(1) | |
| -- check the spot for the next stair for a floor | |
| local obj = last or self | |
| local x, y, z = obj:GetGridCoords() | |
| local dx, dy, dz = obj:GetExitOffset() | |
| local floor = GetFloorSlab(x + dx, y + dy, z + dz) | |
| if IsValid(floor) then | |
| print("Can't extend the stairs upward, floor tile is in the way") | |
| return | |
| end | |
| x, y, z = obj:GetPosXYZ() | |
| local stair = StairSlab:new({material = self.material, subvariant = self.subvariant}) | |
| stair:SetPosAngle(x + dx * sx, y + dy * sy, z + dz * sz, obj:GetAngle()) | |
| stair:EditorCallbackClone(self) | |
| stair:UpdateEntity() | |
| self:AlignObj() | |
| self:UpdateEntity() | |
| end | |
| function StairSlab:UIExtendDown(parent, prop_id, ged) | |
| if slab_group_op_done(parent or {self}) then | |
| return | |
| end | |
| -- trace down from 'self' | |
| local first, last = self:TraceConnectedStairs(-1) | |
| -- check the spot for the next stair for a floor | |
| local obj = last or self | |
| local x, y, z = obj:GetGridCoords() | |
| local dx, dy, dz = obj:GetExitOffset() | |
| local floor = GetFloorSlab(x, y, z) | |
| if IsValid(floor) then | |
| print("Can't extend the stairs upward, floor tile is in the way") | |
| return | |
| end | |
| x, y, z = obj:GetPosXYZ() | |
| local stair = StairSlab:new({material = self.material, subvariant = self.subvariant}) | |
| stair:SetPosAngle(x - dx * sx, y - dy * sy, z - dz * sz, obj:GetAngle()) | |
| stair:EditorCallbackClone(self) | |
| stair:UpdateEntity() | |
| self:AlignObj() | |
| self:UpdateEntity() | |
| end | |
| function StairSlab:AlignObj(pos, angle) | |
| FloorAlignedObj.AlignObj(self, pos, angle) | |
| if self:GetGameFlags(const.gofPermanent) == 0 then | |
| return | |
| end | |
| local lp = rawget(self, "last_pos") | |
| local p = self:GetPos() | |
| if lp ~= p then | |
| rawset(self, "last_pos", p) | |
| local b = self:GetObjectBBox() | |
| if lp then | |
| local s = b:size() / 2 | |
| b = Extend(b, lp + s) | |
| b = Extend(b, lp - s) | |
| end | |
| ComputeSlabVisibilityInBox(b) | |
| end | |
| end | |
| DefineClass.SlabWallObject = { | |
| __parents = { "Slab", "WallAlignedObj" }, | |
| properties = { | |
| category = "Slabs", | |
| { id = "variant", name = "Variant", editor = "dropdownlist", items = PresetGroupCombo("SlabPreset", "SlabVariants"), default = "", no_edit = true }, | |
| { id = "width", name = "Width", editor = "number", min = 0, max = 3, default = 1, }, | |
| { id = "height", name = "Height", editor = "number", min = 1, max = 4, default = 3, }, | |
| { id = "subvariant", name = "Subvariant", editor = "number", default = 1, | |
| buttons = { | |
| { name = "Next", func = "CycleEntityBtn" }, | |
| },}, | |
| { id = "hide_with_wall", name = "Hide With Wall", editor = "bool", default = false }, | |
| { id = "owned_slabs", editor = "objects", default = false, no_edit = true }, | |
| { id = "forceInvulnerableBecauseOfGameRules", name = "Invulnerable", editor = "bool", default = false, help = "In context of destruction."}, | |
| { id = "colors" }, | |
| { id = "interior_attach_colors" }, | |
| { id = "exterior_attach_colors" }, | |
| { id = "indoor_material_1" }, | |
| { id = "indoor_material_2" }, | |
| }, | |
| entity = "Window_Colonial_Single_01", --ent that exists but is not a slab or common ent so it appear in new obj palette correctly | |
| material = "Planks", | |
| affected_walls = false, | |
| main_wall = false, | |
| last_snap_pos = false, | |
| room = false, | |
| owned_objs = false, | |
| invulnerable = false, | |
| colors_room_member = false, | |
| } | |
| SlabWallObject.GetSide = WallSlab.GetSide | |
| SlabWallObject.GetGridCoords = WallSlab.GetGridCoords | |
| SlabWallObject.IsSameLocation = WallSlab.IsSameLocation | |
| function WallSlab:GetAttachColors() | |
| local iclrs = self.interior_attach_colors or self.room and self.room.inner_colors | |
| local clrs = self:GetExteriorAttachColor() | |
| return iclrs, clrs | |
| end | |
| function SlabWallObject:GetMaterialType() | |
| --this gets the combat or obj material, not to be confused with slab material.. | |
| return self.material_type | |
| end | |
| function SlabWallObject:RefreshColors() | |
| if not self.is_destroyed then return end | |
| local clrs, iclrs | |
| local aw = self.affected_walls | |
| for i = 1, #(aw or "") do | |
| local w = aw[i] | |
| if w.invisible_reasons and not w.invisible_reasons["suppressed"] then | |
| iclrs, clrs = w:GetAttachColors() | |
| if w:GetAngle() ~= self:GetAngle() then | |
| local tmp = iclrs | |
| iclrs = clrs | |
| clrs = tmp | |
| end | |
| break | |
| end | |
| end | |
| if not clrs then | |
| clrs = self.colors or self:GetDefaultColor() | |
| end | |
| if not iclrs then | |
| iclrs = self.room and self.room.inner_colors | |
| end | |
| self:ForEachDestroyedAttach(function(v, self, clrs, iclrs) | |
| local c, ic = clrs, iclrs | |
| SetSlabColorHelper(v, rawget(v, "use_self_colors") and self or c or self) | |
| if c or ic then | |
| local atts = v:GetAttaches() | |
| for i = 1, #(atts or "") do | |
| local a = atts[i] | |
| if not rawget(a, "editor_ignore") then | |
| local angle = a:GetAttachAngle() | |
| if angle == 0 then | |
| if c then | |
| SetSlabColorHelper(a, c) | |
| end | |
| else | |
| if ic then | |
| SetSlabColorHelper(a, ic) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end, self, clrs, iclrs) | |
| end | |
| function SlabWallObject:SetStateSavedOnMap(val) | |
| self:SetState(val) | |
| end | |
| function SlabWallObject:GetStateSavedOnMap() | |
| return self:GetStateText() | |
| end | |
| local function InsertInParentContainersHelper(self, room, side) | |
| assert(side) | |
| if self:IsDoor() then | |
| room.spawned_doors = room.spawned_doors or {} | |
| room.spawned_doors[side] = room.spawned_doors[side] or {} | |
| table.insert(room.spawned_doors[side], self) | |
| else | |
| room.spawned_windows = room.spawned_windows or {} | |
| room.spawned_windows[side] = room.spawned_windows[side] or {} | |
| table.insert(room.spawned_windows[side], self) | |
| end | |
| end | |
| local function RemoveFromParentContainerHelper(self, room, side) | |
| if self:IsDoor() then | |
| local spawned_doors = room.spawned_doors | |
| if spawned_doors and spawned_doors[side] then | |
| table.remove_entry(spawned_doors[side], self) | |
| if #spawned_doors[side] <= 0 then | |
| spawned_doors[side] = nil | |
| end | |
| end | |
| else | |
| local spawned_windows = room.spawned_windows | |
| if spawned_windows and spawned_windows[side] then | |
| table.remove_entry(spawned_windows[side], self) | |
| if #spawned_windows[side] == 0 then | |
| spawned_windows[side] = nil | |
| end | |
| end | |
| end | |
| end | |
| function SlabWallObject:FixNoRoom() | |
| if self.room then return end | |
| if not self.main_wall then return end | |
| local room = self.main_wall.room | |
| local side = self.main_wall.side | |
| self.room = room | |
| self.side = side | |
| if room and side then | |
| InsertInParentContainersHelper(self, room, side) | |
| end | |
| end | |
| function SlabWallObject:Setside(newSide) | |
| if self.side == newSide then return end | |
| if self.room then | |
| RemoveFromParentContainerHelper(self, self.room, self.side) | |
| if newSide then | |
| InsertInParentContainersHelper(self, self.room, newSide) | |
| end | |
| end | |
| self.side = newSide | |
| end | |
| function SlabWallObject:ChangeRoom(newRoom) | |
| if self.room == newRoom then return end | |
| self.restriction_box = false | |
| if self.room then | |
| RemoveFromParentContainerHelper(self, self.room, self.side) | |
| end | |
| if newRoom and not EditorCursorObjs[self] then | |
| XEditorUndo:StartTracking({newRoom}, not "created", "omit_children") -- let XEditorUndo store the "old" data for the room | |
| end | |
| self.room = newRoom | |
| if newRoom then | |
| InsertInParentContainersHelper(self, newRoom, self.side) | |
| end | |
| end | |
| function SlabWallObject:GetWorldBBox() | |
| return GetSlabWorldBBox(self:GetPos(), self.width, self.height, self:GetAngle()) | |
| end | |
| function GetSlabWorldBBox(pos, width, height, angle) | |
| local x, y, z = pos:xyz() | |
| local b | |
| local minAdd = width > 1 and voxelSizeX or 0 | |
| local maxAdd = width == 3 and voxelSizeX or 0 | |
| local tenCm = guim / 10 | |
| if angle == 0 then --e | |
| return box(x - tenCm, y - halfVoxelSizeX - minAdd, z, x + tenCm, y + halfVoxelSizeX + maxAdd, z + height * voxelSizeZ) | |
| elseif angle == 180 * 60 then --w | |
| return box(x - tenCm, y - halfVoxelSizeX - maxAdd, z, x + tenCm, y + halfVoxelSizeX + minAdd, z + height * voxelSizeZ) | |
| elseif angle == 90 * 60 then --s | |
| return box(x - halfVoxelSizeX - maxAdd, y - tenCm, z, x + halfVoxelSizeX + minAdd, y + tenCm, z + height * voxelSizeZ) | |
| else --n | |
| return box(x - halfVoxelSizeX - minAdd, y - tenCm, z, x + halfVoxelSizeX + maxAdd, y + tenCm, z + height * voxelSizeZ) | |
| end | |
| end | |
| function IntersectWallObjs(obj, newPos, width, height, angle) | |
| local ret = false | |
| local b = GetSlabWorldBBox(newPos or obj:GetPos(), width or obj.width, height or obj.height, angle or obj:GetAngle()) | |
| angle = angle or obj:GetAngle() | |
| MapForEach(b:grow(voxelSizeX * 2, voxelSizeX * 2, voxelSizeZ * 3), "SlabWallObject", nil, nil, gofPermanent, function(o, obj, angle) | |
| if o ~= obj then | |
| local a = o:GetAngle() | |
| if a == angle or abs(a - angle) == 180 * 60 then --ignore perpendicular objs | |
| local hisBB = o:GetObjectBBox() | |
| local ib = IntersectRects(b, hisBB) | |
| if ib:IsValid() and (Max(ib:sizex(), ib:sizey()) >= halfVoxelSizeX / 2 and ib:sizez() >= halfVoxelSizeZ / 2) then | |
| --DbgAddBox(b) | |
| --DbgAddBox(hisBB) | |
| ret = true | |
| return "break" | |
| end | |
| end | |
| end | |
| end, obj, angle) | |
| return ret | |
| end | |
| SlabWallObject.MirroringFromRoom = empty_func | |
| function SlabWallObject:CanMirror() | |
| return false | |
| end | |
| function SlabWallObject:EditorCallbackMove() | |
| self:AlignObj() | |
| end | |
| function SlabWallObject:AlignObj(pos, angle) | |
| local x, y, z | |
| if pos then | |
| x, y, z, angle = WallWorldToVoxel(pos:x(), pos:y(), pos:z() or iz, angle or self:GetAngle()) | |
| else | |
| x, y, z, angle = WallWorldToVoxel(self) | |
| end | |
| x, y, z = WallVoxelToWorld(x, y, z, angle) | |
| local oldPos = self:GetPos() | |
| local newPos = point(x, y, z) | |
| if not newPos:z() then | |
| newPos = newPos:SetZ(snapZCeil(terrain.GetHeight(newPos:xy()))) | |
| end | |
| if pos then | |
| --this tests for collision with other wallobjs | |
| if oldPos:IsValid() and oldPos ~= newPos then | |
| if IntersectWallObjs(self, newPos, self.width, self.height, angle) then | |
| --print("WallObject could not move due to collision with other wall obj!", self.handle, newPos, oldPos) | |
| newPos = oldPos | |
| end | |
| end | |
| end | |
| self:SetPosAngle(newPos:x(), newPos:y(), newPos:z() or const.InvalidZ, angle) | |
| self:PostEntityUpdate() -- updates self.main_wall | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| if is_permanent and self.main_wall then | |
| self:Setside(self.main_wall.side) | |
| self:ChangeRoom(self.main_wall.room) | |
| else | |
| self:Setside(false) | |
| self:ChangeRoom(false) | |
| end | |
| end | |
| -- depending on height/width | |
| local SlabWallObject_BaseNames = { "WindowVent", "Window", "Door", "TallDoor" } | |
| local SlabWallObject_BaseNames_Window = { "WindowVent", "Window", "WindowBig", "TallWindow" } | |
| local SlabWallObject_WidthNames = { "Single", "Double", "Triple", "Quadruple", [0] = "Small" } | |
| function SlabWallObjectName(material, height, width, variant, isDoor) | |
| local base = isDoor ~= nil and not isDoor and SlabWallObject_BaseNames_Window[height] or SlabWallObject_BaseNames[height] or "" | |
| if variant then | |
| local v = variant <= 0 and 1 or variant | |
| local str = variant < 10 and "%s_%s_%s_0%s" or "%s_%s_%s_%s" | |
| return string.format(str, base, material, | |
| SlabWallObject_WidthNames[width] or "", tostring(v)) | |
| else | |
| return string.format("%s_%s_%s", base, | |
| material, SlabWallObject_WidthNames[width] or "") | |
| end | |
| end | |
| function SlabWallObject:EditorCallbackPlaceCursor() | |
| -- init properties from entity | |
| if IsValidEntity(self.class) then | |
| local e = self.class | |
| local strs = string.split(e, "_") | |
| local base = strs[1] | |
| local idxW = table.find(SlabWallObject_BaseNames_Window, base) | |
| local idxD = table.find(SlabWallObject_BaseNames, base) | |
| local isDoor = false | |
| if idxW then | |
| --window | |
| self.height = idxW | |
| if self:IsDoor() then | |
| assert(false, "Please fix " .. self.class .. ". It is named as a window but its parent class a door!") | |
| end | |
| else | |
| --door | |
| self.height = idxD | |
| if not self:IsDoor() then | |
| --zulu door SlabWallDoor | |
| --bacon door SlabDoor | |
| --setmetatable(self, g_Classes.SlabDoor or g_Classes.SlabWallDoor) --this wont work, cuz they inherit more then one thing. | |
| assert(false, "Please fix " .. self.class .. ". It is named as a door but its parent class is not a door!") | |
| end | |
| end | |
| self.material = strs[2] | |
| local w = table.find(SlabWallObject_WidthNames, strs[3]) or SlabWallObject_WidthNames[0] == strs[3] and 0 | |
| assert(w) | |
| self.width = w | |
| self.subvariant = tonumber(strs[4]) | |
| self:UpdateEntity() | |
| assert(self:GetEntity() == e, string.format("Failed to guess props from ent for slab wall obj, ent %s, picked ent %s", e, self:GetEntity())) | |
| end | |
| end | |
| function SlabWallObject:EditorCallbackPlace(reason) | |
| Slab.EditorCallbackPlace(self, reason) | |
| if reason ~= "undo" then | |
| self:EditorCallbackPlaceCursor() | |
| self:FixNoRoom() | |
| end | |
| end | |
| function SlabWallObject:HasEntityForSubvariant(var) | |
| local ret = SlabWallObjectName(self.material, self.height, self.width, var, self:IsDoor()) | |
| return IsValidEntity(ret) | |
| end | |
| function SlabWallObject:HasEntityForHeight(height) | |
| local ret | |
| if self.subvariant then | |
| ret = SlabWallObjectName(self.material, height, self.width, self.subvariant, self:IsDoor()) | |
| if IsValidEntity(ret) then | |
| return true | |
| end | |
| end | |
| return IsValidEntity(SlabWallObjectName(self.material, height, self.width, nil, self:IsDoor())), ret | |
| end | |
| function SlabWallObject:HasEntityForWidth(width) | |
| local ret | |
| if self.subvariant then | |
| ret = SlabWallObjectName(self.material, self.height, width, self.subvariant, self:IsDoor()) | |
| if IsValidEntity(ret) then | |
| return true | |
| end | |
| end | |
| return IsValidEntity(SlabWallObjectName(self.material, self.height, width, nil, self:IsDoor())), ret | |
| end | |
| function SlabWallObject:ComposeEntityName() | |
| if self.subvariant then | |
| local ret = SlabWallObjectName(self.material, self.height, self.width, self.subvariant, self:IsDoor()) | |
| if IsValidEntity(ret) then | |
| return ret | |
| else | |
| self:ReportMissingSlabEntity(ret) | |
| end | |
| end | |
| return SlabWallObjectName(self.material, self.height, self.width, nil, self:IsDoor()) | |
| end | |
| function SlabWallObject:EditorCallbackDelete(reason) | |
| --Slab.EditorCallbackDelete(self, reason) --this will do nothing | |
| if IsValid(self.room) then | |
| self.room:OnWallObjDeletedOutsideOfGedRoomEditor(self) --this will remove from parent containers | |
| end | |
| end | |
| function SlabWallObject:Done() | |
| self:RestoreAffectedSlabs() | |
| DoneObjects(self.owned_slabs) | |
| self.owned_slabs = false | |
| if self.owned_objs then | |
| DoneObjects(self.owned_objs) | |
| end | |
| self.owned_objs = false | |
| if not self.room or not self.side then return end | |
| local isDoor = self:IsDoor() | |
| local c | |
| if isDoor then | |
| c = self.room.spawned_doors and self.room.spawned_doors[self.side] | |
| else | |
| c = self.room.spawned_windows and self.room.spawned_windows[self.side] | |
| end | |
| if not c then return end | |
| table.remove_entry(c, self) | |
| end | |
| function SlabWallObject:ForEachAffectedWall(callback, ...) | |
| for _, wall in ipairs(self.affected_walls or empty_table) do | |
| if IsValid(wall) and wall.wall_obj == self then | |
| local func = type(callback) == "function" and callback or wall[callback] | |
| func(wall, ...) | |
| end | |
| end | |
| end | |
| function SlabWallObject:RestoreAffectedSlabs() | |
| SuspendPassEdits("SlabWallObject:RestoreAffectedSlabs") | |
| for _, wall in ipairs(self.affected_walls or empty_table) do | |
| if IsValid(wall) and wall.wall_obj == self then | |
| wall:SetWallObj() | |
| end | |
| end | |
| self.affected_walls = nil | |
| self.main_wall = nil | |
| ResumePassEdits("SlabWallObject:RestoreAffectedSlabs") | |
| end | |
| function SlabWallObject:SetProperty(id, value) | |
| Slab.SetProperty(self, id, value) | |
| if IsChangingMap() then return end | |
| if id == "width" or id == "height" then | |
| self:DelayedUpdateEntity() | |
| end | |
| end | |
| function SlabWallObject:PostLoad() | |
| self:DelayedUpdateEntity() | |
| end | |
| function SlabWallObject:UpdateEntity() | |
| if self.is_destroyed then | |
| if self:UpdateDestroyedState() then | |
| return | |
| end | |
| end | |
| self:DestroyAttaches() | |
| Slab.UpdateEntity(self) | |
| AutoAttachObjects(self) | |
| self:PostEntityUpdate() | |
| self:RefreshEntityState() | |
| self:RefreshClass() | |
| end | |
| function SlabWallObject:CycleEntity(delta) | |
| EditorSubVariantObject.CycleEntity(self, delta) | |
| self:RefreshEntityState() | |
| self:RefreshClass() | |
| self:PostEntityUpdate() | |
| end | |
| function SlabWallObject:RefreshClass() | |
| --SlabWallObject is a cls that generates ent from given props | |
| --SlabWallWindow and SlabWallWindowBroken are functional classes, where ents inherit them | |
| local e = self:GetEntity() | |
| if IsValidEntity(e) then | |
| local cls = g_Classes[e] | |
| if cls and IsKindOf(cls, "SlabWallObject") then | |
| setmetatable(self, cls) | |
| end | |
| end | |
| end | |
| function DbgChangeClassOfAllWindows() | |
| CreateRealTimeThread(function() | |
| ForEachMap(ListMaps(), function() | |
| MapForEach("map", "SlabWallObject", function(obj) | |
| obj:RefreshClass() | |
| end) | |
| SaveMap("no backup") | |
| end) | |
| end) | |
| end | |
| function SlabWallObject:RefreshEntityState() | |
| --cb for lockpickable | |
| end | |
| function SlabWallObject:PostEntityUpdate() | |
| self:UpdateAffectedWalls() | |
| self:UpdateManagedSlabs() | |
| self:UpdateManagedObj() | |
| end | |
| function SlabWallObject:IsWindow() | |
| return not self:IsDoor() | |
| end | |
| function SlabWallObject:IsDoor() | |
| return IsKindOfClasses(self, "SlabWallDoorDecor", "SlabWallDoor") or false | |
| end | |
| function SlabWallObject:ForEachSlabPos(func, ...) | |
| local width = self.width | |
| local height = self.height | |
| if width <= 0 or height <= 0 then return end | |
| local x, y, z = self:GetPosXYZ() | |
| if not z then | |
| z = terrain.GetHeight(x, y) | |
| end | |
| local side = self:GetSide() | |
| for w = 1, width do | |
| for h = 1, self.height do | |
| local tx, ty, tz, wf | |
| tz = z + (h - 1) * const.SlabSizeZ | |
| wf = (w - width/2 - 1) | |
| if side == "E" then -- x = const | |
| tx = x | |
| ty = y + wf * const.SlabSizeY | |
| elseif side == "W" then -- x = const | |
| tx = x | |
| ty = y - wf * const.SlabSizeY | |
| elseif side == "N" then -- y = const | |
| tx = x + wf * const.SlabSizeX | |
| ty = y | |
| else -- y = const | |
| tx = x - wf * const.SlabSizeX | |
| ty = y | |
| end | |
| func(tx, ty, tz, ...) | |
| end | |
| end | |
| end | |
| function SlabWallObject:UpdateAffectedWalls() | |
| SuspendPassEdits("SlabWallObject:UpdateAffectedWalls") | |
| local old_aw = self.affected_walls or empty_table | |
| local new_aw = {} | |
| self.affected_walls = new_aw | |
| self.main_wall = nil | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| local gameFlags = is_permanent and gofPermanent or nil | |
| local x, y, z = self:GetPosXYZ() | |
| if not z then | |
| z = terrain.GetHeight(x, y) | |
| end | |
| local side = self:GetSide() | |
| local width = Max(self.width, 1) | |
| for w = 1, width do | |
| for h = 1, self.height do | |
| local tx, ty, tz, wf | |
| tz = z + (h - 1) * const.SlabSizeZ | |
| wf = (w - width/2 - 1) | |
| if side == "E" then -- x = const | |
| tx = x | |
| ty = y + wf * const.SlabSizeY | |
| elseif side == "W" then -- x = const | |
| tx = x | |
| ty = y - wf * const.SlabSizeY | |
| elseif side == "N" then -- y = const | |
| tx = x + wf * const.SlabSizeX | |
| ty = y | |
| else -- y = const | |
| tx = x - wf * const.SlabSizeX | |
| ty = y | |
| end | |
| local is_main_pos = tx == x and ty == y and tz == z | |
| local main_slab_candidates = is_main_pos and {} | |
| MapForEach(tx, ty, tz, 0, "WallSlab", nil, nil, gameFlags, function(slab, self, is_main_pos, is_permanent) | |
| local wall_obj = slab.wall_obj | |
| if IsValid(wall_obj) and wall_obj ~= self and not new_aw[slab] then | |
| return | |
| end | |
| if self.owned_slabs and table.find(self.owned_slabs, slab) then | |
| return | |
| end | |
| if wall_obj ~= self and (is_permanent or slab:GetGameFlags(gofPermanent) == 0) then | |
| -- non permanent wall objs should not affect permanent walls | |
| slab:SetWallObj(self) | |
| end | |
| new_aw[slab] = true | |
| table.insert(new_aw, slab) | |
| if is_main_pos then | |
| table.insert(main_slab_candidates, slab) | |
| end | |
| end, self, is_main_pos, is_permanent) | |
| if is_main_pos and #main_slab_candidates > 0 then | |
| --first non roof slab, if any | |
| self:PickMainWall(main_slab_candidates) | |
| end | |
| end | |
| end | |
| if not self.main_wall and #new_aw > 0 then | |
| --this makes it so that main_wall wont be in the same pos as the window/door, idk what that will break.. | |
| --this is a fix for a fringe case where the slab at the door/window pos is deleted | |
| self:PickMainWall(new_aw) | |
| end | |
| for _, slab in ipairs(old_aw) do | |
| if IsValid(slab) and slab.wall_obj == self and not new_aw[slab] then | |
| slab:SetWallObj() | |
| end | |
| end | |
| ResumePassEdits("SlabWallObject:UpdateAffectedWalls") | |
| return old_aw | |
| end | |
| function SlabWallObject:PickMainWall(t) | |
| local iHaveRoom = not not self.room | |
| local roofCandidate = false | |
| local nonRoofCandidateDiffRoom = false | |
| local nonRoofRotatedCandidate = false | |
| self.main_wall = false | |
| for i = 1, #t do | |
| local s = t[i] | |
| local slabHasRoom = not not s.room | |
| local anyRoomMissing = (not slabHasRoom or not iHaveRoom) | |
| local slaba = s:GetAngle() | |
| local selfa = self:GetAngle() | |
| local angleIsTheSame = slaba == selfa | |
| local angleIsReveresed = abs(slaba - selfa) == 180*60 | |
| if (slabHasRoom and s.room == self.room or | |
| not iHaveRoom and | |
| (angleIsTheSame or angleIsReveresed)) and | |
| (anyRoomMissing or s.room.being_placed == self.room.being_placed) then | |
| if not IsKindOf(s, "RoofWallSlab") then | |
| if angleIsReveresed then | |
| nonRoofRotatedCandidate = s | |
| else | |
| self.main_wall = s | |
| return | |
| end | |
| elseif not roofCandidate then | |
| roofCandidate = s | |
| end | |
| elseif not anyRoomMissing and s.room ~= self.room and angleIsTheSame then | |
| nonRoofCandidateDiffRoom = s | |
| end | |
| end | |
| self.main_wall = self.main_wall or nonRoofCandidateDiffRoom or nonRoofRotatedCandidate or roofCandidate | |
| end | |
| function SlabWallObject:DestroyAttaches() | |
| if self.is_destroyed and string.find(GetStack(2), "SetAutoAttachMode") then | |
| --todo: hack | |
| return | |
| end | |
| Slab.DestroyAttaches(self, function(o, doNotDelete) | |
| if IsKindOf(self, "EditorTextObject") and o == self.editor_text_obj then return end | |
| return not doNotDelete or not IsKindOf(o, doNotDelete.class) | |
| end, g_Classes.ConstructionSite) | |
| end | |
| function SlabWallObject:SetManagedSlabsShadowOnly(val, clear_contour) | |
| for i = 1, #(self.owned_slabs or "") do | |
| local slab = self.owned_slabs[i] | |
| if slab then --can be saved as false if deleted by user | |
| slab:SetShadowOnly(val) | |
| if clear_contour then | |
| slab:ClearHierarchyGameFlags(const.gofContourInner) | |
| end | |
| end | |
| end | |
| end | |
| function OnMsg.EditorCallback(id, objs) | |
| if id == "EditorCallbackDelete" then | |
| for i = 1, #objs do | |
| local o = objs[i] | |
| if IsKindOf(o, "WallSlab") and o.always_visible and o:GetClipPlane() ~= 0 then | |
| local x, y, z = o:GetPosXYZ() | |
| local swo = MapGetFirst(x, y, z, 0, "SlabWallObject") | |
| if swo and swo.owned_slabs then | |
| local idx = table.find(swo.owned_slabs, o) | |
| if idx then | |
| swo.owned_slabs[idx] = false | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| function SlabWallObject:UpdateManagedSlabs() | |
| if self.width == 0 then | |
| --small window | |
| local main = self.main_wall | |
| --clipboard cpy paste fix, owned_slabs contains empty tables | |
| for i = 1, #(self.owned_slabs or "") do | |
| local s = self.owned_slabs[i] | |
| if not IsValid(s) then | |
| self.owned_slabs[i] = nil | |
| end | |
| end | |
| if not main or (main:GetAngle() ~= self:GetAngle() or self.room and self.room ~= main.room) then | |
| self:UpdateAffectedWalls() | |
| main = self.main_wall | |
| if not main then | |
| DoneObjects(self.owned_slabs) | |
| self.owned_slabs = false | |
| return | |
| end | |
| end | |
| if self.owned_slabs and self.owned_slabs[1] == false then | |
| --happens after copy | |
| DoneObjects(self.owned_slabs) | |
| self.owned_slabs = false | |
| end | |
| if not self.owned_slabs then | |
| self.owned_slabs = {} | |
| local s = WallSlab:new({always_visible = true, forceInvulnerableBecauseOfGameRules = false}) | |
| table.insert(self.owned_slabs, s) | |
| s = WallSlab:new({always_visible = true, forceInvulnerableBecauseOfGameRules = false}) | |
| table.insert(self.owned_slabs, s) | |
| end | |
| local bb = self:GetObjectBBox() | |
| local isVerticalAligned = self:GetAngle() % (180 * 60) == 0 | |
| local mx, my, mz = main:GetPosXYZ() | |
| local ma = main:GetAngle() | |
| local destroyed = self.is_destroyed | |
| for i = 1, 2 do | |
| local s = self.owned_slabs[i] | |
| if IsValid(s) then --this is false when deleted manually and saved | |
| s:SetPosAngle(mx, my, mz, ma) | |
| s.material = main.material | |
| s.variant = main.variant | |
| s.indoor_material_1 = main.indoor_material_1 | |
| s.indoor_material_2 = main.indoor_material_2 | |
| s.subvariant = main.subvariant | |
| s:UpdateEntity() | |
| s:UpdateVariantEntities() | |
| local room = main.room | |
| s:SetColorModifier(main:GetColorModifier()) | |
| s:Setcolors(main.colors or room and room.outer_colors) | |
| s:Setinterior_attach_colors(main.interior_attach_colors or room and room.inner_colors) | |
| s:Setexterior_attach_colors(main.exterior_attach_colors) | |
| s:Setexterior_attach_colors_from_nbr(main.exterior_attach_colors_from_nbr) | |
| s:SetWarped(main:GetWarped()) | |
| collision.SetAllowedMask(s, 0) | |
| --save fixup, default state of slabs is invulnerable, so all small window helper slabs are now all saved as such.. | |
| s.forceInvulnerableBecauseOfGameRules = false --TODO:remove | |
| s.invulnerable = false --TODO:remove | |
| if destroyed ~= s.is_destroyed then | |
| if destroyed then | |
| s:Destroy() | |
| else | |
| s:Repair() | |
| end | |
| end | |
| local p1, p2, p3 | |
| if i == 2 then | |
| p3 = bb:min() | |
| p1 = p3 + point(0, 0, bb:sizez()) | |
| if isVerticalAligned then | |
| p2 = p1 + point(bb:sizex(), 0, 0) | |
| else | |
| p2 = p1 + point(0, bb:sizey(), 0) | |
| end | |
| else | |
| p1 = bb:max() | |
| p3 = p1 - point(0, 0, bb:sizez()) | |
| if isVerticalAligned then | |
| p2 = p1 - point(bb:sizex(), 0, 0) | |
| else | |
| p2 = p1 - point(0, bb:sizey(), 0) | |
| end | |
| end | |
| if isVerticalAligned then | |
| --east/west needs inversion | |
| p1, p3 = p3, p1 | |
| end | |
| --DbgAddVector(p1, p2 - p1) | |
| --DbgAddVector(p2, p3 - p2) | |
| --DbgAddVector(p3, p1 - p3) | |
| s:SetClipPlane(PlaneFromPoints( p1, p2, p3 )) | |
| else | |
| self.owned_slabs[i] = false | |
| end | |
| end | |
| else | |
| if self.owned_slabs then | |
| DoneObjects(self.owned_slabs) | |
| end | |
| self.owned_slabs = false | |
| end | |
| end | |
| local function TryGetARepresentativeWall(self, wall) | |
| local mw | |
| if not mw then | |
| --mw = MapGetFirst(self, 0, "WallSlab", nil, const.efVisible) | |
| local aw = self.affected_walls | |
| for i = 1, #(aw or "") do | |
| local w = aw[i] | |
| if w.invisible_reasons and not w.invisible_reasons["suppressed"] then | |
| mw = w | |
| break | |
| end | |
| end | |
| end | |
| return mw or self.main_wall | |
| end | |
| local function TryFigureOutInteriorMaterialOnTheExteriorSide(self) | |
| local m, c, _ | |
| local mw = TryGetARepresentativeWall(self) | |
| if mw then | |
| if self:GetAngle() == mw:GetAngle() then | |
| m = mw.indoor_material_2 | |
| _, c = mw:GetAttachColors() | |
| else | |
| m = mw.indoor_material_1 | |
| c = mw:GetAttachColors() | |
| end | |
| else | |
| m = self.material | |
| end | |
| return m ~= noneWallMat and m or false, c or self.colors or self:GetDefaultColor() | |
| end | |
| local function TryFigureOutInteriorMaterial(self) | |
| local m, c, _ | |
| local mw = TryGetARepresentativeWall(self) | |
| if mw then | |
| if self:GetAngle() == mw:GetAngle() then | |
| m = mw.indoor_material_1 | |
| c = mw:GetAttachColors() | |
| else | |
| m = mw.indoor_material_2 | |
| _, c = mw:GetAttachColors() | |
| end | |
| elseif self.room then | |
| m = self.room.inner_wall_mat | |
| c = self.room.inner_colors | |
| end | |
| return m ~= noneWallMat and m or false, c | |
| end | |
| local function GetAttchEntName(e, material) | |
| if material then | |
| local e = string.format("%s_Int_%s", e, material) | |
| if IsValidEntity(e) then | |
| return e | |
| end | |
| end | |
| return string.format("%s_Int", e) | |
| end | |
| function SlabWallObject:UpdateManagedObj() | |
| --some windows are composed from more than one entity for kicks | |
| --it musn't be attached or the colors don't work. | |
| if not self.is_destroyed then | |
| local function setupObj(ea, color, idx, si) | |
| local t = self.owned_objs | |
| if not t then | |
| t = {} | |
| for i = 1, idx - 1 do | |
| t[i] = false | |
| end | |
| self.owned_objs = t | |
| end | |
| if not IsValid(t[idx]) then | |
| t[idx] = PlaceObject("Object") | |
| end | |
| local o = t[idx] | |
| if o:GetEntity() ~= ea then | |
| o:ChangeEntity(ea) | |
| end | |
| o:SetPos(self:GetSpotPos(si)) | |
| o:SetAngle(self:GetSpotAngle2D(si)) | |
| SetSlabColorHelper(o, color) | |
| end | |
| local function manageObj(spotName, idx, mat_func) | |
| local added = false | |
| if self:HasSpot(spotName) then | |
| local si = self:GetSpotBeginIndex(spotName) | |
| local e = self:GetEntity() | |
| local material, color = mat_func(self) | |
| local ea = GetAttchEntName(e, material) | |
| if IsValidEntity(ea) then | |
| setupObj(ea, color, idx, si) | |
| added = true | |
| else | |
| print("SlabWallObject has a " .. spotName .. " spot defined but no ent found to place there [" .. ea .. "]") | |
| end | |
| end | |
| if not added then | |
| local t = self.owned_objs | |
| if t and IsValid(t[idx]) then | |
| DoneObject(t[idx]) | |
| t[idx] = false | |
| end | |
| end | |
| end | |
| manageObj("Interior1", 1, TryFigureOutInteriorMaterial) | |
| manageObj("Interior2", 2, TryFigureOutInteriorMaterialOnTheExteriorSide) | |
| local t = self.owned_objs | |
| if t and not IsValid(t[1]) and not IsValid(t[2]) then | |
| self.owned_objs = false | |
| end | |
| else | |
| if self.owned_objs then | |
| DoneObjects(self.owned_objs) | |
| end | |
| self.owned_objs = false | |
| end | |
| end | |
| function SlabWallObject:SetShadowOnly(val, ...) | |
| Slab.SetShadowOnly(self, val, ...) | |
| for _, o in ipairs(self.owned_objs or empty_table) do | |
| o:SetShadowOnly(val, ...) | |
| end | |
| end | |
| function SlabWallObject:GetPlaceClass() | |
| return self | |
| end | |
| function SlabWallObject:GetError() | |
| local lst = MapGet(self, 0, "SlabWallObject") | |
| if #lst > 1 then | |
| return "Stacked doors/windows!" | |
| end | |
| self:ForEachSlabPos(function(x, y, z) | |
| local slb = MapGetFirst(x, y, z, 0, "WallSlab") | |
| if slb then | |
| if slb.wall_obj ~= self then | |
| return "Stacked doors/windows!" | |
| end | |
| end | |
| end) | |
| end | |
| DefineClass.SlabWallDoorDecor = { --SlabWallDoor carries logic in zulu, SlabWallDoorDecor == SlabWallObject but is considered a door by IsDoor | |
| __parents = { "SlabWallObject" }, | |
| fx_actor_class = "Door", | |
| } | |
| DefineClass("SlabWallDoor", "SlabWallDoorDecor") --door with decals | |
| DefineClass("SlabWallWindow", "SlabWallObject") --window with decals | |
| DefineClass("SlabWallWindowBroken", "SlabWallObject") | |
| function GetFloorAlignedObj(gx, gy, gz, class) | |
| local x, y, z = VoxelToWorld(gx, gy, gz) | |
| return MapGetFirst(x, y, z, 0, class, nil, efVisible) | |
| end | |
| function GetWallAlignedObj(gx, gy, gz, dir, class) | |
| local x, y, z = WallVoxelToWorld(gx, gy, gz, dir) | |
| return MapGetFirst(x, y, z, 0, class, nil, efVisible) | |
| end | |
| function GetWallAlignedObjs(gx, gy, gz, dir, class) | |
| local x, y, z = WallVoxelToWorld(gx, gy, gz, dir) | |
| return MapGet(x, y, z, 0, class, nil, nil, gofPermanent) or empty_table | |
| end | |
| function GetFloorSlab(gx, gy, gz) | |
| return GetFloorAlignedObj(gx, gy, gz, "FloorSlab") | |
| end | |
| function GetWallSlab(gx, gy, gz, side) | |
| return GetWallAlignedObj(gx, gy, gz, side, "WallSlab") | |
| end | |
| function GetWallSlabs(gx, gy, gz, side) | |
| return GetWallAlignedObjs(gx, gy, gz, side, "WallSlab") | |
| end | |
| function GetStairSlab(gx, gy, gz) | |
| return GetFloorAlignedObj(gx, gy, gz, "StairSlab") | |
| end | |
| function EnumConnectedFloorSlabs(x, y, z, visited) | |
| local queue, objs = {}, {} | |
| visited = visited or {} | |
| local function push(x, y, z) | |
| local hash = slab_hash(x, y, z) | |
| if visited[hash] then return end | |
| visited[hash] = true | |
| local slab = GetFloorSlab(x, y, z) | |
| if not slab or visited[slab] then return end | |
| visited[slab] = true | |
| table.insert_unique(objs, slab) | |
| queue[#queue + 1] = { x = x, y = y, z = z } | |
| end | |
| push(x, y, z) | |
| while #queue > 0 do | |
| local loc = table.remove(queue) | |
| push(loc.x + 1, loc.y, loc.z) | |
| push(loc.x - 1, loc.y, loc.z) | |
| push(loc.x, loc.y + 1, loc.z) | |
| push(loc.x, loc.y - 1, loc.z) | |
| end | |
| return objs | |
| end | |
| function EnumConnectedWallSlabs(x, y, z, side, floor, enum_adjacent_sides, zdir, visited) | |
| local queue, objs = {}, {} | |
| visited = visited or {} | |
| zdir = zdir or 0 | |
| local function push(x, y, z, side) | |
| local hash = slab_hash(x, y, z, side) | |
| if visited[hash] then return end | |
| visited[hash] = true | |
| local slab = GetWallSlab(x, y, z, side) | |
| if not slab or (floor and slab.floor ~= floor) or visited[slab] then return end | |
| visited[slab] = true | |
| table.insert_unique(objs, slab) | |
| queue[#queue + 1] = { x = x, y = y, z = z, side = side } | |
| end | |
| push(x, y, z, side) | |
| while #queue > 0 do | |
| local loc = table.remove(queue) | |
| if zdir >= 0 then | |
| push(loc.x, loc.y, loc.z + 1, loc.side) | |
| end | |
| if zdir <= 0 then | |
| push(loc.x, loc.y, loc.z - 1, loc.side) | |
| end | |
| if loc.side == "E" or loc.side == "W" then -- x = const | |
| push(loc.x, loc.y + 1, loc.z, loc.side) | |
| push(loc.x, loc.y - 1, loc.z, loc.side) | |
| if enum_adjacent_sides then | |
| push(loc.x, loc.y, loc.z, "S") | |
| push(loc.x, loc.y, loc.z, "N") | |
| -- handle non-convex shapes | |
| push(loc.x + 1, loc.y + 1, loc.z, "N") | |
| push(loc.x - 1, loc.y + 1, loc.z, "N") | |
| push(loc.x + 1, loc.y - 1, loc.z, "S") | |
| push(loc.x - 1, loc.y - 1, loc.z, "S") | |
| end | |
| else -- y = const | |
| push(loc.x + 1, loc.y, loc.z, loc.side) | |
| push(loc.x - 1, loc.y, loc.z, loc.side) | |
| if enum_adjacent_sides then | |
| push(loc.x, loc.y, loc.z, "E") | |
| push(loc.x, loc.y, loc.z, "W") | |
| -- handle non-convex shapes | |
| push(loc.x + 1, loc.y + 1, loc.z, "W") | |
| push(loc.x + 1, loc.y - 1, loc.z, "W") | |
| push(loc.x - 1, loc.y + 1, loc.z, "E") | |
| push(loc.x - 1, loc.y - 1, loc.z, "E") | |
| end | |
| end | |
| end | |
| return objs | |
| end | |
| function EnumConnectedStairSlabs(x, y, z, zdir, visited) | |
| local stair = GetStairSlab(x, y, z) | |
| local objs = {} | |
| visited = visited or {} | |
| zdir = zdir or 0 | |
| if stair then | |
| objs[1] = stair | |
| visited[stair] = true | |
| local first, last, all | |
| if zdir >= 0 then | |
| first, last, all = stair:TraceConnectedStairs(1) | |
| table.iappend(objs, all) | |
| for _, obj in ipairs(all) do | |
| visited[obj] = true | |
| end | |
| end | |
| if zdir <= 0 then | |
| first, last, all = stair:TraceConnectedStairs(-1) | |
| table.iappend(objs, all) | |
| for _, obj in ipairs(all) do | |
| visited[obj] = true | |
| end | |
| end | |
| end | |
| return objs | |
| end | |
| function FindConnectedWallSlab(obj) | |
| if IsKindOf(obj, "WallSlab") then | |
| return obj | |
| elseif IsKindOf(obj, "SlabWallObject") then | |
| return obj.main_wall | |
| elseif IsKindOf(obj, "FloorSlab") then | |
| local x, y, z = obj:GetGridCoords() | |
| local tiles = EnumConnectedFloorSlabs(x, y, z) | |
| for _, tile in ipairs(tiles or empty_table) do | |
| x, y, z = tile:GetGridCoords() | |
| for _, side in ipairs(slab_sides) do | |
| local slab = GetWallSlab(x, y, z, side) | |
| if IsValid(slab) then | |
| return slab | |
| end | |
| end | |
| end | |
| end | |
| end | |
| function FindConnectedFloorSlab(obj) | |
| if IsKindOf(obj, "FloorSlab") then | |
| return obj | |
| end | |
| if IsKindOf(obj, "SlabWallObject") then | |
| obj = obj.main_wall | |
| end | |
| if IsKindOf(obj, "WallSlab") then | |
| local x, y, z = obj:GetGridCoords() | |
| local walls = EnumConnectedWallSlabs(x, y, z, obj:GetSide(), obj.floor) | |
| for _, wall in ipairs(walls) do | |
| x, y, z = wall:GetGridCoords() | |
| local slab = GetFloorSlab(x, y, z) | |
| if IsValid(slab) and slab.floor == obj.floor then | |
| return slab | |
| end | |
| end | |
| end | |
| end | |
| function SlabsPushUp(gx, gy, gz, visited) | |
| visited = visited or {} | |
| local walls, floors = {}, {} | |
| local objs | |
| -- start by enumerating all connected slabs in the given grid coords | |
| floors = EnumConnectedFloorSlabs(gx, gy, gz, visited) | |
| for _, side in ipairs(slab_sides) do | |
| objs = EnumConnectedWallSlabs(gx, gy, gz, side, false, "enum adjacent", 1, visited) | |
| if #objs > 0 then | |
| table.iappend(walls, objs) | |
| end | |
| end | |
| -- enumerate the whole connected structure above | |
| local iwall, ifloor = 1, 1 | |
| while iwall <= #walls or ifloor <= #floors do | |
| if iwall <= #walls then | |
| local x, y, z = walls[iwall]:GetGridCoords() | |
| objs = EnumConnectedWallSlabs(x, y, z, walls[iwall]:GetSide(), false, "enum adjacent", 1, visited) | |
| if #objs > 0 then | |
| table.iappend(walls, objs) | |
| end | |
| objs = EnumConnectedFloorSlabs(x, y, z, visited) | |
| if #objs > 0 then | |
| table.iappend(floors, objs) | |
| end | |
| iwall = iwall + 1 | |
| end | |
| if ifloor <= #floors then | |
| -- no need to check for other floors, they would be enumerated already | |
| local x, y, z = floors[ifloor]:GetGridCoords() | |
| for _, side in ipairs(slab_sides) do | |
| objs = EnumConnectedWallSlabs(x, y, z, side, false, "enum adjacent", 1, visited) | |
| if #objs > 0 then | |
| table.iappend(walls, objs) | |
| end | |
| end | |
| ifloor = ifloor + 1 | |
| end | |
| end | |
| -- push up all enumerated objects by sz | |
| for _, obj in ipairs(floors) do | |
| local x, y, z = obj:GetPosXYZ() | |
| local gx, gy, gz = obj:GetGridCoords() | |
| obj:SetPos(x, y, z + sz) | |
| local stairs = EnumConnectedStairSlabs(gx, gy, gz) | |
| for i, stair in ipairs(stairs) do | |
| x, y, z = stair:GetPosXYZ() | |
| stair:SetPos(x, y, z + sz) | |
| end | |
| end | |
| for _, obj in ipairs(walls) do | |
| local x, y, z = obj:GetPosXYZ() | |
| obj:SetPos(x, y, z + sz) | |
| if IsValid(obj.wall_obj) and obj.wall_obj.main_wall == obj then | |
| obj.wall_obj:SetPos(x, y, z + sz) | |
| end | |
| end | |
| end | |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | |
| function ComposeCornerPlugName(mat, crossingType, variant) | |
| variant = variant or "01" | |
| local ret = string.format("WallExt_%s_Cap%s_%s", mat, crossingType, variant) | |
| return ret | |
| end | |
| function ComposeCornerBeamName(mat, interiorExterior, variant, svd) | |
| variant = variant or "01" | |
| interiorExterior = interiorExterior or "Ext" | |
| local ret = string.format("Wall%s_%s_Corner_%s", interiorExterior, mat, variant) | |
| return ret | |
| end | |
| DefineClass.RoomCorner = { | |
| __parents = { "Slab", "CornerAlignedObj", "ComponentExtraTransform", "HideOnFloorChange" }, | |
| properties = { | |
| { id = "ColorModifier", dont_save = true, }, | |
| { id = "isPlug", editor = "bool", default = false }, | |
| }, | |
| room_container_name = "spawned_corners", | |
| } | |
| function RoomCorner:SetProperty(id, value) | |
| EditorCallbackObject.SetProperty(self, id, value) | |
| end | |
| function RoomCorner:Setentity(val) | |
| if not IsValidEntity(val) then | |
| --print(self.handle) | |
| self:ReportMissingSlabEntity(val) | |
| return | |
| end | |
| self.entity = val | |
| self:ChangeEntity(val) | |
| self:ResetVisibilityFlags() | |
| end | |
| function RoomCorner:GetAttachColors() | |
| return false, false | |
| end | |
| function RoomCorner:UpdateEntity() | |
| self.bad_entity = nil | |
| if self.is_destroyed then --TODO: do corner destroyed nbrs matter? | |
| if self:UpdateDestroyedState() then | |
| return | |
| end | |
| end | |
| local pos = self:GetPos() | |
| local newEnt = "InvisibleObject" | |
| local angle = 0 | |
| local dir = self.side | |
| local room = self.room | |
| if not room then return end | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| local gameFlags = is_permanent and gofPermanent or nil | |
| local mat = self.material | |
| local faceThis | |
| if mat ~= noneWallMat then | |
| local amIRoof = not not IsKindOf(self, "RoofCornerWallSlab")-- and "RoofWallSlab" or "WallSlab" | |
| local alwaysVisibleSlabsPresent = false | |
| local walls = MapGet(pos, voxelSizeX, "WallSlab", nil, nil, gameFlags, function(o, self, amIRoof, is_permanent) | |
| if not is_permanent and o:GetGameFlags(gofPermanent) ~= 0 then return end | |
| if not self:ShouldUpdateEntity(o) then return end | |
| local clsTest = amIRoof == not not IsKindOf(o, "RoofWallSlab") | |
| if not clsTest then return end | |
| --gofshadow should not be considered here, it would cause ent change because of temp invisible things, I can't recall what it fixes either | |
| local visible = (o:GetEnumFlags(const.efVisible) ~= 0 or IsValid(o.wall_obj)) | |
| if not visible then return end | |
| local x, y, z = o:GetPosXYZ() | |
| if z ~= pos:z() then return end | |
| alwaysVisibleSlabsPresent = alwaysVisibleSlabsPresent or o.always_visible | |
| return true | |
| end, self, amIRoof, is_permanent) or empty_table | |
| --filter out walls on the same positions | |
| if (amIRoof or alwaysVisibleSlabsPresent) and #walls > 1 then | |
| local pos_top = pos:AddZ(voxelSizeZ) | |
| pos_top = pos_top:SetZ(Min(room and room:GetRoofZAndDir(pos_top) or pos_top:z(), pos_top:z())) | |
| for i = #walls, 1, -1 do | |
| local wall_i = walls[i] | |
| local pos_i = wall_i:GetPos() | |
| local height_i = wall_i.room and wall_i.room:GetRoofZAndDir(pos_i) or 0 | |
| for j = 1, #walls do | |
| local wall_j = walls[j] | |
| if wall_j ~= wall_i then | |
| local pos_j = wall_j:GetPos() | |
| if pos_i == pos_j then | |
| if wall_i.room == wall_j.room or wall_i.room ~= room then | |
| table.remove(walls, i) | |
| end | |
| break | |
| elseif amIRoof and wall_i.room ~= wall_j.room then | |
| local go = true | |
| local other_room = wall_i.room ~= room and wall_i.room or wall_j.room | |
| if other_room ~= room then | |
| if IsValid(other_room) and (other_room:GetRoofZAndDir(pos_top) or 0) > pos_top:z() then | |
| go = false --don't drop lower roof slabs if we stick out | |
| end | |
| end | |
| if go then | |
| local height_j = wall_j.room and wall_j.room:GetRoofZAndDir(pos_j) or 0 | |
| if height_i < height_j then | |
| table.remove(walls, i) | |
| break | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| local ext_material_list = Presets.SlabPreset.SlabMaterials or empty_table | |
| local int_material_list = Presets.SlabPreset.SlabIndoorMaterials or empty_table | |
| local esvd = ext_material_list[mat] | |
| local is_inner_none = room.inner_wall_mat == noneWallMat | |
| local inner_mat_to_use = not is_inner_none and room.inner_wall_mat or mat | |
| local isvd = int_material_list[inner_mat_to_use] | |
| local variantStr = false | |
| if self.subvariant ~= -1 then | |
| local digit = self.subvariant | |
| variantStr = digit < 10 and "0" .. tostring(digit) or tostring(digit) | |
| end | |
| if #walls > 1 then | |
| if #walls == 2 then | |
| local p1 = walls[1]:GetPos() - pos | |
| local p2 = walls[2]:GetPos() - pos | |
| if p1:x() ~= p2:x() and p1:y() ~= p2:y() then --else they are on the same plane and corner should be invisible | |
| local x = p1:x() ~= 0 and p1:x() or p2:x() | |
| local y = p1:y() ~= 0 and p1:y() or p2:y() | |
| if x < 0 and y < 0 then | |
| angle = 0 | |
| elseif x < 0 and y > 0 then | |
| angle = 270 * 60 | |
| elseif x > 0 and y > 0 then | |
| angle = 180 * 60 | |
| elseif x > 0 and y < 0 then | |
| angle = 90 * 60 | |
| end | |
| local d = slabCornerAngleToDir[angle] | |
| if self.isPlug then | |
| if self.material == "Concrete" and dir ~= d then | |
| newEnt = ComposeCornerPlugName(mat, "D") | |
| end | |
| if newEnt == "InvisibleObject" or not IsValidEntity(newEnt) then | |
| newEnt = ComposeCornerPlugName(mat, "L") | |
| end | |
| else | |
| if d ~= dir or | |
| walls[1].variant == "IndoorIndoor" or | |
| walls[2].variant == "IndoorIndoor" then | |
| --What this does is that ExEx sets will use Ext corners for places where Int corners should be used. | |
| --This may not be exactly correct, the pedantically correct way would be for ExEx materials to use ExEx corners and have an ExExInt and an ExExExt variants. | |
| --Leave as is until someone needs it. | |
| local interior_exterior = not is_inner_none and "Int" or "Ext" | |
| if not variantStr and isvd then | |
| local subvariants, total = GetMaterialSubvariants(isvd, "corner_subvariants") | |
| if subvariants and #subvariants > 0 then | |
| local random = self:GetSeed(total) | |
| newEnt = GetRandomSubvariantEntity(random, subvariants, function(suffix, mat, interior_exterior) | |
| return ComposeCornerBeamName(mat, interior_exterior, suffix) | |
| end, inner_mat_to_use, interior_exterior) or ComposeCornerBeamName(inner_mat_to_use, interior_exterior) | |
| end | |
| end | |
| if newEnt == "InvisibleObject" then | |
| newEnt = ComposeCornerBeamName(inner_mat_to_use, interior_exterior, variantStr) | |
| end | |
| else | |
| if not variantStr and esvd then | |
| local subvariants, total = GetMaterialSubvariants(esvd, "corner_subvariants") | |
| if subvariants and #subvariants > 0 then | |
| local random = self:GetSeed(total) | |
| newEnt = GetRandomSubvariantEntity(random, subvariants, function(suffix, mat) | |
| return ComposeCornerBeamName(mat, "Ext", suffix) | |
| end, mat) or ComposeCornerBeamName(mat, "Ext") | |
| end | |
| end | |
| if newEnt == "InvisibleObject" then | |
| newEnt = ComposeCornerBeamName(mat, "Ext", variantStr) | |
| end | |
| end | |
| end | |
| end | |
| elseif #walls == 3 then | |
| if self.isPlug then | |
| newEnt = ComposeCornerPlugName(mat, "T") | |
| local a1 = walls[1]:GetAngle() | |
| local a2 = walls[2]:GetAngle() | |
| local a3 = walls[3]:GetAngle() | |
| local delim = 180 * 60 | |
| local orthoEl | |
| if a1 % delim == a2 % delim then | |
| orthoEl = walls[3] | |
| elseif a1 % delim == a3 % delim then | |
| orthoEl = walls[2] | |
| else | |
| orthoEl = walls[1] | |
| end | |
| local toMe = pos - orthoEl:GetPos() | |
| faceThis = pos + toMe | |
| end | |
| elseif #walls == 4 then | |
| if self.isPlug then | |
| newEnt = ComposeCornerPlugName(mat, "X") | |
| end | |
| end | |
| end | |
| end | |
| if not IsValidEntity(newEnt) then | |
| if self.subvariant == 1 or self.subvariant == -1 then | |
| --presumably if var 1 is missing nothing can be done | |
| self:ReportMissingSlabEntity(newEnt) | |
| newEnt = "InvisibleObject" | |
| else | |
| print("Reverting corner [" .. self.handle .. "] subvariant [" .. self.subvariant .. "] because no entity [" .. newEnt .. "] found.") | |
| self.subvariant = -1 | |
| self:UpdateEntity() | |
| return | |
| end | |
| end | |
| if newEnt ~= self.entity or IsChangingMap() then | |
| self:Setentity(newEnt) | |
| self:ApplyMaterialProps() | |
| end | |
| if faceThis then | |
| self:Face(faceThis) | |
| else | |
| self:SetAngle(angle) | |
| end | |
| self:SetColorFromRoom() | |
| end | |
| function RoomCorner:SetColorFromRoom() | |
| local room = self.room | |
| if not room then return end | |
| local rm = self:GetColorsRoomMember() | |
| self:Setcolors(self.colors or room[rm]) | |
| end | |
| function RoomCorner:GetColorsRoomMember() | |
| local room = self.room | |
| if not room then | |
| return self.colors_room_member | |
| end | |
| if slabCornerAngleToDir[self:GetAngle()] == self.side or room.inner_wall_mat == noneWallMat then | |
| return "outer_colors" | |
| else | |
| return "inner_colors" | |
| end | |
| end | |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | |
| g_BoxesToCompute = false | |
| local BoxIntersect = box().Intersect | |
| local irInside = const.irInside | |
| local function DoesBoxEncompassBox(b1, b2) | |
| return BoxIntersect(b1, b2) == irInside | |
| end | |
| MapGameTimeRepeat("ComputeSlabVisibility", nil, function() | |
| if not g_BoxesToCompute then | |
| WaitWakeup() | |
| end | |
| ComputeSlabVisibility() | |
| end) | |
| --the order in which MapGameTimeRepeat get registered determines when they'll execute, | |
| --we want DelayedSlabUpdate to be after ComputeSlabVisibility if they are concurent | |
| MapGameTimeRepeat("DelayedSlabUpdate", -1, function(sleep) | |
| SlabUpdate() | |
| if first then | |
| Msg("SlabsDoneLoading") | |
| first = false | |
| end | |
| WaitWakeup() | |
| end) | |
| function ComputeSlabVisibilityOfObjects(objs) | |
| local bbox = empty_box | |
| for _, obj in ipairs(objs) do | |
| bbox = AddRects(bbox, obj:GetObjectBBox()) | |
| end | |
| ComputeSlabVisibilityInBox(bbox) | |
| end | |
| function ComputeSlabVisibilityInBox(box) | |
| if not box or box:IsEmpty2D() then | |
| return | |
| end | |
| g_BoxesToCompute = g_BoxesToCompute or {} | |
| local boxes = g_BoxesToCompute | |
| for i = 1, #boxes do | |
| local bi = boxes[i] | |
| if bi == box then | |
| --same | |
| return | |
| end | |
| if DoesBoxEncompassBox(bi, box) then | |
| --fully encompassed | |
| return | |
| elseif DoesBoxEncompassBox(box, bi) then | |
| boxes[i] = box | |
| return | |
| end | |
| end | |
| --DbgAddBox(box) | |
| NetUpdateHash("ComputeSlabVisibilityInBox", box) | |
| table.insert(boxes, box) | |
| DelayedComputeSlabVisibility() | |
| end | |
| function DelayedComputeSlabVisibility() | |
| Wakeup(PeriodicRepeatThreads["ComputeSlabVisibility"]) | |
| end | |
| local function TestMaterials(myMat, hisMat, reverseNoneForMe, reverseNoneForHim) | |
| --first ret true -- someone will get suppressed | |
| --second ret true - otherSlab will get suppressed, false - slab will get suppressed | |
| if hisMat == noneWallMat then | |
| return true, reverseNoneForHim and true or false | |
| elseif myMat == noneWallMat and hisMat ~= noneWallMat then | |
| return true, not reverseNoneForMe and true or false | |
| end | |
| return false | |
| end | |
| function Slab:IsOffset() | |
| return false | |
| end | |
| function RoofWallSlab:IsOffset() | |
| local x1, y1, z1 = WallVoxelToWorld(WallWorldToVoxel(self)) | |
| local x2, y2, z2 = self:GetPosXYZ() | |
| return x1 ~= x2 or y1 ~= y2 | |
| end | |
| -- 1 if otherSlab should be suppressed | |
| -- -1 if self should be suppressed | |
| -- 0 if noone is suppress | |
| function WallSlab:ShouldSuppressSlab(otherSlab, material_preset) | |
| if self:IsSuppressionDisabled(otherSlab) then | |
| return 0 | |
| end | |
| local importance_test = self:SuppressByImportance(otherSlab) | |
| if importance_test ~= 0 then | |
| return importance_test | |
| end | |
| local amIRoof = IsKindOf(self, "RoofWallSlab") | |
| local isHeRoof = IsKindOf(otherSlab, "RoofWallSlab") | |
| if isHeRoof and not amIRoof then | |
| return 1 | |
| elseif not isHeRoof and amIRoof then | |
| return -1 | |
| end | |
| local mr = self.room | |
| local reverseNoneForMe = IsValid(mr) and (amIRoof and mr.none_roof_wall_mat_does_not_affect_nbrs or not amIRoof and mr.none_wall_mat_does_not_affect_nbrs) or false | |
| local hr = otherSlab.room | |
| local reverseNoneForHim = IsValid(hr) and (isHeRoof and hr.none_roof_wall_mat_does_not_affect_nbrs or not isHeRoof and hr.none_wall_mat_does_not_affect_nbrs) or false | |
| local r1, r2 = TestMaterials(self.material, otherSlab.material, reverseNoneForMe, reverseNoneForHim) | |
| if r1 then | |
| return r2 and 1 or -1 | |
| end | |
| if isHeRoof and amIRoof then | |
| return 0 | |
| end | |
| local material_test = self:SuppressByMaterial(otherSlab, material_preset) | |
| if material_test ~= 0 then | |
| return material_test | |
| end | |
| if IsValid(self.room) and IsValid(otherSlab.room) then | |
| return self.room.handle - otherSlab.room.handle | |
| end | |
| if not IsValid(self.room) and IsValid(otherSlab.room) then | |
| return -1 | |
| end | |
| if IsValid(self.room) and not IsValid(otherSlab.room) then | |
| return 1 | |
| end | |
| return self.handle - otherSlab.handle | |
| end | |
| -- 1 if otherSlab should be suppressed | |
| -- -1 if self should be suppressed | |
| -- 0 if noone is suppress | |
| function FloorSlab:ShouldSuppressSlab(otherSlab, material_preset) | |
| if self:IsSuppressionDisabled(otherSlab) then | |
| return 0 | |
| end | |
| local importance_test = self:SuppressByImportance(otherSlab) | |
| if importance_test ~= 0 then | |
| return importance_test | |
| end | |
| local reverseNoneForMe = IsValid(self.room) and self.room.none_floor_mat_does_not_affect_nbrs or false | |
| local reverseNoneForHim = IsValid(otherSlab.room) and otherSlab.room.none_floor_mat_does_not_affect_nbrs or false | |
| local r1, r2 = TestMaterials(self.material, otherSlab.material, reverseNoneForMe, reverseNoneForHim) | |
| if r1 then | |
| return r2 and 1 or -1 | |
| end | |
| local material_test = self:SuppressByMaterial(otherSlab, material_preset) | |
| if material_test ~= 0 then | |
| return material_test | |
| end | |
| if IsValid(self.room) and IsValid(otherSlab.room) then | |
| local amIRoof = self.room:IsRoofOnly() | |
| local isHeRoof = otherSlab.room:IsRoofOnly() | |
| if isHeRoof and not amIRoof then | |
| return 1 | |
| elseif amIRoof and not isHeRoof then | |
| return -1 | |
| end | |
| return self.room.handle - otherSlab.room.handle | |
| end | |
| if not IsValid(self.room) and IsValid(otherSlab.room) then | |
| return -1 | |
| end | |
| if IsValid(self.room) and not IsValid(otherSlab.room) then | |
| return 1 | |
| end | |
| return self.handle - otherSlab.handle | |
| end | |
| CeilingSlab.ShouldSuppressSlab = FloorSlab.ShouldSuppressSlab | |
| cornerToWallSides = { | |
| East = { "East", "North" }, | |
| South = { "East", "South" }, | |
| West = { "West", "South" }, | |
| North = { "West", "North" }, | |
| } | |
| -- 1 if otherSlab should be suppressed | |
| -- -1 if self should be suppressed | |
| -- 0 if noone is suppress | |
| function RoomCorner:ShouldSuppressSlab(otherSlab, material_preset) | |
| if self:IsSuppressionDisabled(otherSlab) then | |
| return 0 | |
| end | |
| local importance_test = self:SuppressByImportance(otherSlab) | |
| if importance_test ~= 0 then | |
| return importance_test | |
| end | |
| local amIRoof = IsKindOf(self, "RoofCornerWallSlab") | |
| local isHeRoof = IsKindOf(otherSlab, "RoofCornerWallSlab") | |
| if isHeRoof and not amIRoof then | |
| return 1 | |
| elseif not isHeRoof and amIRoof then | |
| return -1 | |
| elseif isHeRoof and amIRoof then | |
| return 0 | |
| end | |
| local r1, r2 = TestMaterials(self.material, otherSlab.material) | |
| r2 = not r2 --reverse behavior, none corners should be below other corners | |
| if r1 then return r2 and 1 or -1 end | |
| local material_test = self:SuppressByMaterial(otherSlab, material_preset) | |
| if material_test ~= 0 then | |
| return material_test | |
| end | |
| if IsValid(self.room) and IsValid(otherSlab.room) then | |
| --if one of the walls of an adjacent bld is disabled, corners of existing walls have precedence | |
| local myC, hisC = 0, 0 | |
| local myAdj = cornerToWallSides[self.side] | |
| local hisAdj = cornerToWallSides[otherSlab.side] | |
| for i = 1, 2 do | |
| myC = myC + ((self.room:GetWallMatHelperSide(myAdj[i]) == noneWallMat) and 1 or 0) | |
| hisC = hisC + ((otherSlab.room:GetWallMatHelperSide(hisAdj[i]) == noneWallMat) and 1 or 0) | |
| end | |
| if myC ~= hisC then | |
| return hisC - myC | |
| end | |
| return self.room.handle - otherSlab.room.handle | |
| end | |
| return self.handle - otherSlab.handle | |
| end | |
| function CSlab:ShouldSuppressSlab(otherSlab) | |
| return 0 | |
| end | |
| function CSlab:SuppressByMaterial(slab, material_preset) | |
| local mp_self = material_preset or self:GetMaterialPreset() | |
| local mp_slab = slab:GetMaterialPreset() | |
| return (mp_self and mp_self.strength or 0) - (mp_slab and mp_slab.strength or 0) | |
| end | |
| function CSlab:SuppressByImportance(slab) | |
| return self.class_suppression_strenght - slab.class_suppression_strenght | |
| end | |
| function CSlab:IsSuppressionDisabled(slab) | |
| return self == slab or self.always_visible or slab.always_visible | |
| end | |
| function GetTopmostWallSlab(slab) | |
| return MapGetFirst(slab, 0, "WallSlab", function(o) | |
| return o.isVisible | |
| end) | |
| end | |
| CSlab.visibility_pass = 1 | |
| function CSlab:ComputeVisibility(passed) | |
| self:SetSuppressor(false) | |
| end | |
| function CSlab:ComputeVisibilityAround() | |
| ComputeSlabVisibilityInBox(self:GetObjectBBox()) | |
| end | |
| local function PassSlab(slab, passed) | |
| --for easier debug, all passing goes trhough here | |
| passed[slab] = true | |
| end | |
| local topMySide | |
| local topOpSide | |
| function OnMsg.DoneMap() | |
| topMySide = nil | |
| topOpSide = nil | |
| end | |
| local function PassWallSlabs(slab, self, passed, mpreset) | |
| if slab == self then | |
| return | |
| end | |
| if slab:GetAngle() == self:GetAngle() then | |
| local r = topMySide:ShouldSuppressSlab(slab, mpreset) | |
| if r > 0 then | |
| --slab is suppressed | |
| PassSlab(slab, passed) | |
| slab:SetSuppressor(topMySide, self) | |
| elseif r < 0 then | |
| if topMySide:SetSuppressor(slab, self) then | |
| topMySide = slab | |
| end | |
| end | |
| else | |
| local r = topOpSide and topOpSide:ShouldSuppressSlab(slab, mpreset) or 0 | |
| if r > 0 then | |
| --slab is suppressed | |
| PassSlab(slab, passed) | |
| slab:SetSuppressor(topOpSide, self) | |
| elseif r < 0 then | |
| if topOpSide:SetSuppressor(slab, self) then | |
| topOpSide = slab | |
| end | |
| end | |
| topOpSide = topOpSide or slab | |
| end | |
| end | |
| function WallSlab:ComputeVisibility(passed) | |
| --walls | |
| passed = passed or {} | |
| local mpreset = self:GetMaterialPreset() | |
| topMySide = self | |
| topOpSide = false | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| local gameFlags = is_permanent and gofPermanent or nil | |
| MapForEach(self, 0, "WallSlab", nil, nil, gameFlags, PassWallSlabs, self, passed, mpreset) | |
| local top = topMySide | |
| local variant = false | |
| --take inner mats from tops, todo: maybe they should be calculated b4 hand from all slabs in spot? | |
| local m1 = noneWallMat | |
| local m2 = noneWallMat | |
| local c2 | |
| --dont do innerinner variants for roof<->notroof combo | |
| if top and topOpSide then | |
| local isTopRoof = IsKindOf(top, "RoofWallSlab") | |
| local isOpTopRoof = IsKindOf(topOpSide, "RoofWallSlab") | |
| if isTopRoof ~= isOpTopRoof then | |
| if isTopRoof and not isOpTopRoof then | |
| PassSlab(top, passed) | |
| if top:SetSuppressor(topOpSide, self) then | |
| top = topOpSide | |
| topOpSide = false | |
| end | |
| elseif not isTopRoof and isOpTopRoof then | |
| PassSlab(topOpSide, passed) | |
| if topOpSide:SetSuppressor(top, self) then | |
| topOpSide = false | |
| end | |
| end | |
| end | |
| end | |
| local opSideCompResult | |
| if topOpSide then | |
| variant = "IndoorIndoor" | |
| opSideCompResult = top:ShouldSuppressSlab(topOpSide, mpreset) | |
| if opSideCompResult > 0 then | |
| topOpSide:SetSuppressor(top, self) | |
| m1 = top.room and top.room.inner_wall_mat or top.indoor_material_1 or noneWallMat | |
| m2 = topOpSide.room and topOpSide.room.inner_wall_mat or topOpSide.indoor_material_1 or noneWallMat | |
| c2 = topOpSide.room and topOpSide.room.inner_colors | |
| elseif opSideCompResult < 0 then | |
| if top:SetSuppressor(topOpSide, self) then | |
| top = topOpSide | |
| end | |
| m1 = top.room and top.room.inner_wall_mat or top.indoor_material_1 or noneWallMat | |
| m2 = topMySide.room and topMySide.room.inner_wall_mat or topMySide.indoor_material_1 or noneWallMat | |
| c2 = topMySide.room and topMySide.room.inner_colors | |
| if top.wall_obj then | |
| passed[top] = nil --topOpSide is visible, he needs to pass in case his covered up by a slabwallobj | |
| end | |
| elseif opSideCompResult == 0 then | |
| passed[topOpSide] = nil --both are visible, this guy needs to pass to fix up his variant | |
| m1 = top.room and top.room.inner_wall_mat or top.indoor_material_1 or noneWallMat --stacked roof walls, ignore his mat, use ours though. | |
| end | |
| if m2 == noneWallMat and m1 == noneWallMat then | |
| variant = "Outdoor" | |
| elseif m2 == noneWallMat then | |
| variant = "OutdoorIndoor" | |
| elseif m1 == noneWallMat then | |
| --this will hide opposing inner mat | |
| variant = "Outdoor" | |
| end | |
| else | |
| local indoorMat = top.room and top.room.inner_wall_mat or top.indoor_material_1 | |
| if indoorMat == noneWallMat then | |
| variant = "Outdoor" | |
| else | |
| variant = "OutdoorIndoor" | |
| m1 = indoorMat | |
| end | |
| end | |
| if top.material == noneWallMat and not top.always_visible then | |
| top:SetSuppressor(self) | |
| else | |
| if top.exterior_attach_colors == c2 then | |
| --save fixup, TODO: remove | |
| top:Setexterior_attach_colors(false) | |
| end | |
| if top:ShouldUpdateEntity(self) | |
| and (top.variant ~= variant or top.indoor_material_2 ~= m2 or top.exterior_attach_colors_from_nbr ~= c2) then | |
| local newVar = variant | |
| if not IsValid(top.room) then | |
| --slabs placed by hand have their variant preserved, unless forced | |
| newVar = top.variant | |
| m2 = top.indoor_material_2 | |
| end | |
| if top.forceVariant ~= "" then | |
| newVar = top.forceVariant | |
| end | |
| local defaults = getmetatable(top) | |
| top.variant = newVar ~= defaults.variant and newVar or nil | |
| top.indoor_material_2 = m2 ~= defaults.indoor_material_2 and m2 or nil | |
| top:Setexterior_attach_colors_from_nbr(c2) | |
| top:UpdateEntity() | |
| top:UpdateVariantEntities() | |
| end | |
| top:SetSuppressor(false, self) | |
| end | |
| --supress roof walls if they are in the box of above rooms (2D box) | |
| if top.room and not top.room:IsRoofOnly() and IsKindOf(top, "RoofWallSlab") then | |
| local pos = top:GetPos() | |
| local adjacent_rooms = top.room.adjacent_rooms or empty_table | |
| for _, adj_room in ipairs(adjacent_rooms) do | |
| local data = adjacent_rooms[adj_room] | |
| if not adj_room.being_placed and | |
| not adj_room:IsRoofOnly() and adj_room.box ~= data[1] then --ignore rooms inside our room | |
| local adj_box = adj_room.box:grow(1, 1, 0) | |
| local in_box_3d = pos:InBox(adj_box) | |
| if not in_box_3d and top.room.floor < adj_room.floor then | |
| local rb = adj_room.roof_box | |
| if rb then | |
| rb = rb:grow(1, 1, 0) | |
| in_box_3d = pos:InBox(rb) | |
| --[[ | |
| --restore if needed | |
| if in_box_3d then | |
| --this part shows offset pieces that are partially in the roof box of a lower room | |
| local b = top:GetObjectBBox() | |
| local ib = IntersectRects(b, rb) | |
| if ib:sizex() ~= b:sizex() and ib:sizey() ~= b:sizey() then | |
| in_box_3d = false --at least one side should be fully in, else we are partially affected and will leave a whole | |
| end | |
| end | |
| ]] | |
| end | |
| end | |
| if in_box_3d then | |
| top:SetSuppressor(self) | |
| if topOpSide and opSideCompResult == 0 then --topOpSide is still visible | |
| topOpSide:SetSuppressor(self) | |
| end | |
| break | |
| end | |
| end | |
| end | |
| end | |
| topMySide = nil | |
| topOpSide = nil | |
| end | |
| local floor_top | |
| function OnMsg.DoneMap() | |
| floor_top = nil | |
| end | |
| local function FloorPassFloorAndCeilingSlabs(slab, self, passed, mpreset) | |
| if slab == self then | |
| return | |
| end | |
| local comp = floor_top:ShouldSuppressSlab(slab, mpreset) | |
| if comp == 0 then | |
| return | |
| end | |
| PassSlab(slab, passed) | |
| if comp > 0 then | |
| slab:SetSuppressor(floor_top, self) | |
| else | |
| if floor_top:SetSuppressor(slab, self) then | |
| floor_top = slab | |
| end | |
| end | |
| end | |
| local function FloorPassRoofPlaneAndEdgeSlabs(slab, floor_top, topZ, passed, self) | |
| local dz = topZ - select(3, slab:GetPosXYZ()) | |
| if 0 < dz and dz <= voxelSizeZ then | |
| PassSlab(slab, passed) | |
| slab:SetSuppressor(floor_top, self) | |
| end | |
| end | |
| function FloorSlab:ComputeVisibility(passed) | |
| passed = passed or {} | |
| floor_top = self | |
| local mpreset = self:GetMaterialPreset() | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| local gameFlags = is_permanent and gofPermanent or nil | |
| MapForEach(self, 0, "FloorSlab", "CeilingSlab", nil, nil, gameFlags, | |
| FloorPassFloorAndCeilingSlabs, self, passed, mpreset) | |
| if floor_top.material == noneWallMat then | |
| floor_top:SetSuppressor(floor_top, self) | |
| else | |
| floor_top:SetSuppressor(false, self) | |
| --hide roofs 1 vox below 155029 | |
| MapForEach(floor_top, halfVoxelSizeX, "RoofPlaneSlab", "RoofEdgeSlab", nil, nil, gameFlags, | |
| FloorPassRoofPlaneAndEdgeSlabs, floor_top, select(3, floor_top:GetPosXYZ()), passed, self) | |
| end | |
| floor_top = nil | |
| end | |
| CeilingSlab.ComputeVisibility = FloorSlab.ComputeVisibility | |
| function SlabWallObject:ComputeVisibility(passed) | |
| self:UpdateAffectedWalls() | |
| end | |
| local roof_suppressed | |
| function OnMsg.DoneMap() | |
| roof_suppressed = nil | |
| end | |
| local function RoofPassRoofEdgeAndCornerSlabs(slab, self, passed, z) | |
| if slab == self or slab.room == self.room | |
| or (slab:GetAngle() == self:GetAngle() and slab:GetMirrored() == self:GetMirrored()) | |
| or self:IsSuppressionDisabled(slab) | |
| then | |
| return | |
| end | |
| local _, _, slab_z = slab:GetPosXYZ() | |
| if z < slab_z then | |
| roof_suppressed = true | |
| return | |
| end | |
| PassSlab(slab, passed) | |
| if slab:SetSuppressor(self) and z == slab_z then | |
| roof_suppressed = true | |
| end | |
| end | |
| function RoofSlab:ComputeVisibility(passed) | |
| passed = passed or {} | |
| roof_suppressed = nil | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| local gameFlags = is_permanent and gofPermanent or nil | |
| if self.room and not self.room:IsRoofOnly() then | |
| --determine a list of rooms, inside whose volumes our self lies | |
| local passed = {} | |
| local function CheckSuppressed(adj_room, pos, adjacent_rooms, slab_room, passed) | |
| local data = adjacent_rooms[adj_room] | |
| if not passed[adj_room] then | |
| passed[adj_room] = true | |
| --ignore rooms inside our room | |
| if slab_room ~= adj_room and | |
| not adj_room:IsRoofOnly() and | |
| not adj_room.being_placed and | |
| (not data or adj_room.box ~= data[1]) | |
| then | |
| local adj_box = adj_room.box:grow(1,1,1) | |
| local in_box = pos:InBox(adj_box) | |
| if not in_box and (slab_room.floor or 0) < (adj_room.floor or 0) then | |
| local rb = adj_room.roof_box | |
| if rb then | |
| rb = rb:grow(1, 1, 0) | |
| in_box = pos:InBox(rb) | |
| end | |
| end | |
| if in_box then | |
| roof_suppressed = true | |
| return "break" | |
| end | |
| end | |
| end | |
| end | |
| local pos = self:GetPos() | |
| local adjacent_rooms = self.room.adjacent_rooms or empty_table | |
| local sbox = self:GetObjectBBox() | |
| EnumVolumes(sbox:grow(1, 1, 1), CheckSuppressed, pos, adjacent_rooms, self.room, passed) --this catches some rooms that are not considered adjacent (roof adjacency) | |
| if not roof_suppressed then | |
| --this catches other rooms the previous enum doesn't, normal adjacency | |
| for _, adj_room in ipairs(adjacent_rooms) do | |
| if CheckSuppressed(adj_room, pos, adjacent_rooms, self.room, passed) == "break" then | |
| break | |
| end | |
| end | |
| end | |
| end | |
| --idk, but this seems to be the best looking option atm, of course that could change | |
| if not roof_suppressed and const.SuppressMultipleRoofEdges and IsKindOfClasses(self, "RoofEdgeSlab", "RoofCorner") then | |
| --if more than one in one pos, suppress all | |
| local x, y, z = self:GetPosXYZ() | |
| MapForEach(x, y, guic, "RoofEdgeSlab", "RoofCorner", nil, nil, gameFlags, | |
| RoofPassRoofEdgeAndCornerSlabs, self, passed, z) | |
| end | |
| if roof_suppressed then | |
| self:SetSuppressor(self) | |
| else | |
| self:SetSuppressor(false) | |
| end | |
| roof_suppressed = nil | |
| end | |
| local dev = Platform.developer | |
| RoomCorner.visibility_pass = 2 | |
| function RoomCorner:ComputeVisibility(passed) | |
| local _, _, z = self:GetPosXYZ() | |
| local topSlab = self | |
| local mpreset = self:GetMaterialPreset() | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| local gameFlags = is_permanent and gofPermanent or nil | |
| MapForEach(self, halfVoxelSizeX, "RoomCorner", nil, nil, gameFlags, function(slab, self, z, mpreset) | |
| if slab == self then | |
| return | |
| end | |
| local _, _, z1 = slab:GetPosXYZ() | |
| if z1 ~= z or self.isPlug ~= slab.isPlug then | |
| return --plugs suppress plugs only, corners suppress corners only | |
| end | |
| if slab:ShouldUpdateEntity(self) then | |
| slab:UpdateEntity() --update all entities all the time, otherwise we get map save diffs when they get updated on load map | |
| end | |
| local comp = topSlab:ShouldSuppressSlab(slab, mpreset) | |
| if comp > 0 then | |
| slab:SetSuppressor(topSlab, self) | |
| elseif comp < 0 then | |
| if topSlab:SetSuppressor(slab, self) then | |
| topSlab = slab | |
| end | |
| end | |
| end, self, z, mpreset) | |
| if topSlab:ShouldUpdateEntity(self) then | |
| topSlab:UpdateEntity() | |
| end | |
| if topSlab.entity ~= "InvisibleObject" and IsValidEntity(topSlab.entity) or | |
| dev and topSlab.entity == "InvisibleObject" and topSlab.is_destroyed then --so they can be seen and repaired by nbrs | |
| topSlab:SetSuppressor(false, self) | |
| else | |
| topSlab:SetSuppressor(topSlab, self) | |
| end | |
| end | |
| StairSlab.visibility_pass = 3 | |
| function StairSlab:ComputeVisibility(passed) | |
| if self:GetEnumFlags(const.efVisible) == 0 then | |
| return | |
| end | |
| local is_permanent = self:GetGameFlags(gofPermanent) ~= 0 | |
| local gameFlags = is_permanent and gofPermanent or nil | |
| local x, y, z = self:GetPosXYZ() | |
| if z then | |
| local max = self.hide_floor_slabs_above_in_range | |
| for i = 1, max do | |
| z = z + voxelSizeZ | |
| MapForEach(x, y, z, 0, "FloorSlab", nil, nil, gameFlags, function(slab, self) | |
| slab:SetSuppressor(self) | |
| end, self) | |
| end | |
| else | |
| print(string.format("Stairs with handle[%d] have an invalid Z!", stairs_slab.handle)) | |
| end | |
| end | |
| local function _ComputeSlabVisibility(boxes) | |
| local passed = {} | |
| local max_pass = 1 | |
| local passes | |
| for i = 1, #boxes do | |
| local _box = boxes[i]:grow(1, 1, 1) -- include the wall elements | |
| --query optimizes some children away, so they are added explicitly | |
| MapForEach(_box, "CSlab", function(slab) | |
| if passed[slab] then | |
| return | |
| end | |
| local pass = slab.visibility_pass | |
| if pass == 1 then | |
| PassSlab(slab, passed) | |
| slab:ComputeVisibility(passed) | |
| else | |
| max_pass = Max(max_pass, pass) | |
| passes = passes or {} | |
| passes[pass] = table.create_add(passes[pass], slab) | |
| end | |
| end) | |
| end | |
| for i=2,max_pass do | |
| for _, slab in ipairs(passes[i] or empty_table) do | |
| if not passed[slab] then | |
| PassSlab(slab, passed) | |
| slab:ComputeVisibility(passed) | |
| end | |
| end | |
| end | |
| end | |
| function ComputeSlabVisibility() | |
| local boxes = g_BoxesToCompute | |
| g_BoxesToCompute = false | |
| if not boxes then return end --nothing to compute | |
| SuspendPassEdits("ComputeSlabVisibility") | |
| procall(_ComputeSlabVisibility, boxes) | |
| ResumePassEdits("ComputeSlabVisibility") | |
| Msg("SlabVisibilityComputeDone") | |
| end | |
| function DeleteOrphanCorners() | |
| MapForEach("map", "RoomCorner", nil, nil, gofPermanent, function(o) | |
| if not o.room then | |
| DoneObject(o) | |
| end | |
| end) | |
| end | |
| function DeleteOrphanWalls() | |
| local c = 0 | |
| MapForEach("map", "WallSlab", nil, nil, gofPermanent, function(o) | |
| if not o.room then | |
| DoneObject(o) | |
| c = c + 1 | |
| end | |
| end) | |
| print("deleted: ", c) | |
| end | |
| function RecreateSelectedSlabFloorWall_RestoreSel(t) | |
| editor.ClearSel() | |
| for i = 1, #t do | |
| local entry = t[i] | |
| local o = MapGet(entry[2], 0, entry[1], nil, nil, gofPermanent, function(o, room) | |
| return o.room == room | |
| end, entry[3]) | |
| if o then | |
| editor.AddToSel(o) | |
| end | |
| end | |
| end | |
| function RecreateSelectedSlabFloorWall() | |
| local ol = editor.GetSel() | |
| local restoreSel = {} | |
| local walls = {} | |
| local floors = {} | |
| for i = 1, #ol do | |
| local o = ol[i] | |
| local room = o.room | |
| if IsValid(room) then | |
| if IsKindOf(o, "WallSlab") then | |
| table.insert(restoreSel, {o.class, o:GetPos(), room}) | |
| table.insert_unique(walls, room) | |
| elseif IsKindOf(o, "FloorSlab") then | |
| table.insert(restoreSel, {o.class, o:GetPos(), room}) | |
| table.insert_unique(floors, room) | |
| end | |
| end | |
| end | |
| for i = 1, #walls do | |
| walls[i]:RecreateWalls() | |
| end | |
| for i = 1, #floors do | |
| floors[i]:RecreateFloor() | |
| end | |
| if #restoreSel > 0 then | |
| DelayedCall(0, RecreateSelectedSlabFloorWall_RestoreSel, restoreSel) | |
| end | |
| end | |
| local defaultColorMod = RGBA(100, 100, 100, 255) | |
| function Slab:ApplyColorModFromSource(source) | |
| local cm = source:GetColorModifier() | |
| if cm ~= defaultColorMod then | |
| self:SetColorModifier(cm) | |
| end | |
| end | |
| function Slab:EditorCallbackClone(source) | |
| self.room = false | |
| self.subvariant = source.subvariant | |
| self:Setcolors(source.colors) | |
| self:Setinterior_attach_colors(source.interior_attach_colors) | |
| self:Setexterior_attach_colors(source.exterior_attach_colors) | |
| self:Setexterior_attach_colors_from_nbr(source.exterior_attach_colors_from_nbr) | |
| self:ApplyColorModFromSource(source) | |
| end | |
| function WallSlab:EditorCallbackClone(source) | |
| Slab.EditorCallbackClone(self, source) | |
| --TODO: rem this once colors auto copy | |
| self:Setcolors(source.colors or source.room and source.room.outer_colors) | |
| self:Setinterior_attach_colors(source.interior_attach_colors or source.room and source.room.inner_colors) | |
| self:ApplyColorModFromSource(source) | |
| end | |
| function FloorSlab:EditorCallbackClone(source) | |
| Slab.EditorCallbackClone(self, source) | |
| self:Setcolors(source.colors or source.room and source.room.floor_colors) | |
| self:ApplyColorModFromSource(source) | |
| end | |
| function SlabWallObject:EditorCallbackClone(source) | |
| if self.owned_slabs == source.owned_slabs then | |
| --props copy will sometimes provoke the creation of new slabs and sometimes it wont.. | |
| self.owned_slabs = false | |
| end | |
| self.room = false | |
| self.subvariant = source.subvariant | |
| --aparently windows n doors use the default colorization | |
| self:SetColorization(source) | |
| end | |
| function RoofSlab:EditorCallbackClone(source) | |
| Slab.EditorCallbackClone(self, source) | |
| self:Setcolors(source.colors or source.room and source.room.roof_colors) | |
| self:ApplyColorModFromSource(source) | |
| local x, y = source:GetSkew() | |
| self:SetSkew(x, y) | |
| end | |
| function OnMsg.GedPropertyEdited(ged_id, obj, prop_id, old_value) | |
| if obj.class == "ColorizationPropSet" then | |
| -- escalate OnEditorSetProperty for the colorization nested object properties to the parent Room/Slab | |
| local ged = GedConnections[ged_id] | |
| local parents = ged:GatherAffectedGameObjects(obj) | |
| for _, parent in ipairs(parents) do | |
| if parent:IsKindOfClasses("Room", "Slab") then | |
| local prop_id = "" | |
| for _, value in ipairs(parent:GetProperties()) do | |
| if parent:GetProperty(value.id) == obj then | |
| prop_id = value.id | |
| break | |
| end | |
| end | |
| if IsKindOf(parent, "Slab") then | |
| ged:NotifyEditorSetProperty(parent, prop_id, obj) | |
| parent:SetProperty(prop_id, obj) | |
| elseif IsKindOf(parent, "Room") then | |
| parent:OnEditorSetProperty(prop_id, old_value, ged) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| local similarSlabPropsToMatch = { | |
| "entity_base_name", | |
| "material", | |
| "variant", | |
| "indoor_material_1", | |
| "indoor_material_2", | |
| } | |
| function EditorSelectSimilarSlabs(matchSubvariant) | |
| local sel = editor.GetSel() | |
| local o = #(sel or "") > 0 and sel[1] | |
| if not o then | |
| print("No obj selected.") | |
| return | |
| end | |
| if not IsKindOf(o, "Slab") then | |
| print("Obj not a Slab.") | |
| return | |
| end | |
| local newSel = {o} | |
| MapForEach("map", "Slab", function(s, o, newSel, similarSlabPropsToMatch) | |
| if o ~= s then | |
| local similar = true | |
| for i = 1, #similarSlabPropsToMatch do | |
| local p = similarSlabPropsToMatch[i] | |
| if s[p] ~= o[p] then | |
| similar = false | |
| break | |
| end | |
| end | |
| if similar then | |
| if not matchSubvariant or s:GetEntity() == o:GetEntity() then | |
| table.insert(newSel, s) | |
| end | |
| end | |
| end | |
| end, o, newSel, similarSlabPropsToMatch) | |
| editor.ClearSel() | |
| editor.AddToSel(newSel) | |
| end | |
| function DbgRestoreDefaultsFor_forceInvulnerableBecauseOfGameRules() | |
| MapForEach("map", "Slab", function(o) | |
| if not o.room then | |
| --cls default | |
| o.forceInvulnerableBecauseOfGameRules = g_Classes[o.class].forceInvulnerableBecauseOfGameRules | |
| else | |
| if IsKindOf(o, "FloorSlab") and o.floor == 1 then | |
| o.forceInvulnerableBecauseOfGameRules = true | |
| else | |
| --vulnerable | |
| o.forceInvulnerableBecauseOfGameRules = false | |
| end | |
| end | |
| end) | |
| end | |
| slab_missing_entity_white_list = { | |
| } | |
| function CSlab:ReportMissingSlabEntity(ent) | |
| if not slab_missing_entity_white_list[ent] then | |
| print(string.format("[WARNING] Missing slab entity %s, reporting slab handle [%d], class [%s], material [%s], variant [%s], map [%s]", (ent or tostring(ent)), self.handle, self.class, self.material, self.variant, GetMapName())) | |
| slab_missing_entity_white_list[ent] = true | |
| end | |
| self.bad_entity = true | |
| end | |
| function GetBadEntitySlabsOnMap() | |
| return MapGet("map", "Slab", function(o) return o.bad_entity end) | |
| end | |
| function IsSlabPassable(o) | |
| if o:GetSkewX() == 0 and o:GetSkewY() == 0 then | |
| local sp = o:GetSpotPos(o:GetSpotBeginIndex("Slab")) | |
| if SnapToVoxel(sp) == sp then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function ValidateSlabs() | |
| local slabs = MapGet("map", "Slab", nil, nil, const.gofPermanent) | |
| local killedSlabs = 0 | |
| local resetDestroyed = 0 | |
| local passedNbrs = {} | |
| local slabsWithDestroyedNbrs = {} | |
| for _, slab in ipairs(slabs) do | |
| local e = slab:GetEntity() | |
| local isInvisibleObj = (e == "InvisibleObject") --these slabs suppress themselves, so isVisible is not a guarantee that its suppressed by another | |
| local isInvisible = not slab.isVisible and not isInvisibleObj or slab.material == noneWallMat | |
| if isInvisible and not slab.room then | |
| --invisible manually placed slab - kill it | |
| DoneObject(slab) | |
| killedSlabs = killedSlabs + 1 | |
| elseif isInvisible and (slab.is_destroyed or slab.destroyed_neighbours ~= 0) then | |
| slab:ResetDestroyedState() | |
| resetDestroyed = resetDestroyed + 1 | |
| end | |
| if not isInvisible then | |
| --fix neighbour data | |
| if slab.is_destroyed then | |
| local function proc(nbr, i) | |
| passedNbrs[nbr] = true | |
| local f = GetNeigbhourSideFlagTowardMe(1 << (i - 1), nbr, slab) | |
| if (nbr.destroyed_neighbours & f) == 0 then | |
| nbr.destroyed_neighbours = nbr.destroyed_neighbours | f | |
| end | |
| end | |
| local nbrs = {slab:GetNeighbours()} | |
| nbrs[1], nbrs[2], nbrs[3], nbrs[4] = nbrs[3], nbrs[4], nbrs[1], nbrs[2] --so masks fit, l,r,t,b -> t,b,l,r | |
| for i = 1, 4 do | |
| local nbrs2 = nbrs[i] | |
| if IsValid(nbrs2) then | |
| proc(nbrs2, i) | |
| else | |
| for j, nbr in ipairs(nbrs2) do | |
| proc(nbr, i) | |
| end | |
| end | |
| end | |
| elseif slab.destroyed_neighbours ~= 0 then | |
| slabsWithDestroyedNbrs[slab] = true | |
| end | |
| end | |
| if IsKindOf(slab, "Lockpickable") then | |
| --save fixup | |
| if slab:IsBlockedDueToRoom() and slab.lockpickState ~= "blocked" then | |
| slab:SetlockpickState("blocked") | |
| end | |
| end | |
| end | |
| for slab, _ in pairs(slabsWithDestroyedNbrs) do | |
| if not passedNbrs[slab] then | |
| for i = 0, 3 do | |
| local dn = slab.destroyed_neighbours | |
| local f = 1 << i | |
| if (dn & f) ~= 0 then | |
| local nbr = slab:GetNeighbour(f) | |
| if nbr and not nbr.is_destroyed then | |
| slab.destroyed_neighbours = slab.destroyed_neighbours & ~f | |
| end | |
| end | |
| end | |
| end | |
| end | |
| if killedSlabs > 0 then | |
| print("Killed invisible roomless slabs ", killedSlabs) | |
| end | |
| if resetDestroyed > 0 then | |
| print("Repaired invisible destroyed slabs ", resetDestroyed) | |
| end | |
| EnumVolumes(function(v) | |
| if v.ceiling_mat ~= noneWallMat and not v.build_ceiling then | |
| v.ceiling_mat = nil --the set material is irrelevant since there is no ceiling, revert to default | |
| end | |
| end) | |
| end | |
| function OnMsg.ValidateMap() | |
| if IsEditorActive() and not mapdata.IsRandomMap and mapdata.GameLogic then | |
| ValidateSlabs() | |
| end | |
| end | |
| function testInvulnerableSlabs(lst) | |
| lst = lst or MapGet("map", "RoomCorner", const.efVisible, function(o) return o.forceInvulnerableBecauseOfGameRules end) | |
| DbgClear() | |
| local c = 0 | |
| for i = 1, #(lst or "") do | |
| DbgAddVector(lst[i]) | |
| c = c + 1 | |
| end | |
| print(c, #(lst or "")) | |
| end | |
| ------------------------------------ | |
| --maps are broken again, map fixing stuff | |
| ------------------------------------ | |
| local invulnerableMaterials = { | |
| ["Concrete"] = true | |
| } | |
| function FixInvulnerabilityStateOfOwnedSlabsOnMap() | |
| local function makeInvul(o, val) | |
| o.invulnerable = val | |
| o.forceInvulnerableBecauseOfGameRules = val | |
| end | |
| EnumVolumes(function(r) | |
| local invul = r.outside_border | |
| local firstFloor = not r:IsRoofOnly() and r.floor == 1 | |
| r:ForEachSpawnedObj(function(o, invul) | |
| if firstFloor and IsKindOf(o, "FloorSlab") or invul or invulnerableMaterials[o.material] and IsKindOf(o, "WallSlab") then | |
| makeInvul(o, true) | |
| else | |
| makeInvul(o, false) | |
| end | |
| end, invul) | |
| end) | |
| end | |
| --deleting slabs from editor deletes invisible slabs underneath | |
| if Platform.developer then | |
| function DeleteSelectedSlabsWithoutPropagation() | |
| for _, obj in ipairs(editor.GetSel() or empty_table) do | |
| rawset(obj, "dont_propagate_deletion", true) | |
| end | |
| editor.DelSelWithUndoRedo() | |
| end | |
| end |