Download CommonLua/Classes/Modifiers.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 21 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Classes/Modifiers.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Classes/Modifiers.lua
-
curl -L -o Modifiers.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Classes/Modifiers.lua
21 kB
| RecursiveCallMethods.OnModifiableValueChanged = "call" | |
| DefineClass.Modifiable = { | |
| __parents = { "ContinuousEffectContainer" }, | |
| -- Each modifiable property has modifiable = true in its property definition; it must not have Get<prop>/Set<prop> methods | |
| -- A base_<prop> member is autogenerated, it is initialized with the modifiable prop's default value | |
| -- Usage: To add/remove modifiers, create ObjectModifier/MultipleObjectsModifier Modifier objects. | |
| -- You must not directly call UpdateModifier, the Modifier objects above call it internally when needed. | |
| -- Modifier objects will apply the modifier when created, remove it when destroyed, and provide TurnOff/TurnOn methods. | |
| -- Important members you need to set when creating a Modifier object: | |
| -- a) prop - name of the property affected | |
| -- b) target(s) - specifies the object(s) to be modified | |
| -- c) add (optional) - additive modifier (applied first) | |
| -- d) mul (optional) - multiplicative modifier, base 1000 | |
| -- e) subobject_path (optional) - path to subobject to modify; { key1, key2 } will affect obj[key1][key2] | |
| -- SetBase("property_name", value) function is provided | |
| -- It can be used to set the base_<prop> value, which is the value upon which modifications are applied. | |
| -- This recalculates the modifiable property. | |
| -- OnModifiableValueChanged(prop) callback is provided | |
| -- It is called whenever a modifiable property's value changed, either due to modifiers or the base changing. | |
| -- It is a part of the RecursiveCallMethods group, hence all of a classes' parents implementations will get called, including the classes' own implementation. | |
| -- (the parents' implementations are called first) | |
| -- The calculation performed by modifiers is provided by the Modifier class and can be overridden | |
| -- The default Modifier class uses parameters called mul and add and performs | |
| -- value = MulDivRound(base_value, mul, 1000) + add | |
| -- with multiplicative modifications all multiplied together. | |
| -- For each property we keep a table with accumulated Modifier parameters as named members, and all contributing modifiers with their own ModCalc params in the array part | |
| -- e.g. for the default mod/add ModCalc: | |
| -- { | |
| -- mul = 4000, | |
| -- add = -700, | |
| -- { id = "some_upgrade", mul = 2000, add = 0, }, | |
| -- { id = "some_tech", mul = 2000, add = -200, }, | |
| -- { id = "some_penalty", mul = 1000, add = -500, }, | |
| -- } | |
| -- PropModifiers have the following format (note that "mul" and "add" are not hardcoded, but inherited from Modifier): | |
| -- { id = <modification_id>, prop = <property_id>, display_name = <display_name>, mul = <multiplicative_modifier>, add = <additive_modifier> } | |
| -- | |
| modifications = false, | |
| } | |
| local remove = table.remove | |
| local ifind = table.ifind | |
| local function ChangeValue(self, prop, value) | |
| local old_value = self[prop] | |
| if old_value ~= value then | |
| self[prop] = value | |
| -- go over posted messages and check if they are for the same object and property | |
| local prev_msg, index = ifind(PostMsgList, "OnModifiableValueChanged", self, prop) | |
| if prev_msg then | |
| old_value = prev_msg[4] | |
| remove(PostMsgList, index) | |
| end | |
| if old_value ~= value then | |
| PostMsg("OnModifiableValueChanged", self, prop, old_value, value) | |
| end | |
| end | |
| end | |
| function OnMsg.OnModifiableValueChanged(obj, prop, old_value, value) | |
| procall(obj.OnModifiableValueChanged, obj, prop, old_value, value) | |
| end | |
| function Modifiable:AddModifier(id, prop, ...) | |
| local modifier = Modifier:ModCreate(...) | |
| if not modifier then return end | |
| modifier.id = id or nil | |
| self:AddModifierObj(modifier, prop) | |
| return modifier | |
| end | |
| function Modifiable:AddModifierObj(modifier, prop) | |
| prop = prop or modifier.prop | |
| local modifications = self.modifications | |
| if not modifications then | |
| modifications = {} | |
| self.modifications = modifications | |
| end | |
| local modification_list = modifications[prop] | |
| if not modification_list then | |
| local prop_meta = self:GetPropertyMetadata(prop) | |
| if prop_meta then | |
| modification_list = Modifier:new{ min = prop_meta.min or nil, max = prop_meta.max or nil } | |
| else | |
| modification_list = Modifier:new() | |
| end | |
| modifications[prop] = modification_list | |
| end | |
| if modifier.id then | |
| table.remove_entry(modification_list, "id", modifier.id) | |
| end | |
| if modifier.container then | |
| table.remove_entry(modification_list, "container", modifier.container) | |
| end | |
| modification_list[#modification_list + 1] = modifier | |
| modification_list:ModAccumulate() | |
| return ChangeValue(self, prop, modification_list:ModApply(self["base_" .. prop])) | |
| end | |
| function Modifiable:RemoveModifier(id, prop) | |
| local modifications = self.modifications | |
| local modification_list = modifications and modifications[prop] | |
| if not modification_list or not table.remove_entry(modification_list, "id", id) then return end | |
| local value = self["base_" .. prop] | |
| if #modification_list > 0 then | |
| modification_list:ModAccumulate() | |
| value = modification_list:ModApply(value) | |
| else | |
| modifications[prop] = nil | |
| end | |
| return ChangeValue(self, prop, value) | |
| end | |
| function Modifiable:RemoveModifierObj(modifier, prop) | |
| prop = prop or modifier.prop | |
| local modifications = self.modifications | |
| local modification_list = modifications and modifications[prop or false] | |
| if not modification_list then return end | |
| if not table.remove_entry(modification_list, modifier) and (not modifier.container or not table.remove_entry(modification_list, "container", modifier.container)) then | |
| return | |
| end | |
| local value = self["base_" .. prop] | |
| if #modification_list > 0 then | |
| modification_list:ModAccumulate() | |
| value = modification_list:ModApply(value) | |
| else | |
| modifications[prop] = nil | |
| end | |
| return ChangeValue(self, prop, value) | |
| end | |
| function Modifiable:ChangedModifier(prop) | |
| local modifications = self.modifications | |
| local modification_list = modifications and modifications[prop or false] | |
| if not modification_list then return end | |
| modification_list:ModAccumulate() | |
| return ChangeValue(self, prop, modification_list:ModApply(self["base_" .. prop])) | |
| end | |
| function Modifiable:ModifyValue(value, prop) -- apply modifiers of a prop to a value | |
| local modifications = self.modifications | |
| local modification_list = modifications and modifications[prop] | |
| if modification_list then | |
| value = modification_list:ModApply(value) | |
| end | |
| return value | |
| end | |
| function Modifiable:GetBase(prop) | |
| return self["base_" .. prop] | |
| end | |
| function Modifiable:SetBase(prop, value, base_prop) | |
| base_prop = base_prop or "base_" .. prop | |
| local base_value = self[base_prop] | |
| if base_value == value then return end | |
| self[base_prop] = value | |
| local modifications = self.modifications | |
| local modification_list = modifications and modifications[prop] | |
| if modification_list then | |
| value = modification_list:ModApply(value) | |
| end | |
| return ChangeValue(self, prop, value) | |
| end | |
| function Modifiable:AddBase(prop, value) | |
| if value ~= 0 then | |
| self:SetBase(prop, value + self["base_" .. prop]) | |
| end | |
| end | |
| function Modifiable:GetClassValue(prop) | |
| return getmetatable(self)[prop] | |
| end | |
| function Modifiable:RestoreBase(prop) | |
| local base_prop = "base_" .. prop | |
| if rawget(self, base_prop) == nil then return end | |
| self:SetBase(prop, nil, base_prop) | |
| return true | |
| end | |
| function Modifiable:RestoreModifiableValue(prop) | |
| local value = self:GetClassValue(prop) | |
| local modifications = self.modifications | |
| local modification_list = modifications and modifications[prop] | |
| if modification_list then | |
| value = modification_list:ModApply(value) | |
| end | |
| return ChangeValue(self, prop, value) | |
| end | |
| function Modifiable:GetPropertyModifierTexts(prop) | |
| local modifications = self.modifications | |
| if not modifications then return empty_table end | |
| local modification_list = modifications[prop] | |
| if not modification_list then return empty_table end | |
| local mod_texts = {} | |
| for _, mod in ipairs(modification_list) do | |
| if mod.display_text then | |
| mod_texts[#mod_texts + 1] = T{mod.display_text, mod} | |
| end | |
| end | |
| return mod_texts | |
| end | |
| function Modifiable:ModifierById(id, prop) | |
| local modifications = self.modifications | |
| if not modifications then return false end | |
| if prop then | |
| local modification_list = modifications[prop] | |
| return modification_list and table.find_value(modification_list, "id", id) | |
| else | |
| for _, modification_list in pairs(modifications) do | |
| local mod = table.find_value(modification_list, "id", id) | |
| if mod then | |
| return mod | |
| end | |
| end | |
| end | |
| end | |
| Modifiable.OnModifiableValueChanged = empty_func | |
| function OnMsg.ClassesPostprocess() | |
| ClassDescendants("Modifiable", function (name, class) | |
| for _, prop_meta in ipairs(class.properties) do | |
| if prop_meta.modifiable and prop_meta.editor == "number" then | |
| local prop_id = prop_meta.id | |
| local value = rawget(class, prop_id) | |
| if value ~= nil then | |
| rawset(class, "base_" .. prop_id, value) | |
| end | |
| end | |
| end | |
| end) | |
| end | |
| if Platform.developer then | |
| function OnMsg.ClassesPreprocess(classdefs) | |
| for name, def in pairs(classdefs) do | |
| for _, meta in ipairs(def.properties) do | |
| if meta.modifiable then | |
| local prop = meta.id | |
| --print(name, prop) | |
| if def["Get" .. prop] or def["Set" .. prop] then | |
| printf("Class %s should not have Get/Set accessor functions for the modifiable property %s", name, prop) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| ----- Modifier | |
| DefineClass.Modifier = { | |
| __parents = { "PropertyObject" }, | |
| display_text = "", | |
| id = "", | |
| prop = false, | |
| mul = 1000, | |
| add = 0, | |
| min = false, | |
| max = false, | |
| add_min = 0, | |
| add_max = 0, | |
| } | |
| function Modifier:ModCreate(mul, add, text, add_min, add_max, min, max) | |
| if (mul or 1000) == 1000 and (add or 0) == 0 and (add_min or 0) == 0 and (add_max or 0) == 0 then return end | |
| local modifier = self:new() | |
| modifier:ModSet(mul, add, text, add_min, add_max, min, max) | |
| return modifier | |
| end | |
| function Modifier:ModSet(mul, add, text, add_min, add_max, min, max) | |
| self.mul = mul ~= 1000 and mul or nil | |
| self.add = add ~= 0 and add or nil | |
| self.display_text = text ~= "" and text or nil | |
| self.add_min = add_min ~= 0 and add_min or nil | |
| self.add_max = add_max ~= 0 and add_max or nil | |
| self.min = min or nil | |
| self.max = max or nil | |
| end | |
| local MulDivRound = MulDivRound | |
| local Clamp = Clamp | |
| function Modifier:ModApply(value) | |
| value = MulDivRound(value + self.add, self.mul, 1000) | |
| local min, max = self.min, self.max | |
| min = min and (min + self.add_min) | |
| max = max and (max + self.add_max) | |
| return Clamp(value, min, max) | |
| end | |
| function Modifier:ModAccumulate(mod_list) | |
| local mul, add, add_min, add_max = 1000, 0, 0, 0 | |
| for _, mod in ipairs(mod_list or self) do | |
| add = add + mod.add | |
| mul = MulDivRound(mul, mod.mul, 1000) | |
| add_min = add_min + mod.add_min | |
| add_max = add_max + mod.add_max | |
| end | |
| self.add = add ~= 0 and add or nil | |
| self.mul = mul ~= 1000 and mul or nil | |
| self.add_min = add_min ~= 0 and add_min or nil | |
| self.add_max = add_max ~= 0 and add_max or nil | |
| end | |
| ----- ObjectModifier | |
| DefineClass.ObjectModifier = { | |
| __parents = { "InitDone", "Modifier" }, | |
| target = false, | |
| is_applied = false, | |
| } | |
| function ObjectModifier:Init() | |
| self:TurnOn() | |
| end | |
| function ObjectModifier:Done() | |
| self:TurnOff() | |
| end | |
| function ObjectModifier:ResolveObject(obj) | |
| return obj | |
| end | |
| function ObjectModifier:TurnOn() | |
| if self.is_applied then return end | |
| local target = self:ResolveObject(self.target) | |
| if target then target:AddModifierObj(self) end | |
| self.is_applied = true | |
| end | |
| function ObjectModifier:TurnOff() | |
| if not self.is_applied then return end | |
| local target = self:ResolveObject(self.target) | |
| if target then target:RemoveModifierObj(self) end | |
| self.is_applied = false | |
| end | |
| function ObjectModifier:Change(...) | |
| self:ModSet(...) | |
| if self.is_applied then | |
| local target = self:ResolveObject(self.target) | |
| if target then target:ChangedModifier(self.prop) end | |
| end | |
| end | |
| function ObjectModifier:IsApplied() | |
| return self.is_applied | |
| end | |
| ----- MultipleObjectsModifier | |
| DefineClass.MultipleObjectsModifier = { | |
| __parents = { "InitDone", "Modifier" }, | |
| targets = false, | |
| is_applied = false, | |
| } | |
| function MultipleObjectsModifier:Init() | |
| self:TurnOn() | |
| end | |
| function MultipleObjectsModifier:Done() | |
| self:TurnOff() | |
| end | |
| function MultipleObjectsModifier:ResolveObject(obj) | |
| return obj | |
| end | |
| function MultipleObjectsModifier:TurnOn() | |
| if self.is_applied then return end | |
| for i, target in ipairs(self.targets or empty_table) do | |
| target = self:ResolveObject(target) | |
| if target then target:AddModifierObj(self) end | |
| end | |
| self.is_applied = true | |
| end | |
| function MultipleObjectsModifier:TurnOff() | |
| if not self.is_applied then return end | |
| for i, target in ipairs(self.targets or empty_table) do | |
| target = self:ResolveObject(target) | |
| if target then target:RemoveModifierObj(self) end | |
| end | |
| self.is_applied = false | |
| end | |
| function MultipleObjectsModifier:CleanInvalidTargets() | |
| table.validate(self.targets) | |
| end | |
| function MultipleObjectsModifier:CanDelete() | |
| for i, target in ipairs(self.targets or empty_table) do | |
| if IsValid(target) then | |
| return false | |
| end | |
| end | |
| return true | |
| end | |
| function MultipleObjectsModifier:Change(...) | |
| self:ModSet(...) | |
| if self.is_applied then | |
| for i, target in ipairs(self.targets or empty_table) do | |
| target = self:ResolveObject(target) | |
| if target then target:ChangedModifier(self.prop) end | |
| end | |
| end | |
| end | |
| function MultipleObjectsModifier:AddTarget(target) | |
| assert(not table.find(self.targets, target)) | |
| table.insert(self.targets, target) | |
| if self.is_applied then | |
| target = self:ResolveObject(target) | |
| if target then target:AddModifierObj(self) end | |
| end | |
| end | |
| function MultipleObjectsModifier:RemoveTarget(target) | |
| local found = table.remove_entry(self.targets, target) | |
| if found and self.is_applied then | |
| target = self:ResolveObject(target) | |
| if target then target:RemoveModifierObj(self) end | |
| end | |
| end | |
| if FirstLoad then | |
| ModifiablePropsComboItems = {} | |
| ModifiablePropScale = {} | |
| end | |
| local function UpdateModifiablePropScales() | |
| local scale = {} | |
| ClassDescendants("Modifiable", function(name, classdef, scale) | |
| local class_props = classdef:GetProperties() | |
| for i = 1, #class_props do | |
| local prop = class_props[i] | |
| if prop.modifiable and prop.editor == "number" then | |
| local new_scale = prop.scale | |
| local existing_scale = scale[prop.id] | |
| if not existing_scale then | |
| scale[prop.id] = new_scale | |
| elseif existing_scale ~= new_scale then | |
| assert(false, "Modifiable property with different scale factors!") | |
| end | |
| end | |
| end | |
| end, scale) | |
| ModifiablePropsComboItems = table.keys(scale) | |
| table.sort(ModifiablePropsComboItems, CmpLower) | |
| ModifiablePropScale = scale | |
| end | |
| function OnMsg.ClassesBuilt() | |
| UpdateModifiablePropScales() | |
| end | |
| function OnMsg.BinAssetsLoaded() | |
| UpdateModifiablePropScales() | |
| end | |
| function ClassModifiablePropsCombo(obj) | |
| local existing, props = {}, {} | |
| local class_props = obj:GetProperties() | |
| for i = 1,#class_props do | |
| local prop = class_props[i] | |
| if prop.modifiable and prop.editor == "number" and not existing[prop.id] then | |
| existing[prop.id] = true | |
| props[#props + 1] = {value = prop.id, text = prop.name or prop.id} | |
| end | |
| end | |
| TSort(props, "text") | |
| return props | |
| end | |
| function ClassModifiablePropsNonTranslatableCombo(obj) | |
| if type(obj) == "string" then | |
| obj = g_Classes[obj] | |
| end | |
| if not obj then | |
| return ModifiablePropsComboItems | |
| end | |
| local props = {} | |
| for _, prop in ipairs(obj:GetProperties()) do | |
| if prop.modifiable and prop.editor == "number" then | |
| props[#props + 1] = prop.id | |
| end | |
| end | |
| table.sort(props) | |
| return props | |
| end | |
| function NestedObjectsCombo(obj) | |
| if type(obj) == "string" then | |
| obj = g_Classes[obj] | |
| end | |
| if not obj then return {} end | |
| local items = {} | |
| local props = obj:GetProperties() | |
| for _, prop in ipairs(props) do | |
| if prop.editor == "nested_obj" or prop.editor == "nested_list" then | |
| items[#items + 1] = prop.id | |
| end | |
| end | |
| table.sort(items) | |
| table.insert(items, 1, "") | |
| return items | |
| end | |
| DefineClass.ModifyProperty = { | |
| __parents = { "ContinuousEffect", "Modifier" }, | |
| properties = { | |
| { id = "Id", }, | |
| { id = "obj_class", name = "Object Class", help = "Apply to objects of this class only (optional)", | |
| editor = "choice", default = false, items = function (self) return ClassDescendantsList("Modifiable") end, | |
| no_edit = function(self) return not self.HasClassProp end, dont_save = function(self) return not self.HasClassProp end }, | |
| { id = "sub_object", name = "Sub-object", help = "Use to specify the sub-object to be modified (optional)", | |
| editor = "string_list", default = false, | |
| item_default = "", items = function (self) return self:HasMember("obj_class") and NestedObjectsCombo(self.obj_class) or {} end, arbitrary_value = false, max_items = -1, | |
| no_edit = function(self) return not self.obj_class or not self.HasSubObjectProp end, dont_save = function(self) return not self.HasSubObjectProp end }, | |
| { id = "id", name = "Id", help = "Only the last modifier with this id will be active (optional)", | |
| editor = "text", default = false, | |
| no_edit = function(self) return not self.HasIdProp end, dont_save = function(self) return not self.HasIdProp end }, | |
| { id = "prop", name = "Property", help = "Name of a numeric property to modify", | |
| editor = "choice", default = false, items = function (self) return ClassModifiablePropsNonTranslatableCombo(self.obj_class) end, }, | |
| { id = "add", name = "Add", help = "Additive modifier, applied before Mul", | |
| editor = "number", default = 0, scale = function (self) return self:GetModScale() end, }, | |
| { id = "mul", name = "Mul", help = "Multiplicative modifier", | |
| editor = "number", default = 1000, min = 0, scale = 1000 }, | |
| { id = "add_min", name = "Change min", help = "Add to modified value min", | |
| editor = "number", default = 0, scale = function (self) return self:GetModScale() end, }, | |
| { id = "add_max", name = "Change max", help = "Add to modified value max", | |
| editor = "number", default = 0, scale = function (self) return self:GetModScale() end, }, | |
| { id = "display_text", name = "Display Text", help = "Can be used to display in the UI this modifier", | |
| editor = "text", default = "", translate = true, | |
| no_edit = function(self) return self.HasDisplayTextProp end, dont_save = function(self) return self.HasDisplayTextProp end }, | |
| }, | |
| CreateInstance = false, | |
| RequiredObjClasses = { | |
| "Modifiable", | |
| }, | |
| Documentation = "Applies a modifier to a property within the object parameter of this effect.", | |
| EditorNestedObjCategory = "Continuous Effects", | |
| EditorView = T(744151871819, "Modify <u(SubObjectEditorView)><u(prop)><AddEditorView><MulEditorView> <u(id)>"), | |
| HasClassProp = true, | |
| HasSubObjectProp = true, | |
| HasIdProp = true, | |
| HasDisplayTextProp = true, | |
| } | |
| function ModifyProperty:GetSubObjectEditorView() | |
| if next(self.sub_object or empty_table) then | |
| return table.concat(self.sub_object, ".") .. "." | |
| end | |
| return "" | |
| end | |
| function ModifyProperty:GetAddEditorView() | |
| if self.add == 0 then return "" end | |
| local scale = self:GetModScale() | |
| local text = (self.add < 0 and Untranslated(" -") or Untranslated(" +")) .. FormatAsFloat(abs(self.add), type(scale) == "number" and scale or const.Scale[scale] or 1, 3, true) | |
| return type(scale) == "string" and text .. Untranslated(scale) or text | |
| end | |
| function ModifyProperty:GetMulEditorView() | |
| if self.mul == 1000 then return "" end | |
| return Untranslated(" x") .. FormatAsFloat(self.mul, 1000, 3, true) | |
| end | |
| function ModifyProperty:GetModScale() | |
| return ModifiablePropScale[self.prop] or 1 | |
| end | |
| function ModifyProperty:ResolveObject(obj) | |
| if self.obj_class and not obj:IsKindOf(self.obj_class) then return end | |
| for i, field in ipairs(self.sub_object or empty_table) do | |
| obj = type(obj) == "table" and rawget(obj, field) | |
| end | |
| return obj | |
| end | |
| function ModifyProperty:OnStart(obj, context) | |
| obj = self:ResolveObject(obj) | |
| if obj then | |
| obj:AddModifierObj(self) | |
| end | |
| end | |
| ModifyProperty.__exec = ModifyProperty.OnStart | |
| function ModifyProperty:OnStop(obj, context) | |
| obj = self:ResolveObject(obj) | |
| if obj then | |
| obj:RemoveModifierObj(self) | |
| end | |
| end | |
| function ModifyProperty:GetError() | |
| if not self.prop then | |
| return "Missing property to modify" | |
| elseif self.add == 0 and self.mul == 1000 and self.add_min == 0 and self.add_max == 0 then | |
| return "Default values result in no modification" | |
| end | |
| end | |
| ----- ModifiersPreset | |
| DefineClass.ModifiersPreset = { | |
| __parents = { "Preset", }, | |
| properties = { | |
| { category = "Effect", id = "Modifiers", name = "Modifiers", no_edit = function(self) return not self.ModifiersClass end, | |
| editor = "nested_list", default = false, base_class = function(self) return self.ModifiersClass end, all_descendants = true, }, | |
| }, | |
| ModifiersClass = false, | |
| EditorMenubarName = false, | |
| } | |
| function ModifiersPreset:ApplyModifiers(obj) | |
| for _, modifier in ipairs(self.Modifiers or empty_table) do | |
| modifier:OnStart(obj, self) | |
| end | |
| end | |
| function ModifiersPreset:UnapplyModifiers(obj) | |
| for _, modifier in ipairs(self.Modifiers or empty_table) do | |
| modifier:OnStop(obj, self) | |
| end | |
| end | |
| function ModifiersPreset:PostLoad() | |
| for _, modifier in ipairs(self.Modifiers or empty_table) do | |
| modifier.container = self | |
| end | |
| Preset.PostLoad(self) | |
| end |