Download CommonLua/FXSource.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 18 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/FXSource.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/FXSource.lua
-
curl -L -o FXSource.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/FXSource.lua
18 kB
| local Behaviors | |
| local BehaviorsList | |
| MapVar("BehaviorLabels", {}) | |
| MapVar("BehaviorLabelsUpdate", {}) | |
| MapVar("BehaviorAreaUpdate", sync_set()) | |
| ---- | |
| local function GatherFXSourceTags() | |
| local tags = {} | |
| Msg("GatherFXSourceTags", tags) | |
| ForEachPreset("FXSourcePreset", function(preset, group, tags) | |
| for tag in pairs(preset.Tags) do | |
| tags[tag] = true | |
| end | |
| end, tags) | |
| return table.keys(tags, true) | |
| end | |
| ---- | |
| function FXSourceUpdate(self, game_state_changed, forced_match, forced_update) | |
| assert(not DisableSoundFX) | |
| if not IsValid(self) or not forced_update and self.update_disabled then | |
| return | |
| end | |
| local preset = self:GetPreset() or empty_table | |
| local fx_event = preset.Event | |
| if fx_event then | |
| local match = forced_match | |
| if match == nil then | |
| match = MatchGameState(self.game_states) | |
| end | |
| if not match then | |
| fx_event = false | |
| end | |
| end | |
| if fx_event and Behaviors then | |
| for name, set in pairs(preset.Behaviors) do | |
| local behavior = Behaviors[name] | |
| if behavior then | |
| local enabled = behavior:IsFXEnabled(self, preset) | |
| if set and not enabled or not set and enabled then | |
| fx_event = false | |
| break | |
| end | |
| end | |
| end | |
| end | |
| local current_fx = self.current_fx | |
| if fx_event == current_fx and not forced_update then | |
| return | |
| end | |
| if current_fx then | |
| PlayFX(current_fx, "end", self) | |
| end | |
| if fx_event then | |
| PlayFX(fx_event, "start", self) | |
| end | |
| self.current_fx = fx_event or nil | |
| if game_state_changed then | |
| if current_fx and not fx_event and preset.PlayOnce then | |
| self.update_disabled = true | |
| end | |
| self:OnGameStateChanged() | |
| end | |
| end | |
| ---- | |
| DefineClass.FXSourceBehavior = { | |
| __parents = { "PropertyObject" }, | |
| id = false, | |
| CreateLabel = false, | |
| LabelUpdateMsg = false, | |
| LabelUpdateDelay = 0, | |
| LabelUpdateDelayStep = 50, | |
| IsFXEnabled = return_true, | |
| } | |
| function FXSourceBehavior:GetEditorView() | |
| return Untranslated(self.id or self.class) | |
| end | |
| function FXSourceUpdateBehaviorLabels() | |
| local now = GameTime() | |
| local labels_to_update = BehaviorLabelsUpdate | |
| local sources_to_update = BehaviorAreaUpdate | |
| if not next(sources_to_update) then | |
| sources_to_update = false | |
| end | |
| local labels = BehaviorLabels | |
| local next_time = max_int64 | |
| local pass_edits | |
| local FXSourceUpdate = FXSourceUpdate | |
| for _, name in ipairs(labels_to_update) do | |
| local def = Behaviors[name] | |
| local label = def and labels[name] | |
| if label then | |
| local delay = def.LabelUpdateDelay | |
| local time = labels_to_update[name] | |
| if now < time then | |
| if next_time < time then | |
| next_time = time | |
| end | |
| elseif now <= time + delay then | |
| if not pass_edits then | |
| pass_edits = true | |
| SuspendPassEdits("FXSource") | |
| end | |
| if delay == 0 then | |
| for _, source in ipairs(label) do | |
| FXSourceUpdate(source) | |
| if sources_to_update then | |
| sources_to_update:remove(source) | |
| end | |
| end | |
| else | |
| local step = def.LabelUpdateDelayStep | |
| local steps = 1 + delay / step | |
| local BraidRandom = BraidRandom | |
| local seed = xxhash(name, MapLoadRandom) | |
| for i, source in ipairs(label) do | |
| local delta | |
| delta, seed = BraidRandom(seed, steps) | |
| local time_i = time + delta * step | |
| if now == time_i then | |
| FXSourceUpdate(source) | |
| if sources_to_update then | |
| sources_to_update:remove(source) | |
| end | |
| elseif now < time_i and next_time > time_i then | |
| next_time = time_i | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| if pass_edits then | |
| ResumePassEdits("FXSource") | |
| end | |
| return (next_time > now and next_time < max_int) and (next_time - now) or nil | |
| end | |
| --ErrorOnMultiCall("FXSourceUpdate") | |
| MapGameTimeRepeat("FXSourceUpdateBehaviorLabels", nil, function() | |
| local sleep = FXSourceUpdateBehaviorLabels() | |
| WaitWakeup(sleep) | |
| end) | |
| function FXSourceUpdateBehaviorLabel(id) | |
| if not BehaviorLabels[id] then | |
| return | |
| end | |
| local list = BehaviorLabelsUpdate | |
| if not list[id] then | |
| list[#list + 1] = id | |
| end | |
| list[id] = GameTime() | |
| WakeupPeriodicRepeatThread("FXSourceUpdateBehaviorLabels") | |
| end | |
| function FXSourceUpdateBehaviorArea() | |
| local sources_to_update = BehaviorAreaUpdate | |
| if not next(sources_to_update) then return end | |
| SuspendPassEdits("FXSource") | |
| local FXSourceUpdate = FXSourceUpdate | |
| for _, source in ipairs(sources_to_update) do | |
| FXSourceUpdate(source) | |
| end | |
| table.clear(sources_to_update, true) | |
| ResumePassEdits("FXSource") | |
| end | |
| function FXSourceUpdateBehaviorAround(id, pos, radius) | |
| local def = Behaviors[id] | |
| local label = def and BehaviorLabels[id] | |
| if not label then | |
| return | |
| end | |
| local list = BehaviorAreaUpdate | |
| MapForEach(pos, radius, "FXSource", function(source, label, list) | |
| if label[source] then | |
| list:insert(source) | |
| end | |
| end, label, list) | |
| if not next(list) then | |
| return | |
| end | |
| WakeupPeriodicRepeatThread("FXSourceUpdateBehaviorArea") | |
| end | |
| MapGameTimeRepeat("FXSourceUpdateBehaviorArea", nil, function() | |
| FXSourceUpdateBehaviorArea() | |
| WaitWakeup() | |
| end) | |
| function OnMsg.ClassesBuilt() | |
| ClassDescendants("FXSourceBehavior", function(class, def) | |
| local id = def.id | |
| if id then | |
| Behaviors = table.create_set(Behaviors, id, def) | |
| assert(not def.LabelUpdateMsg or def.CreateLabel) | |
| if def.CreateLabel and def.LabelUpdateMsg then | |
| OnMsg[def.LabelUpdateMsg] = function() | |
| FXSourceUpdateBehaviorLabel(id) | |
| end | |
| end | |
| end | |
| end) | |
| BehaviorsList = Behaviors and table.keys(Behaviors, true) | |
| end | |
| local function RegisterBehaviors(source, labels, preset) | |
| if not Behaviors then | |
| return | |
| end | |
| preset = preset or source:GetPreset() | |
| if not preset or not preset.Event then | |
| return | |
| end | |
| for name, set in pairs(preset.Behaviors) do | |
| local behavior = Behaviors[name] | |
| if behavior and behavior.CreateLabel then | |
| labels = labels or BehaviorLabels | |
| local label = labels[name] | |
| if not label then | |
| labels[name] = {source, [source] = true} | |
| elseif not label[source] then | |
| label[#label + 1] = source | |
| label[source] = true | |
| end | |
| end | |
| end | |
| end | |
| function FXSourceRebuildLabels() | |
| BehaviorLabels = {} | |
| BehaviorLabelsUpdate = BehaviorLabelsUpdate or {} | |
| MapForEach("map", "FXSource", const.efMarker, RegisterBehaviors, BehaviorLabels) | |
| end | |
| local function UnregisterBehaviors(source, labels) | |
| for name, label in pairs(labels or BehaviorLabels) do | |
| if label[source] then | |
| table.remove_value(label, source) | |
| label[source] = nil | |
| end | |
| end | |
| end | |
| ---- | |
| DefineClass.FXBehaviorChance = { | |
| __parents = { "FXSourceBehavior" }, | |
| id = "Chance", | |
| CreateLabel = true, | |
| properties = { | |
| { category = "FX: Chance", id = "EnableChance", name = "Chance", editor = "number", default = 100, min = 0, max = 100, scale = "%", slider = true }, | |
| { category = "FX: Chance", id = "ChangeInterval", name = "Change Interval", editor = "number", default = 0, min = 0, scale = function(self) return self.IntervalScale end, help = "Time needed to change the chance result." }, | |
| { category = "FX: Chance", id = "IntervalScale", name = "Interval Scale", editor = "choice", default = false, items = function() return table.keys(const.Scale, true) end }, | |
| { category = "FX: Chance", id = "IsGameTime", name = "Game Time", editor = "bool", default = false, help = "Change interval time type. Game Time is needed for events messing with the game logic." }, | |
| }, | |
| } | |
| function FXBehaviorChance:IsFXEnabled(source, preset) | |
| local chance = preset and preset.EnableChance or 100 | |
| if chance >= 100 then return true end | |
| local time = (preset.IsGameTime and GameTime() or RealTime()) / Max(1, preset.ChangeInterval or 0) | |
| local seed = xxhash(source.handle, time, MapLoadRandom) | |
| return (seed % 100) < chance | |
| end | |
| ---- | |
| DefineClass.FXSourcePreset = { | |
| __parents = { "Preset" }, | |
| properties = { | |
| { category = "FX", id = "Event", name = "FX Event", editor = "combo", default = false, items = function(fx) return ActionFXClassCombo(fx) end }, | |
| { category = "FX", id = "GameStates", name = "Game State", editor = "set", default = set(), three_state = true, items = function() return GetGameStateFilter() end }, | |
| { category = "FX", id = "PlayOnce", name = "Play Once", editor = "bool", default = false, help = "Kill the object if the FX is no more matched after changing game state", }, | |
| { category = "FX", id = "EditorPlay", name = "Editor Play", editor = "choice", default = "force play", items = {"no change", "force play", "force stop"}, developer = true }, | |
| { category = "FX", id = "Entity", name = "Editor Entity", editor = "combo", default = false, items = function() return GetAllEntitiesCombo() end }, | |
| { category = "FX", id = "Tags", name = "Tags", editor = "set", default = set(), items = GatherFXSourceTags, help = "Help the game logic find this source if needed", }, | |
| { category = "FX", id = "Behaviors", name = "Behaviors", editor = "set", default = set(), items = function() return BehaviorsList end, three_state = true, }, | |
| { category = "FX", id = "ConditionText", name = "Condition", editor = "text", default = "", read_only = true, no_edit = function(self) return not next(self.Behaviors) end }, | |
| { category = "FX", id = "Actor", name = "FX Actor", editor = "combo", default = false, items = function(fx) return ActorFXClassCombo(fx) end}, | |
| { category = "FX", id = "Scale", name = "Scale", editor = "number", default = false }, | |
| { category = "FX", id = "Color", name = "Color", editor = "color", default = false }, | |
| { category = "FX", id = "FXButtons", editor = "buttons", default = false, buttons = {{ name = "Map Select", func = "ActionSelect" }} }, | |
| }, | |
| GlobalMap = "FXSourcePresets", | |
| EditorMenubarName = "FX Sources", | |
| EditorMenubar = "Editors.Art", | |
| EditorIcon = "CommonAssets/UI/Icons/atoms electron physic.png", | |
| } | |
| function FXSourcePreset:GetConditionText() | |
| local texts = {} | |
| for name, set in pairs(self.Behaviors) do | |
| if set then | |
| texts[#texts + 1] = name | |
| else | |
| texts[#texts + 1] = "not " .. name | |
| end | |
| end | |
| return table.concat(texts, " and ") | |
| end | |
| function FXSourcePreset:ActionSelect() | |
| if GetMap() == "" then | |
| return | |
| end | |
| editor.ClearSel() | |
| editor.AddToSel(MapGet("map", "FXSource", const.efMarker, function(obj, id) | |
| return obj.FxPreset == id | |
| end, self.id)) | |
| end | |
| function FXSourcePreset:OnEditorSetProperty(prop_id) | |
| if GetMap() == "" then | |
| return | |
| end | |
| local prop = self:GetPropertyMetadata(prop_id) | |
| if not prop or prop.category ~= "FX" then | |
| return | |
| end | |
| MapForEach("map", "FXSource", const.efMarker, function(obj, self) | |
| if obj.FxPreset == self.id then | |
| obj:SetPreset(self) | |
| end | |
| end, self) | |
| end | |
| function FXSourcePreset:GetProperties() | |
| local orig_props = Preset.GetProperties(self) | |
| local props = orig_props | |
| if Behaviors then | |
| for name, set in pairs(self.Behaviors) do | |
| local classdef = Behaviors[name] | |
| local propsi = classdef and classdef.properties | |
| if propsi and #propsi > 0 then | |
| if props == orig_props then | |
| props = table.icopy(props) | |
| end | |
| props = table.iappend(props, propsi) | |
| end | |
| end | |
| end | |
| return props | |
| end | |
| DefineClass("FXSourceAutoResolve") | |
| DefineClass.FXSource = { | |
| __parents = { "Object", "FXObject", "EditorEntityObject", "EditorCallbackObject", "EditorTextObject", "FXSourceAutoResolve" }, | |
| flags = { efMarker = true, efWalkable = false, efCollision = false, efApplyToGrids = false }, | |
| editor_text_offset = point(0, 0, -guim), | |
| editor_text_style = "FXSourceText", | |
| editor_entity = "ParticlePlaceholder", | |
| entity = "InvisibleObject", | |
| properties = { | |
| { category = "FX Source", id = "FxPreset", name = "FX Preset", editor = "preset_id", default = false, preset_class = "FXSourcePreset", buttons = {{ name = "Start", func = "ActionStart" }, { name = "End", func = "ActionEnd" }} }, | |
| { category = "FX Source", id = "Playing", name = "Playing", editor = "bool", default = false, dont_save = true, read_only = true }, | |
| }, | |
| current_fx = false, | |
| update_disabled = false, | |
| game_states = false, | |
| prefab_no_fade_clamp = true, | |
| } | |
| function FXSource:GetPlaying() | |
| return not not self.current_fx | |
| end | |
| function FXSource:EditorGetText() | |
| return (self.FxPreset or "") ~= "" and self.FxPreset or self.class | |
| end | |
| function FXSource:GetEditorLabel() | |
| local label = self.class | |
| if (self.FxPreset or "") ~= "" then | |
| label = label .. " (" .. self.FxPreset .. ")" | |
| end | |
| return label | |
| end | |
| function FXSource:GetError() | |
| if not self.FxPreset then | |
| return "FX source has no FX preset assigned." | |
| end | |
| end | |
| function FXSource:GameInit() | |
| if ChangingMap then | |
| return -- sound FX are disabled during map changing | |
| end | |
| FXSourceUpdate(self) | |
| end | |
| FXSourceAutoResolve.EditorExit = FXSourceUpdate | |
| function FXSourceAutoResolve:EditorEnter() | |
| CreateRealTimeThread(function() | |
| WaitChangeMapDone() | |
| if GetMap() == "" then | |
| return | |
| end | |
| local match | |
| if IsEditorActive() then | |
| local preset = self:GetPreset() or empty_table | |
| local editor_play = preset.EditorPlay | |
| if editor_play == "force play" then | |
| match = true | |
| elseif editor_play == "force stop" then | |
| match = false | |
| end | |
| end | |
| FXSourceUpdate(self, nil, match) | |
| end) | |
| end | |
| function FXSourceAutoResolve:OnEditorSetProperty(prop_id) | |
| if prop_id == "FxPreset" then | |
| FXSourceUpdate(self, nil, self:GetPlaying(), true) | |
| self:EditorTextUpdate() | |
| end | |
| end | |
| MapVar("FXSourceStates", false) | |
| MapVar("FXSourceUpdateThread", false) | |
| function FXSource:SetGameStates(states) | |
| states = states or false | |
| local prev_states = self.game_states | |
| if prev_states == states then | |
| return | |
| end | |
| local counters = FXSourceStates or {} | |
| FXSourceStates = counters | |
| for state in pairs(states) do | |
| counters[state] = (counters[state] or 0) + 1 | |
| end | |
| for state in pairs(prev_states) do | |
| local count = counters[state] or 0 | |
| assert(count > 0) | |
| if count > 1 then | |
| counters[state] = count - 1 | |
| else | |
| counters[state] = nil | |
| end | |
| end | |
| self.game_states = states or nil | |
| end | |
| function FXSource:OnGameStateChanged() | |
| end | |
| function FXSource:SetFxPreset(id) | |
| if (id or "") == "" then | |
| self.FxPreset = nil | |
| self:SetPreset() | |
| return | |
| end | |
| self.FxPreset = id | |
| self:SetPreset(FXSourcePresets[id]) | |
| end | |
| function FXSource:SetPreset(preset) | |
| UnregisterBehaviors(self) | |
| if not preset then | |
| self:SetGameStates(false) | |
| self:ChangeEntity(FXSource.entity) | |
| self.fx_actor_class = nil | |
| self:SetState("idle") | |
| self:SetScale(100) | |
| self:SetColorModifier(const.clrNoModifier) | |
| FXSourceUpdate(self, nil, false) | |
| return | |
| end | |
| RegisterBehaviors(self, nil, preset) | |
| self:SetGameStates(preset.GameStates) | |
| if preset.Entity then | |
| self.editor_entity = preset.Entity | |
| if IsEditorActive() then | |
| self:ChangeEntity(preset.Entity) | |
| end | |
| end | |
| if preset.Actor then | |
| self.fx_actor_class = preset.Actor | |
| end | |
| if preset.State then | |
| self:SetState(preset.State) | |
| end | |
| if preset.Scale then | |
| self:SetScale(preset.Scale) | |
| end | |
| if preset.Color then | |
| self:SetColorModifier(preset.Color) | |
| end | |
| if self.current_fx then | |
| FXSourceUpdate(self, nil, true) | |
| end | |
| end | |
| function FXSource:GetPreset() | |
| return FXSourcePresets[self.FxPreset] | |
| end | |
| function FXSource:Done() | |
| self:SetGameStates(false) -- unregister from FXSourceStates | |
| FXSourceUpdate(self, nil, false) | |
| UnregisterBehaviors(self) | |
| end | |
| function FXSource:ActionStart() | |
| FXSourceUpdate(self, nil, true, true) | |
| ObjModified(self) | |
| end | |
| function FXSource:ActionEnd() | |
| FXSourceUpdate(self, nil, false, true) | |
| ObjModified(self) | |
| end | |
| local function FXSourceUpdateAll(area, ...) | |
| SuspendPassEdits("FXSource") | |
| MapForEach(area, "FXSource", const.efMarker, FXSourceUpdate, ...) | |
| ResumePassEdits("FXSource") | |
| end | |
| function FXSourceUpdateOnGameStateChange(delay) | |
| if GetMap() == "" then | |
| return | |
| end | |
| delay = delay or GameTime() == 0 and 0 or config.MapSoundUpdateDelay or 1000 | |
| DeleteThread(FXSourceUpdateThread) | |
| FXSourceUpdateThread = CreateGameTimeThread(function(delay) | |
| if delay <= 0 then | |
| FXSourceUpdateAll("map", "game_state_changed") | |
| else | |
| local boxes = GetMapBoxesCover(config.MapSoundBoxesCoverParts or 8, "MapSoundBoxesCover") | |
| local count = #boxes | |
| for i, box in ipairs(boxes) do | |
| FXSourceUpdateAll(box, "game_state_changed") | |
| Sleep((i + 1) * delay / count - i * delay / count) | |
| end | |
| end | |
| FXSourceUpdateThread = false | |
| end, delay) | |
| end | |
| function OnMsg.ChangeMapDone() | |
| FXSourceUpdateOnGameStateChange() | |
| end | |
| function OnMsg.GameStateChanged(changed) | |
| if ChangingMap or GetMap() == "" then return end | |
| local GameStateDefs, FXSourceStates = GameStateDefs, FXSourceStates | |
| if not FXSourceStates then return end | |
| for id in sorted_pairs(changed) do | |
| if GameStateDefs[id] and (FXSourceStates[id] or 0) > 0 then -- if a game state is changed, update sound sources | |
| FXSourceUpdateOnGameStateChange() | |
| break | |
| end | |
| end | |
| end | |
| if Platform.developer then | |
| local function ReplaceWithSources(objs, fx_src_preset) | |
| if #(objs or "") == 0 then | |
| return 0 | |
| end | |
| XEditorUndo:BeginOp{ objects = objs, name = "ReplaceWithFXSource" } | |
| editor.ClearSel() | |
| local sources = {} | |
| for _, obj in ipairs(objs) do | |
| local pos, axis, angle, scale, coll = obj:GetPos(), obj:GetAxis(), obj:GetAngle(), obj:GetScale(), obj:GetCollectionIndex() | |
| DoneObject(obj) | |
| local src = PlaceObject("FXSource") | |
| src:SetGameFlags(const.gofPermanent) | |
| src:SetAxisAngle(axis, angle) | |
| src:SetScale(scale) | |
| src:SetPos(pos) | |
| src:SetCollectionIndex(coll) | |
| src:SetFxPreset(fx_src_preset) | |
| sources[#sources + 1] = src | |
| end | |
| Msg("EditorCallback", "EditorCallbackPlace", sources) | |
| editor.AddToSel(sources) | |
| XEditorUndo:EndOp(sources) | |
| return #sources | |
| end | |
| function ReplaceMapSounds(snd_name, fx_src_preset) | |
| local objs = MapGet("map", "SoundSource", function(obj) | |
| for _, entry in ipairs(obj.Sounds) do | |
| if entry.Sound == snd_name then | |
| return true | |
| end | |
| end | |
| end) | |
| local count = ReplaceWithSources(objs, fx_src_preset) | |
| print(count, "sounds replaced and selected") | |
| end | |
| function ReplaceMapParticles(prtcl_name, fx_src_preset) | |
| local objs = MapGet("map", "ParSystem", function(obj) | |
| return obj:GetParticlesName() == prtcl_name | |
| end) | |
| local count = ReplaceWithSources(objs, fx_src_preset) | |
| print(count, "particles replaced and selected") | |
| end | |
| end | |