myspace / Lua /Decor.lua
sirnii's picture
Upload 1816 files
b6a38d7 verified
raw
history blame
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 },
}