Download Lua/Decor.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 15 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/Decor.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/Lua/Decor.lua
-
curl -L -o Decor.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/Lua/Decor.lua
15 kB
| DefineClass("Tree", "EntityClass") | |
| DefineClass.BaseFlag = { | |
| __parents = { "Object" }, | |
| } | |
| function BaseFlag:Init() | |
| -- commented variants do not produce good results (half of the placed objects have animations phase very close to each other) | |
| --local phase = BraidRandom(EncodeVoxelPos(self) % max_int, duration) | |
| --local phase = BraidRandom(point_pack(self:GetPosXYZ()) % max_int, duration) | |
| local duration = GetAnimDuration(self:GetEntity(), self:GetAnim()) | |
| local phase = AsyncRand(duration) | |
| self:SetAnimPhase(1, phase) | |
| end | |
| function OnMsg.ClassesGenerate() | |
| table.iappend(Light.properties, { | |
| {category = "Visuals", id = "NightOnly", name = "Night Only", editor = "bool", default = false}, | |
| }) | |
| end | |
| function GetStoredNightOnlyIntensity0(self) | |
| return self._nightonly_intensity0 | |
| end | |
| function SetStoredNightOnlyIntensity0(self, intensity) | |
| self._nightonly_intensity0 = intensity | |
| end | |
| function GetStoredNightOnlyIntensity1(self) | |
| return self._nightonly_intensity1 | |
| end | |
| function SetStoredNightOnlyIntensity1(self, intensity) | |
| self._nightonly_intensity1 = intensity | |
| end | |
| function ApplyNightOnly(night) | |
| MapForEach("map", "Light", function(light) | |
| if light.NightOnly then | |
| if night then | |
| if rawget(light, "_nightonly_intensity0") then | |
| light.SetIntensity0, light.GetIntensity0 = nil, nil | |
| light.SetIntensity1, light.GetIntensity1 = nil, nil | |
| light:SetIntensity0(light._nightonly_intensity0) | |
| light:SetIntensity1(light._nightonly_intensity1) | |
| light._nightonly_intensity0 = false | |
| light._nightonly_intensity1 = false | |
| end | |
| else | |
| if not rawget(light, "_nightonly_intensity0") then | |
| light._nightonly_intensity0 = light:GetIntensity0() | |
| light._nightonly_intensity1 = light:GetIntensity1() | |
| light:SetIntensity0(0) | |
| light:SetIntensity1(0) | |
| light.GetIntensity0, light.SetIntensity0 = GetStoredNightOnlyIntensity0, SetStoredNightOnlyIntensity0 | |
| light.GetIntensity1, light.SetIntensity1 = GetStoredNightOnlyIntensity1, SetStoredNightOnlyIntensity1 | |
| end | |
| end | |
| end | |
| end) | |
| end | |
| function OnMsg.GameEnterEditor() | |
| ApplyNightOnly(true) | |
| end | |
| function OnMsg.GameExitEditor() | |
| ApplyNightOnly(GameState.Night) | |
| end | |
| function OnMsg.GameStateChanged(changed) | |
| if changed.Night then | |
| ApplyNightOnly(true) | |
| elseif changed.Day or changed.Sunrise or changed.Sunset then | |
| ApplyNightOnly(false) | |
| end | |
| end | |
| function OnMsg.NewMapLoaded() | |
| if GameState.Night then | |
| ApplyNightOnly(true) | |
| elseif GameState.Day or GameState.Sunrise or GameState.Sunset then | |
| ApplyNightOnly(false) | |
| end | |
| end | |
| --[[ | |
| To create objects which play sound depending on which state they are placed on the map: | |
| - manually create a class for them inheriting DecorStateFXObject, or set DecorStateFXObject as parent class for them in the ArtSpec | |
| - in the FX editor, create a new ActionFXSound for each of the states you want to associate with sounds, with the following properties | |
| - Action: DecorState | |
| - Moment: start | |
| - Actor: choose the class of the object | |
| - Target: type the name of the state | |
| - Sound: choose the sound bank from the list | |
| - AttachToObj: check | |
| - if you want the sound to stop when the object switches to another state: | |
| - in EndRules: click "Add", then in EndMoment add "end" | |
| ]] | |
| local function GetParticlesType(pattern) | |
| local items = {} | |
| for name in pairs(ParticleSystemPresets) do | |
| if string.match(name, pattern) then | |
| table.insert(items, name) | |
| end | |
| end | |
| table.insert(items, "") | |
| table.sort(items) | |
| return items | |
| end | |
| DefineClass.DecorGameStatesFilter = { | |
| __parents = {"Object", "EditorTextObject"}, | |
| properties = { | |
| { category = "DecorStateFXObject", id = "ActivationRequiredStates", name = "Map States", | |
| editor = "set", three_state = true, default = false, | |
| items = function() return GetGameStateFilter() end, | |
| buttons = {{name = "Check Game States", func = "PropertyDefGameStatefSetCheck"}}, | |
| help = "Click once for the states required to be enabled(green), twice for the states required to be disabled(red). All other states are don't care.", | |
| }, | |
| }, | |
| editor_text_offset = point(0, 0, 150 * guic), | |
| old_map_states = false, -- objects's map states we are interested in during the last GameState | |
| activated = false, | |
| } | |
| function DecorGameStatesFilter:GameInit() | |
| self.old_map_states = {} | |
| for state in pairs(self.ActivationRequiredStates) do | |
| self.old_map_states[state] = not not GameState[state] | |
| end | |
| self:UpdateGameState() | |
| end | |
| function DecorGameStatesFilter:UpdateGameState() | |
| if not self.old_map_states then return end | |
| local is_destroyed = IsGenericObjDestroyed(self) | |
| local should_be_active = not is_destroyed | |
| if should_be_active then | |
| for state, required_value in pairs(self.ActivationRequiredStates) do | |
| local game_state_value = not not GameState[state] | |
| should_be_active = should_be_active and (required_value == game_state_value) | |
| if game_state_value ~= self.old_map_states[state] then | |
| self.old_map_states[state] = game_state_value | |
| PlayFX(state, game_state_value and "start" or "end", self, self:GetStateText()) | |
| end | |
| end | |
| end | |
| self.activated = should_be_active | |
| self:OnGameStateUpdated() | |
| self:EditorTextUpdate() | |
| end | |
| local efVisible = const.efVisible | |
| local efVisibleNot = bnot(efVisible) | |
| function DecorGameStatesFilter:SetEnumFlags(flags) | |
| if band(flags, efVisible) ~= 0 then | |
| if not IsEditorActive() and self:GetEntity() == "ParticleSoundPlaceholder" then | |
| flags = band(flags , efVisibleNot) | |
| end | |
| end | |
| Object.SetEnumFlags(self, flags) | |
| end | |
| function DecorGameStatesFilter:OnGameStateUpdated() | |
| if self:GetNumStates() == 0 then | |
| if self.activated and IsEditorActive() then | |
| self:SetEnumFlags(efVisible) | |
| else | |
| self:ClearEnumFlags(efVisible) | |
| end | |
| end | |
| end | |
| function DecorGameStatesFilter:EditorEnter() | |
| EditorTextObject.EditorEnter(self) | |
| if self:GetNumStates() == 0 then | |
| self:SetEnumFlags(efVisible) | |
| end | |
| end | |
| function DecorGameStatesFilter:EditorExit() | |
| EditorTextObject.EditorExit(self) | |
| self:UpdateGameState() | |
| end | |
| function DecorGameStatesFilter:EditorGetText() | |
| local text = "" | |
| if not MatchGameState(self.ActivationRequiredStates) then | |
| local mismatch_states = {"Mismatch States:"} | |
| for state, active in pairs(self.ActivationRequiredStates) do | |
| local game_state_active = not not GameState[state] | |
| if active ~= game_state_active then | |
| table.insert(mismatch_states, state) | |
| end | |
| end | |
| text = string.format("%s\n%s", text, table.concat(mismatch_states, " ")) | |
| end | |
| return text | |
| end | |
| function DecorGameStatesFilter:EditorGetTextColor() | |
| return MatchGameState(self.ActivationRequiredStates) and EditorTextObject.EditorGetTextColor(self) or const.clrRed | |
| end | |
| DefineClass.DecorStateFXObject = { | |
| __parents = { "Object", "FXObject", "DecorGameStatesFilter" }, | |
| properties = { | |
| { id = "Pos", name = "Pos", editor = "point", default = InvalidPos(), help = "in meters", scale = "m" }, | |
| { id = "Angle", editor = "number", default = 0, min = 0, max = 360*60 - 1, slider = true, scale = "deg"}, | |
| { category = "DecorStateFXObject", id = "Preset", editor = "dropdownlist", default = "", | |
| items = function(self) return GetParticlesType(self.particles_pattern) end, | |
| }, | |
| }, | |
| place_category = false, | |
| place_name = false, | |
| particles_pattern = "", | |
| entity_scale = 100, | |
| particles = false, | |
| } | |
| function DecorStateFXObject:Init() | |
| self:SetScale(self.entity_scale) | |
| end | |
| function DecorStateFXObject:GameInit() | |
| CreateGameTimeThread(function() | |
| Sleep(1) -- workaround for sound FX being disabled during map load, pending investigation | |
| if IsValid(self) then | |
| self:SetState(self:GetState()) | |
| self:ForEachAttach("Light", Stealth_HandleLight) | |
| ResetVoxelStealthParamsCache() | |
| end | |
| end) | |
| self:PlaceParticles() | |
| end | |
| function DecorStateFXObject:Done() | |
| self:DestroyParticles() | |
| end | |
| function DecorStateFXObject:SetPos(pos, ...) | |
| Object.SetPos(self, pos, ...) | |
| if self.particles then | |
| self.particles:SetPos(pos, ...) | |
| end | |
| end | |
| function DecorStateFXObject:SetAngle(angle, ...) | |
| Object.SetAngle(self, angle, ...) | |
| if self.particles then | |
| self.particles:SetAngle(angle, ...) | |
| end | |
| end | |
| function DecorStateFXObject:DestroyParticles() | |
| if not self.particles then return end | |
| DoneObject(self.particles) | |
| self.particles = false | |
| end | |
| function DecorStateFXObject:PlaceParticles() | |
| if not ParticleSystemPresets[self.Preset] then return end | |
| self.particles = PlaceParticles(self.Preset) | |
| self.particles.HelperEntity = false | |
| self.particles:SetPos(self:GetPos()) | |
| self.particles:SetAngle(self:GetAngle()) | |
| end | |
| function DecorStateFXObject:OnEditorSetProperty(prop_id) | |
| if prop_id == "Preset" then | |
| self:DestroyParticles() | |
| self:PlaceParticles() | |
| end | |
| end | |
| function DecorStateFXObject:SetState(...) | |
| PlayFX("DecorState", "end", self, self:GetStateText()) | |
| Object.SetState(self, ...) | |
| if self:GetEnumFlags(efVisible) ~= 0 then | |
| if not self:IsKindOf("AutoAttachObject") or self:GetAutoAttachMode() ~= "OFF" then | |
| -- NOTE: some lights are playng their sounds on their single "idle" anim despite beiing turner OFF | |
| PlayFX("DecorState", "start", self, self:GetStateText()) | |
| end | |
| end | |
| end | |
| function DecorStateFXObject:OnGameStateUpdated() | |
| DecorGameStatesFilter.OnGameStateUpdated(self) | |
| if self.particles then | |
| if self.activated then | |
| self.particles:SetEnumFlags(efVisible) | |
| else | |
| self.particles:ClearEnumFlags(efVisible) | |
| end | |
| end | |
| end | |
| function DecorStateFXObject:EditorEnter() | |
| DecorGameStatesFilter.EditorEnter(self) | |
| if self.particles then | |
| self.particles:SetEnumFlags(efVisible) | |
| end | |
| end | |
| function DecorStateFXObject:OnXFilterSetVisible(visible) | |
| if self.particles then | |
| if visible then | |
| self.particles:SetEnumFlags(efVisible) | |
| else | |
| self.particles:ClearEnumFlags(efVisible) | |
| end | |
| end | |
| end | |
| function OnMsg.GatherPlaceCategories(list) | |
| ClassDescendants("DecorStateFXObject", function(class_name, class) | |
| if class.place_category or class.place_name then | |
| local place_name = class.place_name or class_name | |
| local category = class.place_category or "Effects" | |
| table.insert(list, {class_name, place_name, "Common", category}) | |
| end | |
| end) | |
| end | |
| DefineClass.DecorStateFXObjectNoSound = { | |
| __parents = {"DecorStateFXObject", "EditorVisibleObject", "EditorTextObject", "StripCObjectProperties"}, | |
| entity = "ParticleSoundPlaceholder", | |
| entity_scale = 10, | |
| color_modifier = RGB(100, 100, 0), | |
| } | |
| function DecorStateFXObjectNoSound:Init() | |
| self:SetColorModifier(self.color_modifier) | |
| end | |
| DefineClass.DecorStateFXObjectWithSound = { | |
| __parents = {"DecorStateFXObject", "SoundSource"}, | |
| properties = { | |
| -- Required for map saving purposes only. | |
| { id = "CollectionIndex", name = "Collection Index", editor = "number", default = 0, read_only = true }, | |
| { id = "CollectionName", name = "Collection Name", editor = "choice", | |
| items = GetCollectionNames, default = "", dont_save = true, | |
| buttons = {{ name = "Collection Editor", func = function(self) | |
| if self:GetRootCollection() then | |
| OpenCollectionEditorAndSelectCollection(self) | |
| end | |
| end }}, | |
| }, | |
| }, | |
| entity = "ParticleSoundPlaceholder", | |
| entity_scale = 10, | |
| sounds_pattern = "", | |
| editor_text_offset = point(0, 0, 150 * guic), | |
| } | |
| function DecorStateFXObjectWithSound:EditorGetText() | |
| return string.format("%s\n%s", DecorGameStatesFilter.EditorGetText(self), SoundSource.EditorGetText(self)) | |
| end | |
| function DecorStateFXObjectWithSound:EditorGetTextColor() | |
| return MatchGameState(self.ActivationRequiredStates) and DecorStateFXObject.EditorGetTextColor(self) or const.clrRed | |
| end | |
| function DecorStateFXObjectWithSound:Init() | |
| local sounds = self:GetProperty("Sounds") | |
| if sounds then return end | |
| local sounds = {} | |
| for _, entry in ipairs(Presets.SoundPreset.ENVIRONMENT) do | |
| if string.match(entry.id, self.sounds_pattern) then | |
| table.insert(sounds, entry.id) | |
| end | |
| end | |
| self:AddSoundsEntry(sounds[1 + self:Random(#sounds)], nil, self.ActivationRequiredStates) | |
| end | |
| function DecorStateFXObjectWithSound:GetAvailableSounds(ignore_editor) | |
| if not self.activated then | |
| return | |
| end | |
| return SoundSourceBase.GetAvailableSounds(self, ignore_editor) | |
| end | |
| function OnMsg.GameStateChanged(changed) | |
| if CurrentMap == "" or ChangingMap then return end | |
| MapForEach("map", "DecorGameStatesFilter", function(decor) | |
| decor:UpdateGameState() | |
| end) | |
| end | |
| function OnMsg.GatherFXActions(list) | |
| list[#list+1] = "DecorState" | |
| end | |
| DefineClass.DecorStateFXAutoAttachObject = { | |
| __parents = {"DecorStateFXObject", "AutoAttachObject"}, | |
| } | |
| function DecorStateFXAutoAttachObject:SetState(...) | |
| -- NOTE: DecorStateFXObject expects GetStateText() to have the old anim so it needs to be called first | |
| DecorStateFXObject.SetState(self, ...) | |
| AutoAttachObject.SetState(self, ...) | |
| end | |
| DefineClass.ShadowOnlyObject = { | |
| __parents = {"EditorObject", "Object"}, | |
| } | |
| function ShadowOnlyObject:Init() | |
| self:Hide() | |
| end | |
| function ShadowOnlyObject:Hide() | |
| self:SetGameFlags(const.gofSolidShadow) | |
| self:SetOpacity(0) | |
| end | |
| function ShadowOnlyObject:Show() | |
| self:ClearGameFlags(const.gofSolidShadow) | |
| self:SetOpacity(100) | |
| end | |
| function ShadowOnlyObject:EditorEnter() | |
| self:Show() | |
| end | |
| function ShadowOnlyObject:EditorExit() | |
| self:Hide() | |
| end | |
| -- use CreateShadowOnlyVersion as parent in ArtSpec to create ShadowOnly version using the same entity | |
| DefineClass.CreateShadowOnlyVersion = {} | |
| function OnMsg.ClassesGenerate(classdefs) | |
| for class_name, classdef in pairs(classdefs) do | |
| local idx = table.find(classdef.__parents, "CreateShadowOnlyVersion") | |
| if idx then | |
| local parents = table.copy(classdef.__parents) | |
| parents[idx] = "ShadowOnlyObject" | |
| local new_class_def = DefineClass(class_name .. "_ShadowOnly", table.unpack(parents)) | |
| new_class_def.entity = classdef.entity or class_name | |
| end | |
| end | |
| end | |
| -------------- specific objects -------------- | |
| DefineClass.Laptop = { | |
| __parents = { "DecorStateFXObject" }, | |
| entity = "Corp_Laptop_01", | |
| } | |
| DefineClass.WW2_Flag = { | |
| __parents = { "DecorStateFXObject" }, | |
| fx_actor_class = "WW2_Flag", | |
| } | |
| DefineClass("WW2_FlagHill_France", "WW2_Flag") | |
| DefineClass("WW2_FlagHill_Legion", "WW2_Flag") | |
| DefineClass.Shanty_WindTower = { | |
| __parents = {"GroundAlignedObj", "Canvas", "DecorStateFXObject", "AnimMomentHook"}, | |
| fx_actor_class = "Shanty_WindTower", | |
| anim_moments_hook = true, | |
| anim_moments_single_thread = true, | |
| } | |
| function Shanty_WindTower:SetState(...) | |
| DecorStateFXObject.SetState(self, ...) | |
| AnimMomentHook.SetState(self, ...) | |
| end | |
| local function SetMaterialTypeToClassDef(cls) | |
| local def = g_Classes[cls] | |
| if def then | |
| def.material_type = table.get(EntityData, cls, "entity", "material_type") | |
| end | |
| end | |
| function OnMsg.EntitiesLoaded() | |
| local lst = ClassDescendantsList("WW2_Flag") | |
| for _, cls in ipairs(lst or empty_table) do | |
| SetMaterialTypeToClassDef(cls) | |
| end | |
| end | |
| DefineClass("SatelliteViewWater", "WaterObj") | |
| DefineClass.WalkableEntity = { | |
| __parents = { "CObject" }, | |
| flags = {efPathSlab = true}, | |
| } | |
| DefineClass.Vehicle = { | |
| __parents = { "CombatObject", "AutoAttachObject" }, | |
| } | |
| DefineClass.HorizonObject = { | |
| __parents = { "CObject" }, | |
| max_allowed_radius = 200 * guim, | |
| flags = { efSelectable = false, cofComponentCollider = false }, | |
| } |