Download CommonLua/Classes/AutoAttach.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 38.8 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Classes/AutoAttach.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Classes/AutoAttach.lua
-
curl -L -o AutoAttach.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Classes/AutoAttach.lua
38.8 kB
| function GetObjStateAttaches(obj, entity) | |
| entity = entity or (obj:GetEntity() or obj.entity) | |
| local state = obj and GetStateName(obj:GetState()) or "idle" | |
| local entity_attaches = Attaches[entity] | |
| return entity_attaches and entity_attaches[state] | |
| end | |
| function GetEntityAutoAttachModes(obj, entity) | |
| local attaches = GetObjStateAttaches(obj, entity) | |
| local modes = {""} | |
| for _, attach in ipairs(attaches or empty_table) do | |
| if attach.required_state then | |
| local mode = string.trim_spaces(attach.required_state) | |
| table.insert_unique(modes, mode) | |
| end | |
| end | |
| return modes | |
| end | |
| --[[@@@ | |
| @class AutoAttachCallback | |
| Inherit this if you want a callback when an objects is autoattached to its parent | |
| --]] | |
| DefineClass.AutoAttachCallback = { | |
| __parents = {"InitDone"}, | |
| } | |
| function AutoAttachCallback:OnAttachToParent(parent, spot) | |
| end | |
| --[[@@@ | |
| @class AutoAttachObject | |
| Objects from this type are able to attach a preset of objects on their creation based on their spot annotations. | |
| --]] | |
| DefineClass.AutoAttachObject = | |
| { | |
| __parents = { "Object", "ComponentAttach" }, | |
| auto_attach_props_description = false, | |
| properties = { | |
| { id = "AutoAttachMode", editor = "choice", default = "", items = function(obj) return GetEntityAutoAttachModes(obj) or {} end }, | |
| { id = "AllAttachedLightsToDetailLevel", editor = "choice", default = false, items = {"Essential", "Optional", "Eye Candy"}}, | |
| }, | |
| auto_attach_at_init = true, | |
| auto_attach_mode = false, | |
| is_forced_lod_min = false, | |
| max_colorization_materials_attaches = 0, | |
| } | |
| local gofAutoAttach = const.gofAutoAttach | |
| function IsAutoAttach(attach) | |
| return attach:GetGameFlags(gofAutoAttach) ~= 0 | |
| end | |
| function AutoAttachObject:DestroyAutoAttaches() | |
| self:DestroyAttaches(IsAutoAttach) | |
| end | |
| function AutoAttachObject:ClearAttachMembers() | |
| local attaches = GetObjStateAttaches(self) | |
| for _, attach in ipairs(attaches) do | |
| if attach.member then | |
| self[attach.member] = nil | |
| end | |
| end | |
| end | |
| function AutoAttachObject:SetAutoAttachMode(value) | |
| self.auto_attach_mode = value | |
| self:DestroyAutoAttaches() | |
| self:ClearAttachMembers() | |
| self:AutoAttachObjects() | |
| end | |
| function AutoAttachObject:OnEditorSetProperty(prop_id, old_value, ged) | |
| if prop_id == "AllAttachedLightsToDetailLevel" or prop_id == "StateText" then | |
| self:SetAutoAttachMode(self:GetAutoAttachMode()) | |
| end | |
| Object.OnEditorSetProperty(self, prop_id, old_value, ged) | |
| end | |
| function AutoAttachObject:GetAutoAttachMode(mode) | |
| local mode_set = GetEntityAutoAttachModes(self) | |
| if not mode_set then | |
| return "" | |
| end | |
| if table.find(mode_set, mode or self.auto_attach_mode) then | |
| return self.auto_attach_mode | |
| end | |
| return mode_set[1] or "" | |
| end | |
| function AutoAttachObject:GetAttachModeSet() | |
| return GetEntityAutoAttachModes(self) | |
| end | |
| if FirstLoad then | |
| s_AutoAttachedLightDetailsBaseObject = false | |
| end | |
| function AutoAttachObjects(obj, context) | |
| if not s_AutoAttachedLightDetailsBaseObject and obj.AllAttachedLightsToDetailLevel then | |
| s_AutoAttachedLightDetailsBaseObject = obj | |
| end | |
| local selectable = obj:GetEnumFlags(const.efSelectable) ~= 0 | |
| local attaches = GetObjStateAttaches(obj) | |
| local max_colorization_materials = 0 | |
| for i = 1, #(attaches or "") do | |
| local attach = attaches[i] | |
| local class = GetAttachClass(obj, attach[2]) | |
| local spot_attaches = {} | |
| local place, detail_class = PlaceCheck(obj, attach, class, context) | |
| if place then | |
| local o = PlaceAtSpot(obj, attach.spot_idx, class, context) | |
| if o then | |
| if attach.mirrored then | |
| o:SetMirrored(true) | |
| end | |
| if attach.offset then | |
| o:SetAttachOffset(attach.offset) | |
| end | |
| if attach.axis and attach.angle and attach.angle ~= 0 then | |
| o:SetAttachAxis(attach.axis) | |
| o:SetAttachAngle(attach.angle) | |
| end | |
| if selectable then | |
| o:SetEnumFlags(const.efSelectable) | |
| end | |
| if attach.inherited_properties then | |
| for key, value in sorted_pairs(attach.inherited_properties) do | |
| o:SetProperty(key, value) | |
| end | |
| end | |
| if IsKindOf(o, "SubstituteByRandomChildEntity") then | |
| -- NOTE: when substituting entity the object is still not attached so it can't be | |
| -- destroyed in SubstituteByRandomChildEntity and we have to do it manually here | |
| if o:IsForcedLODMinAttach() and o:GetDetailClass() ~= "Essential" then | |
| DoneObject(o) | |
| o = nil | |
| else | |
| local top_parent = GetTopmostParent(o) | |
| ApplyCurrentEnvColorizedToObj(top_parent) -- entity changed, possibly colorization too. | |
| top_parent:DestroyRenderObj(true) | |
| end | |
| else | |
| o:SetDetailClass(detail_class) | |
| end | |
| if o then | |
| if attach.inherit_colorization then | |
| o:SetGameFlags(const.gofInheritColorization) | |
| max_colorization_materials = Max(max_colorization_materials, o:GetMaxColorizationMaterials()) | |
| end | |
| o:SetForcedLODMin(rawget(obj, "is_forced_lod_min") or obj:GetForcedLODMin()) | |
| spot_attaches[#spot_attaches+1] = o | |
| end | |
| end | |
| end | |
| if context ~= "placementcursor" then | |
| SetObjMembers(obj, attach, spot_attaches) | |
| end | |
| end | |
| if max_colorization_materials > AutoAttachObject.max_colorization_materials_attaches then | |
| obj.max_colorization_materials_attaches = max_colorization_materials | |
| end | |
| if s_AutoAttachedLightDetailsBaseObject == obj then | |
| s_AutoAttachedLightDetailsBaseObject = false | |
| end | |
| end | |
| function AutoAttachObject:GetMaxColorizationMaterials() | |
| return Max(self.max_colorization_materials_attaches, CObject.GetMaxColorizationMaterials(self)) | |
| end | |
| function AutoAttachObject:CanBeColorized() | |
| return self.max_colorization_materials_attaches and self.max_colorization_materials_attaches > 1 or CObject.CanBeColorized(self) | |
| end | |
| AutoAttachObject.AutoAttachObjects = AutoAttachObjects | |
| function RemoveObjMembers(obj, attach, list) | |
| if attach.member then | |
| local o = obj[attach.member] | |
| for i = 1, #list do | |
| if o == list[i] then | |
| obj[attach.member] = false | |
| break | |
| end | |
| end | |
| end | |
| if attach.memberlist and obj[attach.memberlist] and type(obj[attach.memberlist]) == "table" then | |
| table.remove_entry(obj[attach.memberlist], list) | |
| end | |
| end | |
| -- local functions used in the class methods | |
| local AutoAttachObjects, RemoveObjMembers = AutoAttachObjects, RemoveObjMembers | |
| function AutoAttachObject:Init() | |
| if self.auto_attach_at_init then | |
| AutoAttachObjects(self, "init") | |
| end | |
| end | |
| function AutoAttachObject:__fromluacode(props, arr, handle) | |
| local obj = ResolveHandle(handle) | |
| if obj and obj[true] then | |
| StoreErrorSource(obj, "Duplicate handle", handle) | |
| assert(false, string.format("Duplicate handle %d: new '%s', prev '%s'", handle, self.class, obj.class)) | |
| obj = nil | |
| end | |
| local idx = table.find(props, "AllAttachedLightsToDetailLevel") | |
| local attached_lights_detail = idx and props[idx + 1] | |
| if attached_lights_detail then | |
| obj.AllAttachedLightsToDetailLevel = attached_lights_detail | |
| end | |
| local idx = table.find(props, "LowerLOD") | |
| if idx ~= nil then | |
| obj.is_forced_lod_min = props[idx + 1] | |
| else | |
| idx = table.find(props, "ForcedLODState") | |
| obj.is_forced_lod_min = idx and (props[idx + 1] == "Minimum") | |
| end | |
| obj = self:new(obj) | |
| SetObjPropertyList(obj, props) | |
| SetArray(obj, arr) | |
| obj.is_forced_lod_min = nil | |
| return obj | |
| end | |
| AutoAttachObject.ShouldAttach = return_true | |
| AutoResolveMethods.ShouldAttach = "and" | |
| function AutoAttachObject:OnAttachCreated(attach, spot) | |
| end | |
| function AutoAttachObject:MarkAttachEntities(entities) | |
| if not IsValid(self) then return entities end | |
| entities = entities or {} | |
| self:__MarkEntities(entities) | |
| local cur_mode = self.auto_attach_mode | |
| local modes = self:GetAttachModeSet() | |
| for _, mode in ipairs(modes) do | |
| self:SetAutoAttachMode(mode) | |
| self:__MarkEntities(entities) | |
| end | |
| self:SetAutoAttachMode(cur_mode) | |
| return entities | |
| end | |
| function SetObjMembers(obj, attach, list) | |
| if attach.member then | |
| local name = attach.member | |
| if #list == 0 then | |
| if not rawget(obj, name) then | |
| obj[name] = false -- initialize on init | |
| end | |
| else | |
| assert(#list == 1 and not rawget(obj, name), 'Duplicate member "'..name..'" in the auto-attaches of class "'..obj.class..'"') | |
| obj[name] = list[1] | |
| end | |
| end | |
| if attach.memberlist then | |
| local name = attach.memberlist | |
| if not rawget(obj, name) or not type(obj[name]) == "table" or not IsValid(obj[name][1]) then | |
| obj[name] = {} | |
| end | |
| if #list > 0 then | |
| obj[name][#obj[name] + 1] = list | |
| end | |
| end | |
| end | |
| function GetAttachClass(self, classes) | |
| if type(classes) == "string" then | |
| return classes | |
| end | |
| assert(type(classes) == "table") | |
| local rnd = self:Random(100) | |
| local cur_prob = 0 | |
| for class, prob in pairs(classes) do | |
| cur_prob = cur_prob + prob | |
| if rnd <= cur_prob then | |
| return class | |
| end | |
| end | |
| -- probability of nothing left | |
| return false | |
| end | |
| local IsKindOf = IsKindOf | |
| local shapeshifter_class_whitelist = { "Light", "AutoAttachSIModulator", "ParSystem" } -- classes that are alowed to be instantiated even in shapeshifters | |
| local function IsObjectClassAllowedInShapeshifter(class_to_spawn) | |
| for _, class_name in ipairs(shapeshifter_class_whitelist) do | |
| if IsKindOf(class_to_spawn, class_name) then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| local gofDetailClassMask = const.gofDetailClassMask | |
| function PlaceCheck(obj, attach, class, context) | |
| if not obj:ShouldAttach(attach) then | |
| return false | |
| end | |
| -- placement cursor check | |
| if context == "placementcursor" then | |
| if not attach.show_at_placement and not attach.placement_only then | |
| return false | |
| end | |
| elseif attach.placement_only then | |
| return false | |
| end | |
| if attach.required_state and IsKindOf(obj, "AutoAttachObject") and attach.required_state ~= obj.auto_attach_mode then | |
| return false | |
| end | |
| -- condition check | |
| local condition = attach.condition | |
| if condition then | |
| assert(type(condition) == "function" or type(condition) == "string") | |
| if type(condition) == "function" then | |
| if not condition(obj, attach) then | |
| return false | |
| end | |
| else | |
| if obj:HasMember(condition) and not obj[condition] then | |
| return false | |
| end | |
| end | |
| end | |
| local detail_class = s_AutoAttachedLightDetailsBaseObject and | |
| IsKindOf(g_Classes[class], "Light") and | |
| s_AutoAttachedLightDetailsBaseObject.AllAttachedLightsToDetailLevel | |
| detail_class = detail_class or (attach.DetailClass ~= "Default" and attach.DetailClass) | |
| if not detail_class then | |
| -- try to extract from the class | |
| local detail_mask = GetClassGameFlags(class, gofDetailClassMask) | |
| local detail_from_class = GetDetailClassMaskName(detail_mask) | |
| detail_class = detail_from_class ~= "Default" and detail_from_class | |
| end | |
| local forced_lod_min = rawget(obj, "is_forced_lod_min") or obj:GetForcedLODMin() | |
| if forced_lod_min and detail_class ~= "Essential" then | |
| return false | |
| end | |
| return true, detail_class | |
| end | |
| function PlaceAtSpot(obj, spot, class, context) | |
| local o | |
| if g_Classes[class] then | |
| if context == "placementcursor" then | |
| if g_Classes[class]:IsKindOfClasses("TerrainDecal", "BakedTerrainDecal") then | |
| o = PlaceObject("PlacementCursorAttachmentTerrainDecal") | |
| else | |
| o = PlaceObject("PlacementCursorAttachment") | |
| end | |
| o:ChangeClass(class) | |
| AutoAttachObjects(o, "placementcursor") | |
| elseif context == "shapeshifter" and not IsObjectClassAllowedInShapeshifter(g_Classes[class]) then | |
| o = PlaceObject("Shapeshifter", nil, const.cofComponentAttach) | |
| if IsValidEntity(class) then | |
| o:ChangeEntity(class) | |
| end | |
| else | |
| o = PlaceObject(class, nil, const.cofComponentAttach) | |
| end | |
| else | |
| print("once", 'AutoAttach: unknown class/particle "' .. class .. '" for [object "' .. obj.class .. '", spot "' .. obj:GetSpotName(spot) .. '"]') | |
| end | |
| if not o then | |
| return | |
| end | |
| local err = obj:Attach(o, spot) | |
| if err then | |
| print("once", "Error attaching", o.class, "to", obj.class, ":", err) | |
| return | |
| end | |
| o:SetGameFlags(const.gofAutoAttach) | |
| if not IsKindOf(obj, "Shapeshifter") then | |
| obj:OnAttachCreated(o, spot) | |
| end | |
| if IsKindOf(o, "AutoAttachCallback") then | |
| o:OnAttachToParent(obj, spot) | |
| end | |
| return o | |
| end | |
| if FirstLoad then | |
| Attaches = {} -- global table that keeps the inherited auto_attaches | |
| end | |
| function AutoAttachObjectsToPlacementCursor(obj) | |
| AutoAttachObjects(obj, "placementcursor") | |
| end | |
| --[Deprecated] | |
| function AutoAttachObjectsToShapeshifter(obj) | |
| AutoAttachObjects(obj) | |
| end | |
| function AutoAttachShapeshifterObjects(obj) | |
| AutoAttachObjects(obj, "shapeshifter") | |
| end | |
| local function CanInheritColorization(parent_entity, child_entity) | |
| return true | |
| end | |
| function GetEntityAutoAttachTable(entity, auto_attach) | |
| auto_attach = auto_attach or false | |
| local states = GetStates(entity) | |
| for _, state in ipairs(states) do | |
| local spbeg, spend = GetAllSpots(entity, state) | |
| for spot = spbeg, spend do | |
| local str = GetSpotAnnotation(entity, spot) | |
| if str and #str > 0 then | |
| local item | |
| for w in string.gmatch(str,"%s*(.[^,]+)[, ]?") do | |
| local lw = string.lower(w) | |
| if not item then | |
| -- auto attach description | |
| if lw ~= "att" and lw~="autoattach" then | |
| break | |
| end | |
| item = {} | |
| item.spot_idx = spot | |
| elseif lw=="show at placement" or lw=="show_at_placement" or lw=="show" then -- show at placement | |
| item.show_at_placement = true | |
| elseif lw=="placement only" or lw=="placement_only" then -- placement only | |
| item.placement_only = true | |
| elseif lw=="mirrored" or lw=="mirror" then | |
| item.mirrored = true | |
| elseif not item[2] then | |
| item[2] = w | |
| if not g_Classes[w] then | |
| print("once", "Invalid autoattach", w, "for entity", entity) | |
| end | |
| end | |
| end | |
| if item then | |
| item.inherit_colorization = CanInheritColorization(entity, item[2]) | |
| auto_attach = auto_attach or {} | |
| auto_attach[state] = auto_attach[state] or {} | |
| table.insert(auto_attach[state], item) | |
| end | |
| end | |
| end | |
| end | |
| return auto_attach | |
| end | |
| local function IsAutoAttachObject(entity) | |
| local entity_data = EntityData and EntityData[entity] and EntityData[entity].entity | |
| local classes = entity_data and entity_data.class_parent and entity_data.class_parent or "" | |
| for class in string.gmatch(classes, "[^%s,]+%s*") do | |
| if IsKindOf(g_Classes[class], "AutoAttachObject") then | |
| return true | |
| end | |
| end | |
| end | |
| local function TransferMatchingIdleAttachesToAllState(auto_attach, states) | |
| local idle_attaches = auto_attach["idle"] | |
| if not idle_attaches then return end | |
| local attach_modes | |
| for _, attach in ipairs(idle_attaches) do | |
| if attach.required_state then | |
| attach_modes = true | |
| break | |
| end | |
| end | |
| if not attach_modes then return end | |
| for _, state in ipairs(states) do | |
| auto_attach[state] = auto_attach[state] or {} | |
| table.iappend(auto_attach[state], idle_attaches) | |
| end | |
| end | |
| -- build autoattach table | |
| function RebuildAutoattach() | |
| if not config.LoadAutoAttachData then return end | |
| local ae = GetAllEntities() | |
| for entity, _ in sorted_pairs(ae) do | |
| local auto_attach = IsAutoAttachObject(entity) and GetEntityAutoAttachTable(entity) | |
| auto_attach = GetEntityAutoAttachTableFromPresets(entity, auto_attach) | |
| if auto_attach then | |
| local states = GetStates(entity) | |
| table.remove_value(states, "idle") | |
| if #states > 0 then | |
| -- transfer auto attaches to all states since AutoAttachEditor can define only in "idle" state | |
| TransferMatchingIdleAttachesToAllState(auto_attach, states) | |
| end | |
| Attaches[entity] = auto_attach | |
| else | |
| Attaches[entity] = nil | |
| end | |
| end | |
| end | |
| OnMsg.EntitiesLoaded = RebuildAutoattach | |
| function OnMsg.PresetSave(name) | |
| local class = g_Classes[name] | |
| if IsKindOf(class, "AutoAttachPreset") then | |
| RebuildAutoattach() | |
| end | |
| end | |
| local function PlaceFadingObjects(category, init_pos) | |
| local ae = GetAllEntities() | |
| local init_pos = init_pos or GetTerrainCursor() | |
| local pos = init_pos | |
| for k,v in pairs(ae) do | |
| if EntityData[k] and EntityData[k].entity and EntityData[k].entity.fade_category == category then | |
| local o = PlaceObject(k) | |
| o:ChangeEntity(k) | |
| o:SetPos(pos) | |
| o:SetGameFlags(const.gofPermanent) | |
| pos = pos + point(10*guim, 0) | |
| if (pos:x() / (600*guim) > 0) then | |
| pos = point(init_pos:x(), pos:y() + 20*guim) | |
| end | |
| elseif not EntityData[k] then | |
| print("No EntityData for: ", k) | |
| elseif EntityData[k] and not EntityData[k].entity then | |
| print("No EntityData[].entity for: ", k) | |
| end | |
| end | |
| end | |
| function TestFadeCategories() | |
| local cat = { | |
| "PropsUltraSmall", | |
| "PropsSmall", | |
| "PropsMedium", | |
| "PropsBig", | |
| } | |
| local pos = point(100*guim, 100*guim) | |
| for i=1, #cat do | |
| PlaceFadingObjects(cat[i], pos) | |
| pos = pos + point(0, 100*guim) | |
| end | |
| end | |
| function GetEntitiesAutoattachCount(filter_count) | |
| local el = GetAllEntities() | |
| local filter_count = filter_count or 30 | |
| for k,v in pairs(el) do | |
| local s,e = GetSpotRange(k, EntityStates["idle"], "Autoattach") | |
| if (e-s) > filter_count then | |
| print(k, e-s) | |
| end | |
| end | |
| end | |
| function ListEntityAutoattaches(entity) | |
| local s,e = GetSpotRange(entity, EntityStates["idle"], "Autoattach") | |
| for i=s, e do | |
| local annotation = GetSpotAnnotation(entity, i) | |
| print(i, annotation) | |
| end | |
| end | |
| ---------------------- AutoAttach editor ---------------------- | |
| local function FindArtSpecById(id) | |
| local spec = EntitySpecPresets[id] | |
| if not spec then | |
| local idx = string.find(id, "_[0-9]+$") | |
| if idx then | |
| spec = EntitySpecPresets[string.sub(id, 0, idx - 1)] | |
| end | |
| end | |
| return spec | |
| end | |
| local function GenerateMissingEntities() | |
| local all_entities = GetAllEntities() | |
| local to_create = {} | |
| for entity in pairs(all_entities) do | |
| local spec = FindArtSpecById(entity) | |
| if spec and not AutoAttachPresets[entity] and string.find(spec.class_parent, "AutoAttachObject", 1, true) then | |
| table.insert(to_create, entity) | |
| end | |
| end | |
| if #to_create > 0 then | |
| for _, entity in ipairs(to_create) do | |
| local preset = AutoAttachPreset:new({id = entity}) | |
| preset:Register() | |
| preset:UpdateSpotData() | |
| Sleep(1) | |
| end | |
| AutoAttachPreset:SortPresets() | |
| ObjModified(Presets.AutoAttachPreset) | |
| end | |
| end | |
| function GetEntitySpots(entity) | |
| if not IsValidEntity(entity) then return {} end | |
| local states = GetStates(entity) | |
| local idle = table.find(states, "idle") | |
| if not idle then | |
| print("WARNING: No idle state for", entity, "cannot fetch spots.") | |
| return {} | |
| end | |
| local spots = {} | |
| local spbeg, spend = GetAllSpots(entity, "idle") | |
| for spot = spbeg, spend do | |
| local str = GetSpotName(entity, spot) | |
| spots[str] = spots[str] or {} | |
| table.insert(spots[str], spot) | |
| end | |
| return spots | |
| end | |
| local zeropoint = point(0, 0, 0) | |
| function GetEntityAutoAttachTableFromPresets(entity, attach_table) | |
| local preset = AutoAttachPresets[entity] | |
| if not preset then return attach_table end | |
| local spots | |
| for _, spot in ipairs(preset) do | |
| for _, rule in ipairs(spot) do | |
| attach_table = rule:FillAutoAttachTable(attach_table, entity, preset) | |
| end | |
| end | |
| return attach_table | |
| end | |
| DefineClass.AutoAttachRuleBase = { | |
| __parents = { "PropertyObject" }, | |
| parent = false, | |
| } | |
| function AutoAttachRuleBase:FillAutoAttachTable(attach_table, entity, preset) | |
| return attach_table | |
| end | |
| function AutoAttachRuleBase:IsActive() | |
| return false | |
| end | |
| function AutoAttachRuleBase:OnEditorNew(parent, ged, is_paste) | |
| self.parent = parent | |
| end | |
| local function GetSpotsCombo(entity_name) | |
| local t = {} | |
| local spots = GetEntitySpots(entity_name) | |
| for spot_name, indices in sorted_pairs(spots) do | |
| for i = 1, #indices do | |
| table.insert(t, spot_name .. " " .. i) | |
| end | |
| end | |
| return t | |
| end | |
| DefineClass.AutoAttachRuleInherit = { | |
| __parents = { "AutoAttachRuleBase" }, | |
| properties = { | |
| { id = "parent_entity", category = "Rule", name = "Parent Entity", editor = "combo", items = function() return ClassDescendantsCombo("AutoAttachObject") end, default = "", }, | |
| { id = "spot", category = "Rule", name = "Spot", editor = "combo", items = function(obj) return GetSpotsCombo(obj:GetParentEntity()) end, default = "", }, | |
| }, | |
| } | |
| function AutoAttachRuleInherit:GetParentEntity() | |
| return self.parent_entity | |
| end | |
| function AutoAttachRuleInherit:GetSpotAndIdx() | |
| local spot = self.spot | |
| local break_idx = string.find(spot, "%d+$") | |
| if not break_idx then return end | |
| local spot_name = string.sub(spot, 1, break_idx - 2) | |
| local spot_idx = tonumber(string.sub(spot, break_idx)) | |
| if spot_name and spot_idx then | |
| return spot_name, spot_idx | |
| end | |
| end | |
| function AutoAttachRuleInherit:GetEditorView() | |
| local str = string.format("Inherit %s from %s", self.spot or "[SPOT]", self:GetParentEntity() or "[ENTITY]") | |
| if not self:FindInheritedSpot() then | |
| str = "<color 168 168 168>" .. str .. "</color>" | |
| end | |
| return str | |
| end | |
| function AutoAttachRuleInherit:FindInheritedSpot() | |
| local entity = self:GetParentEntity() | |
| local parent_preset = AutoAttachPresets[entity] | |
| if not parent_preset then return end | |
| local spot_name, spot_idx = self:GetSpotAndIdx() | |
| if not spot_name or not spot_idx then return end | |
| local aaspot_idx, aapost_obj = parent_preset:GetSpot(spot_name, spot_idx) | |
| if not aapost_obj then return end | |
| return aapost_obj, entity, parent_preset | |
| end | |
| function AutoAttachRuleInherit:FillAutoAttachTable(attach_table, entity, preset) | |
| local spot, parent_entity, parent_preset = self:FindInheritedSpot() | |
| if not spot then return attach_table end | |
| for _, rule in ipairs(spot) do | |
| attach_table = rule:FillAutoAttachTable(attach_table, parent_entity, parent_preset, self.parent) | |
| end | |
| return attach_table | |
| end | |
| function AutoAttachRuleInherit:IsActive() | |
| local spot, _, _ = self:FindInheritedSpot() | |
| return not not spot | |
| end | |
| DefineClass.AutoAttachRule = { | |
| __parents = { "AutoAttachRuleBase" }, | |
| properties = { | |
| { id = "attach_class", category = "Rule", name = "Object Class", editor = "combo", items = function() return ClassDescendantsCombo("CObject") end, default = "", }, | |
| { id = "quick_modes", default = false, no_save = true, editor = "buttons", category = "Rule", buttons = { | |
| {name = "ParSystem", func = "QuickSetToParSystem"}, | |
| }}, | |
| { id = "offset", category = "Rule", name = "Offset", editor = "point", default = point(0, 0, 0), }, | |
| { id = "axis" , category = "Rule", name = "Axis", editor = "point", default = point(0, 0, 0), }, | |
| { id = "angle", category = "Rule", name = "Angle", editor = "number", default = 0, scale = "deg" }, | |
| { id = "member", category = "Rule", name = "Member", help = "The name of the property of the parent object that should be pointing to the attach object.", editor = "text", default = "", }, | |
| { id = "required_state", category = "Rule", name = "Attach State", help = "Conditional attachment", default = "", editor = "combo", items = function(obj) | |
| return obj and obj.parent and obj.parent.parent and obj.parent.parent:GuessPossibleAutoattachStates() or {} | |
| end, }, | |
| { id = "GameStatesFilter", name="Game State", category = "Rule", editor = "set", default = set(), three_state = true, items = function() return GetGameStateFilter() end }, | |
| { id = "DetailClass", category = "Rule", name = "Detail Class Override", editor = "dropdownlist", | |
| items = {"Default", "Essential", "Optional", "Eye Candy"}, default = "Default", | |
| }, | |
| { id = "inherited_values", no_edit = true, editor = "prop_table", default = false, }, | |
| }, | |
| parent = false, | |
| } | |
| function AutoAttachRule:IsActive() | |
| return self.attach_class ~= "" | |
| end | |
| function AutoAttachRule:QuickSetToParSystem() | |
| self.attach_class = "ParSystem" | |
| ObjModified(self) | |
| end | |
| function AutoAttachRule:ResolveConditionFunc() | |
| local gamestates_filters = self.GameStatesFilter | |
| if not gamestates_filters or not next(gamestates_filters) then | |
| return false | |
| end | |
| return function(obj, attach) | |
| if gamestates_filters then | |
| for key, value in pairs(gamestates_filters) do | |
| if value then | |
| if not GameState[key] then return false end | |
| else | |
| if GameState[key] then return false end | |
| end | |
| end | |
| end | |
| return true | |
| end | |
| end | |
| function AutoAttachRule:GetCleanInheritedPropertyValues() | |
| local inherited_values = self.inherited_values | |
| if not inherited_values then | |
| return false | |
| end | |
| local inherited_props = self:GetInheritedProps() | |
| if not inherited_props or #inherited_props == 0 then | |
| return false | |
| end | |
| local clean_value_list = {} | |
| for _, prop in ipairs(inherited_props) do | |
| local value = inherited_values[prop.id] | |
| if value ~= nil then | |
| clean_value_list[prop.id] = value | |
| end | |
| end | |
| return clean_value_list | |
| end | |
| function AutoAttachRule:FillAutoAttachTable(attach_table, entity, preset, spot) | |
| if self.attach_class == "" then | |
| return attach_table | |
| end | |
| spot = spot or self.parent | |
| attach_table = attach_table or {} | |
| local attach_table_idle = attach_table["idle"] or {} | |
| attach_table["idle"] = attach_table_idle | |
| local istart, iend = GetSpotRange(spot.parent.id, "idle", spot.name) | |
| if istart < 0 then | |
| print(string.format("Warning: Could not find '%s' spot range for '%s'", spot.name, entity)) | |
| else | |
| table.insert(attach_table_idle, { | |
| spot_idx = istart + spot.idx - 1, | |
| [2] = self.attach_class, | |
| offset = self.offset, | |
| axis = self.axis ~= zeropoint and self.axis, | |
| angle = self.angle ~= 0 and self.angle, | |
| member = self.member ~= "" and self.member, | |
| required_state = self.required_state ~= "" and self.required_state or false, | |
| condition = self:ResolveConditionFunc() or false, | |
| DetailClass = self.DetailClass ~= "Default" and self.DetailClass, | |
| inherited_properties = self:GetCleanInheritedPropertyValues(), | |
| inherit_colorization = preset.PropagateColorization and CanInheritColorization(entity, self.attach_class), | |
| }) | |
| end | |
| return attach_table | |
| end | |
| function AutoAttachRule:GetEditorView() | |
| local str | |
| if self.attach_class == "ParSystem" then | |
| str = "Particles <color 198 25 198>" .. (self.inherited_values and self.inherited_values["ParticlesName"] or "?") .. "</color>" | |
| else | |
| str = "Attach <color 75 105 198>" .. (self.attach_class or "?") .. "</color>" | |
| end | |
| str = str .. " (" .. self.DetailClass .. ")" | |
| if self.required_state ~= "" then | |
| str = str .. " : <color 20 120 20>" .. self.required_state .. "</color>" | |
| end | |
| if self.attach_class == "" then | |
| str = "<color 168 168 168>" .. str .. "</color>" | |
| end | |
| return str | |
| end | |
| function AutoAttachRule:Setattach_class(value) | |
| if self.parent and self.parent.parent and self.parent.parent.id == value then | |
| value = "" | |
| return false | |
| end | |
| self.attach_class = value | |
| end | |
| function AutoAttachRule:GetInheritedProps() | |
| local properties = {} | |
| local class_obj = g_Classes[self.attach_class] | |
| if not class_obj then | |
| return properties | |
| end | |
| local orig_properties = PropertyObject.GetProperties(self) | |
| local properties_of_target_entity = class_obj:GetProperties() | |
| for _, prop in ipairs(properties_of_target_entity) do | |
| if prop.autoattach_prop then | |
| assert(not table.find(orig_properties, "id", prop.id), | |
| string.format("Property %s conflict between AutoAttachRule and %s", prop.id, self.attach_class)) | |
| prop = table.copy(prop) | |
| prop.dont_save = true | |
| table.insert(properties, prop) | |
| end | |
| end | |
| return properties | |
| end | |
| function AutoAttachRule:GetProperties() | |
| local properties = PropertyObject.GetProperties(self) | |
| local class_obj = g_Classes[self.attach_class] | |
| if not class_obj then | |
| return properties | |
| end | |
| properties = table.copy(properties) | |
| properties = table.iappend(properties, self:GetInheritedProps()) | |
| return properties | |
| end | |
| function AutoAttachRule:SetProperty(id, value) | |
| if table.find(self:GetInheritedProps(), "id", id) then | |
| self.inherited_values = self.inherited_values or {} | |
| self.inherited_values[id] = value | |
| return | |
| end | |
| PropertyObject.SetProperty(self, id, value) | |
| end | |
| function AutoAttachRule:GetProperty(id) | |
| if self.inherited_values and self.inherited_values[id] ~= nil then | |
| return self.inherited_values[id] | |
| end | |
| return PropertyObject.GetProperty(self, id) | |
| end | |
| function AutoAttachRule:OnEditorSetProperty(prop_id, old_value, ged) | |
| RebuildAutoattach() | |
| ged:ResolveObj("SelectedPreset"):RecreateDemoObject(ged) | |
| local id = self.parent.parent.id | |
| local class = rawget(_G, id) | |
| if class and not class:IsKindOf("AutoAttachObject") then | |
| return false | |
| end | |
| MapForEach("map", id, function(obj) | |
| obj:SetAutoAttachMode(obj:GetAutoAttachMode()) | |
| end) | |
| end | |
| function AutoAttachRule:GetMaxColorizationMaterials() | |
| if IsKindOf(_G[self.attach_class], "WaterObj") then return 3 end | |
| return self.attach_class ~= "" and IsValidEntity(self.attach_class) and ColorizationMaterialsCount(self.attach_class) or 0 | |
| end | |
| function AutoAttachRule:ColorizationReadOnlyReason() | |
| return false | |
| end | |
| function AutoAttachRule:ColorizationPropsNoEdit(i) | |
| if self.parent.parent.PropagateColorization then | |
| return true | |
| end | |
| return self:GetMaxColorizationMaterials() < i | |
| end | |
| DefineClass.AutoAttachSpot = { | |
| __parents = { "PropertyObject", "Container" }, | |
| properties = { | |
| { id = "name", name = "Spot Name", editor = "text", default = "", read_only = true }, | |
| { id = "idx", name = "Number", editor = "number", default = -1, read_only = true, }, | |
| { id = "original_index", name = "Original Index", editor = "number", default = -1, read_only = true, }, | |
| }, | |
| annotated_autoattach = false, | |
| EditorView = Untranslated("<Color><name> <idx><opt(u(attach_class), ' - <color 32 192 32>')> <AnnotatedAutoattachMsg>"), | |
| parent = false, | |
| ContainerClass = "AutoAttachRuleBase", | |
| } | |
| function AutoAttachSpot:Color() | |
| return not self:HasSomethingAttached() and "<color 168 168 168>" or "" | |
| end | |
| function AutoAttachSpot:HasSomethingAttached() | |
| if #self == 0 then return false end | |
| for _, rule in ipairs(self) do | |
| if rule:IsActive() then | |
| return true | |
| end | |
| end | |
| return false | |
| end | |
| function AutoAttachSpot:AnnotatedAutoattachMsg() | |
| if not self.annotated_autoattach then return "" end | |
| return "<color 158 22 22>" .. self.annotated_autoattach | |
| end | |
| function AutoAttachSpot.CreateRule(root, obj) | |
| obj[#obj + 1] = AutoAttachRule:new({parent = obj}) | |
| ObjModified(root) | |
| ObjModified(obj) | |
| end | |
| function CommonlyUsedAttachItems() | |
| local ret = {} | |
| ForEachPreset("AutoAttachPreset", function(preset) | |
| for _, rule in ipairs(preset) do | |
| for _, subrule in ipairs(rule) do | |
| local class = rawget(subrule, "attach_class") | |
| if class and class ~= "" then | |
| ret[class] = (ret[class] or 0) + 1 | |
| end | |
| end | |
| end | |
| end) | |
| for class, count in pairs(ret) do | |
| if count == 1 then | |
| ret[class] = nil | |
| end | |
| end | |
| return table.keys2(ret, "sorted") | |
| end | |
| DefineClass.AutoAttachPresetFilter = { | |
| __parents = { "GedFilter" }, | |
| properties = { | |
| { id = "NonEmpty", name = "Only show non-empty entries", default = false, editor = "bool" }, | |
| { id = "HasAttach", name = "Has attach of class", default = false, editor = "combo", items = CommonlyUsedAttachItems }, | |
| { id = "_", editor = "buttons", default = false, buttons = { { name = "Add new AutoAttach entity", func = "AddEntity" } } }, | |
| }, | |
| } | |
| function AutoAttachPresetFilter:FilterObject(obj) | |
| if self.NonEmpty then | |
| for _, rule in ipairs(obj) do | |
| for _, subrule in ipairs(rule) do | |
| if subrule:IsKindOf("AutoAttachRule") and subrule.attach_class ~= "" then | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| local class = self.HasAttach | |
| if class then | |
| for _, rule in ipairs(obj) do | |
| for _, subrule in ipairs(rule) do | |
| if subrule:IsKindOf("AutoAttachRule") and subrule.attach_class == class then | |
| return true | |
| end | |
| end | |
| end | |
| return false | |
| end | |
| return true | |
| end | |
| function AutoAttachPresetFilter:AddEntity(root, prop_id, ged) | |
| local entities = {} | |
| ForEachPreset("EntitySpec", function(preset) | |
| if not string.find(preset.class_parent, "AutoAttachObject", 1, true) and not preset.id:starts_with("#") then | |
| entities[#entities + 1] = preset.id | |
| end | |
| end) | |
| local entity = ged:WaitListChoice(entities, "Choose entity to add:") | |
| if not entity then | |
| return | |
| end | |
| local spec = EntitySpecPresets[entity] | |
| if spec.class_parent == "" then | |
| spec.class_parent = "AutoAttachObject" | |
| else | |
| spec.class_parent = spec.class_parent .. ",AutoAttachObject" | |
| end | |
| GedSetUiStatus("add_autoattach_entity", "Saving ArtSpec...") | |
| EntitySpec:SaveAll() | |
| self.NonEmpty = false | |
| self.HasAttach = false | |
| GenerateMissingEntities() | |
| ged:SetSelection("root", { 1, table.find(Presets.AutoAttachPreset.Default, "id", entity) }) | |
| GedSetUiStatus("add_autoattach_entity") | |
| ged:ShowMessage(Untranslated("Attention!"), Untranslated("You need to commit both the assets and the project folder!")) | |
| end | |
| DefineClass.AutoAttachPreset = { | |
| __parents = { "Preset" }, | |
| properties = { | |
| { id = "Id", read_only = true, }, | |
| { id = "SaveIn", read_only = true, }, | |
| { id = "help", editor = "buttons", buttons = {{name = "Go to ArtSpec", func = "GotoArtSpec"}}, default = false,}, | |
| { id = "PropagateColorization", editor = "bool", default = true }, | |
| }, | |
| GlobalMap = "AutoAttachPresets", | |
| ContainerClass = "AutoAttachSpot", | |
| GedEditor = "GedAutoAttachEditor", | |
| EditorMenubar = "Editors.Art", | |
| EditorMenubarName = "AutoAttach Editor", | |
| EditorIcon = "CommonAssets/UI/Icons/attach attachment paperclip.png", | |
| FilterClass = "AutoAttachPresetFilter", | |
| EnableReloading = false, | |
| } | |
| function AutoAttachPreset:GuessPossibleAutoattachStates() | |
| return GetEntityAutoAttachModes(nil, self.id) | |
| end | |
| function AutoAttachPreset:EditorContext() | |
| local context = Preset.EditorContext(self) | |
| context.Classes = {} | |
| context.ContainerTree = true | |
| return context | |
| end | |
| function AutoAttachPreset:EditorItemsMenu() | |
| return {} | |
| end | |
| function AutoAttachPreset:GotoArtSpec(root) | |
| local editor = OpenPresetEditor("EntitySpec") | |
| local spec = self:GetEntitySpec() | |
| local root = editor:ResolveObj("root") | |
| local group_idx = table.find(root, root[spec.group]) | |
| local idx = table.find(root[spec.group], spec) | |
| editor:SetSelection("root", {group_idx, idx}) | |
| end | |
| function AutoAttachPreset:PostLoad() | |
| for idx, item in ipairs(self) do | |
| item.parent = self | |
| for _, subitem in ipairs(item) do | |
| subitem.parent = item | |
| end | |
| end | |
| Preset.PostLoad(self) | |
| end | |
| function AutoAttachPreset:GenerateCode(code) | |
| self:UpdateSpotData() -- to read save_in | |
| -- drop redundant/unneeded data | |
| local has_something_attached = false | |
| for i = #self, 1, -1 do | |
| local spot = self[i] | |
| if not spot:HasSomethingAttached() then | |
| table.remove(self, i) | |
| else | |
| spot.original_index = nil | |
| spot.annotated_autoattach = nil | |
| has_something_attached = true | |
| if not spot[#spot]:IsActive() then | |
| table.remove(spot, #spot) | |
| end | |
| end | |
| end | |
| if has_something_attached then | |
| Preset.GenerateCode(self, code) | |
| end | |
| self:UpdateSpotData() -- to write original_index and annotated_autoattach back to the structure | |
| end | |
| function AutoAttachPreset:GetSpot(name, idx) | |
| for i, value in ipairs(self) do | |
| if value.name == name and value.idx == idx then | |
| return i, value | |
| end | |
| end | |
| end | |
| function AutoAttachPreset:UpdateSpotData() | |
| local spec = self:GetEntitySpec() | |
| if not spec then | |
| return | |
| end | |
| self.save_in = spec:GetSaveIn() | |
| local spots = GetEntitySpots(self.id) | |
| -- drop additional spots | |
| for i = #self, 1, -1 do | |
| local entry = self[i] | |
| if entry.idx > (spots[entry.name] and #spots[entry.name] or -1) then | |
| table.remove(self, i) | |
| end | |
| end | |
| for spot_name, indices in pairs(spots) do | |
| for idx = 1, #indices do | |
| local internal_idx, spot = self:GetSpot(spot_name, idx) | |
| if spot then | |
| spot.original_index = indices[idx] | |
| spot.annotated_autoattach = GetSpotAnnotation(self.id, indices[idx]) | |
| else | |
| spot = AutoAttachSpot:new({ | |
| name = spot_name, | |
| idx = idx, | |
| original_index = indices[idx], | |
| annotated_autoattach = GetSpotAnnotation(self.id, indices[idx]), | |
| }) | |
| table.insert(self, spot) | |
| end | |
| spot.parent = self | |
| end | |
| end | |
| table.sort(self, function(a, b) | |
| if a.name < b.name then return true end | |
| if a.name > b.name then return false end | |
| if a.idx < b.idx then return true end | |
| return false | |
| end) | |
| end | |
| if FirstLoad then | |
| GedAutoAttachEditorLockedObject = {} | |
| GedAutoAttachDemos = {} | |
| end | |
| DefineClass.AutoAttachPresetDemoObject = { | |
| __parents = {"Shapeshifter", "AutoAttachObject"} | |
| } | |
| AutoAttachPresetDemoObject.ShouldAttach = return_true | |
| function AutoAttachPresetDemoObject:ChangeEntity(entity) | |
| self:DestroyAutoAttaches() | |
| self:ClearAttachMembers() | |
| Shapeshifter.ChangeEntity(self, entity) | |
| self:DestroyAutoAttaches() | |
| self:ClearAttachMembers() | |
| AutoAttachShapeshifterObjects(self) | |
| end | |
| function AutoAttachPresetDemoObject:CreateLightHelpers() | |
| self:ForEachAttach(function(attach) | |
| if IsKindOf(attach, "Light") then | |
| PropertyHelpers_Init(attach) | |
| end | |
| end) | |
| end | |
| function AutoAttachPresetDemoObject:AutoAttachObjects() | |
| AutoAttachShapeshifterObjects(self) | |
| self:CreateLightHelpers() | |
| end | |
| function AutoAttachPreset:ViewDemoObject(ged) | |
| local demo_obj = GedAutoAttachDemos[ged] | |
| if demo_obj and IsValid(demo_obj) then | |
| ViewObject(demo_obj) | |
| end | |
| end | |
| function AutoAttachPreset:RecreateDemoObject(ged) | |
| if CurrentMap == "" then | |
| return | |
| end | |
| if ged and ged.context.lock_preset then | |
| local obj = GedAutoAttachEditorLockedObject[ged] | |
| obj:DestroyAutoAttaches() | |
| obj:ClearAttachMembers() | |
| AutoAttachObjects(GedAutoAttachEditorLockedObject[ged], "init") | |
| return | |
| end | |
| local demo_obj = GedAutoAttachDemos[ged] | |
| if not demo_obj or not IsValid(demo_obj) then | |
| demo_obj = PlaceObject("AutoAttachPresetDemoObject") | |
| local look_at = GetTerrainGamepadCursor() | |
| look_at = look_at:SetZ(terrain.GetSurfaceHeight(look_at)) | |
| demo_obj:SetPos(look_at) | |
| end | |
| GedAutoAttachDemos[ged] = demo_obj | |
| demo_obj:ChangeEntity(self.id) | |
| end | |
| function OnMsg.GedClosing(ged_id) | |
| local demo_obj = GedAutoAttachDemos[GedConnections[ged_id]] | |
| DoneObject(demo_obj) | |
| GedAutoAttachDemos[GedConnections[ged_id]] = nil | |
| end | |
| function AutoAttachPreset:OnEditorSelect(selected, ged) | |
| if selected then | |
| self:UpdateSpotData() | |
| self:RecreateDemoObject(ged) | |
| end | |
| end | |
| function AutoAttachPreset:GetError() | |
| if not self:GetEntitySpec() then | |
| return "Could not find the ArtSpec." | |
| end | |
| end | |
| function AutoAttachPreset:GetEntitySpec() | |
| return FindArtSpecById(self.id) | |
| end | |
| function OnMsg.GedOpened(ged_id) | |
| local ged = GedConnections[ged_id] | |
| if ged and ged:ResolveObj("root") == Presets.AutoAttachPreset then | |
| CreateRealTimeThread(GenerateMissingEntities) | |
| end | |
| end | |
| function OpenAutoattachEditor(objlist, lock_entity) | |
| if not IsRealTimeThread() then | |
| CreateRealTimeThread(OpenAutoattachEditor, entity) | |
| return | |
| end | |
| lock_entity = not not lock_entity | |
| local target_entity | |
| if objlist and objlist[1] and IsValid(objlist[1]) then | |
| target_entity = objlist[1] | |
| end | |
| if not target_entity and lock_entity then | |
| print("No entity selected.") | |
| return | |
| end | |
| if target_entity then | |
| GenerateMissingEntities() -- make sure all entities are generated. Otherwise the selection may fail | |
| end | |
| local context = AutoAttachPreset:EditorContext() | |
| context.lock_preset = lock_entity | |
| local ged = OpenPresetEditor("AutoAttachPreset", context) | |
| if target_entity then | |
| ged:SetSelection("root", PresetGetPath(AutoAttachPresets[target_entity:GetEntity()])) | |
| GedAutoAttachEditorLockedObject[ged] = target_entity | |
| end | |
| end | |
| DefineClass.AutoAttachSIModulator = | |
| { | |
| __parents = {"CObject", "PropertyObject"}, | |
| properties = { | |
| { id = "SIModulation", editor = "number", default = 100, min = 0, max = 255, slider = true, autoattach_prop = true }, | |
| } | |
| } | |
| function AutoAttachSIModulator:SetSIModulation(value) | |
| local parent = self:GetParent() | |
| if not parent.SIModulationManual then | |
| parent:SetSIModulation(value) | |
| end | |
| end |