Download CommonLua/Classes/CompositeBody.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 47.8 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Classes/CompositeBody.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Classes/CompositeBody.lua
-
curl -L -o CompositeBody.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Classes/CompositeBody.lua
47.8 kB
| function GetSpotOffset(obj, name, idx, state, phase) | |
| assert(obj) | |
| if not IsValid(obj) then | |
| return 0, 0, 0, "obj" | |
| end | |
| idx = idx or obj:GetSpotBeginIndex(name) or -1 | |
| if idx == -1 then | |
| return 0, 0, 0, "spot" | |
| end | |
| state = state or "idle" | |
| phase = phase or 0 | |
| local x, y, z = GetEntitySpotPos(obj, state, phase, idx, idx, true):xyz() | |
| local s = obj:GetWorldScale() | |
| if s ~= 100 then | |
| x, y, z = x * s / 100, y * s / 100, z * s / 100 | |
| end | |
| return x, y, z | |
| end | |
| function GetLocalAngleDiff(attach, local_angle) | |
| return abs(AngleDiff(attach:GetVisualAngleLocal(), local_angle)) | |
| end | |
| function GetLocalRotationTime(attach, local_angle, speed) | |
| return MulDivRound(1000, GetLocalAngleDiff(attach, local_angle), speed) | |
| end | |
| function GetLocalAngle(obj, angle) | |
| return AngleDiff(angle, obj:GetAngle()) | |
| end | |
| ---- | |
| DefineClass.CompositeBodyPart = { | |
| __parents = { "ComponentAnim", "ComponentAttach", "ColorizableObject" }, | |
| flags = { gofSyncState = true, efWalkable = false, efApplyToGrids = false, efCollision = false, efSelectable = true }, | |
| } | |
| function CompositeBodyPart:GetName() | |
| local parent = self:GetParent() | |
| while IsValid(parent) do | |
| if IsKindOf(parent, "CompositeBody") then | |
| for name, part in pairs(parent.attached_parts) do | |
| if part == self then | |
| return name | |
| end | |
| end | |
| return | |
| else | |
| parent = parent:GetParent() | |
| end | |
| end | |
| end | |
| local function RecomposeBody(obj) | |
| for name, part in pairs(obj.attached_parts) do | |
| if part ~= obj then | |
| obj:RemoveBodyPart(part, name) | |
| end | |
| end | |
| obj.attached_parts = nil | |
| obj:ComposeBodyParts() | |
| end | |
| local function EditorRecomposeBodiesOnMap(obj, root, prop_id, ged) | |
| if IsValid(obj) then | |
| RecomposeBody(obj) | |
| elseif obj.object_class then | |
| MapForEach("map", obj.object_class, RecomposeBody) | |
| end | |
| end | |
| local function get_body_parts_count(self) | |
| local class_name = self.id | |
| local class = g_Classes[class_name] or empty_table | |
| local target = self.composite_part_target or class.composite_part_target or class_name | |
| local composite_part_groups = self.composite_part_groups or class.composite_part_groups or { class_name } | |
| local part_presets = Presets.CompositeBodyPreset | |
| local count = 0 | |
| for _, part_name in ipairs(self.composite_part_names or class.composite_part_names) do | |
| for _, part_group in ipairs(composite_part_groups) do | |
| for _, part_preset in ipairs(part_presets[part_group] or empty_table) do | |
| if (not target or part_preset.Target == target) and (part_preset.Parts or empty_table)[part_name] then | |
| count = count + 1 | |
| end | |
| end | |
| end | |
| end | |
| return count | |
| end | |
| -- Composite bodies change the entity, scale and colors of the unit." | |
| DefineClass.CompositeBody = { | |
| __parents = { "Object", "CompositeBodyPart" }, | |
| properties = { | |
| { category = "Composite Body", id = "recompose", name = "Recompose", editor = "buttons", default = false, template = true, buttons = { { name = "Recompose", func = function(...) return EditorRecomposeBodiesOnMap(...) end, } } }, | |
| { category = "Composite Body", id = "composite_part_names", name = "Parts", editor = "string_list", template = true, help = "Composite body parts. Each body preset may cover one or more parts. Each part may have another part as a parent and a custom attach spot.", body_part_match = true }, | |
| { category = "Composite Body", id = "composite_part_main", name = "Main Part", editor = "choice", items = PropGetter("composite_part_names"), template = true, help = "Main body part to be applied directly to the composite object." }, | |
| { category = "Composite Body", id = "composite_part_target", name = "Target", editor = "text", template = true, help = "Will match composite body presets having the same target. If not specified, the class name is used.", body_part_match = true }, | |
| { category = "Composite Body", id = "composite_part_groups", name = "Groups", editor = "string_list", items = PresetGroupsCombo("CompositeBodyPreset"), template = true, help = "Will match composite body presets from those groups. If not specified, the class name is used as a group name.", body_part_match = true }, | |
| { category = "Composite Body", id = "CompositePartCount", name = "Parts Found", editor = "number", template = true, default = 0, dont_save = true, read_only = 0, getter = get_body_parts_count }, | |
| { category = "Composite Body", id = "composite_part_parent", name = "Parent", editor = "prop_table", read_only = true, template = true, help = "Defines custom parent for each body part." }, | |
| { category = "Composite Body", id = "composite_part_spots", name = "Spots", editor = "prop_table", read_only = true, template = true, help = "Defines custom attach spots for each body part." }, | |
| { category = "Composite Body", id = "cycle_colors", name = "Cycle Colors", editor = "bool", default = false, template = true, help = "If you can cycle through the composite body colors during construction.", }, | |
| }, | |
| flags = { gofSyncState = false, gofPropagateState = true }, | |
| composite_seed = false, | |
| colorization_offset = 0, | |
| composite_part_target = false, | |
| composite_part_names = { "Body" }, | |
| composite_part_spots = false, | |
| composite_part_parent = false, | |
| composite_part_main = "Body", | |
| composite_part_groups = false, | |
| attached_parts = false, | |
| override_parts = false, | |
| override_parts_spot = false, | |
| InitBodyParts = empty_func, | |
| SetAutoAttachMode = empty_func, | |
| ChangeEntityDisabled = empty_func, | |
| } | |
| function CompositeBody:CheatCompose() | |
| self:ComposeBodyParts() | |
| end | |
| local props = CompositeBody.properties | |
| for i=1,10 do | |
| local category = "Composite Body Hierarchy" | |
| local function no_edit(self) | |
| local names = self:GetProperty("composite_part_names") or empty_table | |
| local name = names[i] | |
| return not name or name == self:GetProperty("composite_part_main") | |
| end | |
| local function GetPartName(self) | |
| local names = self:GetProperty("composite_part_names") | |
| return names[i] or "" | |
| end | |
| local function GetSpotName(self) | |
| local name = GetPartName(self) | |
| return name .. " Spot" | |
| end | |
| local function GetParentName(self) | |
| local name = GetPartName(self) | |
| return name .. " Parent" | |
| end | |
| local spot_id = "composite_part_spot_" .. i | |
| local parent_id = "composite_part_parent_" .. i | |
| local function getter(self, prop_id) | |
| local target_id | |
| if prop_id == spot_id then | |
| target_id = "composite_part_spots" | |
| elseif prop_id == parent_id then | |
| target_id = "composite_part_parent" | |
| else | |
| return "" | |
| end | |
| local name = GetPartName(self) | |
| local map = self:GetProperty(target_id) | |
| return map and map[name] or "" | |
| end | |
| local function setter(self, value, prop_id) | |
| local target_id | |
| if prop_id == spot_id then | |
| target_id = "composite_part_spots" | |
| elseif prop_id == parent_id then | |
| target_id = "composite_part_parent" | |
| else | |
| return | |
| end | |
| local name = GetPartName(self) | |
| local map = self:GetProperty(target_id) or empty_table | |
| map = table.raw_copy(map) | |
| map[name] = (value or "") ~= "" and value or nil | |
| rawset(self, target_id, map) | |
| end | |
| local function GetParentItems(self) | |
| local names = self:GetProperty("composite_part_names") or empty_table | |
| if names[i] then | |
| names = table.icopy(names) | |
| table.remove_value(names, names[i]) | |
| end | |
| return names, return_true | |
| end | |
| table.iappend(props, { | |
| { category = category, id = spot_id, name = GetSpotName, editor = "text", default = "", dont_save = true, getter = getter, setter = setter, no_edit = no_edit, template = true }, | |
| { category = category, id = parent_id, name = GetParentName, editor = "choice", default = "", items = GetParentItems, dont_save = true, getter = getter, setter = setter, no_edit = no_edit, template = true }, | |
| }) | |
| CompositeBody["Get" .. spot_id] = function(self) | |
| return getter(self, spot_id) | |
| end | |
| CompositeBody["Get" .. parent_id] = function(self) | |
| return getter(self, parent_id) | |
| end | |
| CompositeBody["Set" .. spot_id] = function(self, value) | |
| return setter(self, spot_id, value) | |
| end | |
| CompositeBody["Set" .. parent_id] = function(self, value) | |
| return setter(self, parent_id, value) | |
| end | |
| end | |
| function CompositeBody:Done() | |
| -- allow garbage collection of CompositeBody objects which otherwise have a non-weak reference to themselves | |
| self.attached_parts = nil | |
| self.override_parts = nil | |
| end | |
| function CompositeBody:GetPart(name) | |
| local parts = self.attached_parts | |
| return parts and parts[name] | |
| end | |
| function CompositeBody:GetPartName(part_to_find) | |
| for name, part in pairs(self.attached_parts) do | |
| if part == part_to_find then | |
| return name | |
| end | |
| end | |
| end | |
| function CompositeBody:ForEachBodyPart(func, ...) | |
| local attached_parts = self.attached_parts or empty_table | |
| for _, name in ipairs(self.composite_part_names) do | |
| local part = attached_parts[name] | |
| if part then | |
| func(part, self, ...) | |
| end | |
| end | |
| end | |
| function CompositeBody:UpdateEntity() | |
| return self:ComposeBodyParts() | |
| end | |
| local function ResolveCompositeMainEntity(classdef) | |
| if not classdef then return end | |
| local composite_part_groups = classdef.composite_part_groups | |
| local composite_part_group = composite_part_groups and composite_part_groups[1] or classdef.class | |
| local part_presets = table.get(Presets, "CompositeBodyPreset", composite_part_group) | |
| if next(part_presets) then | |
| local composite_part_target = classdef.composite_part_target | |
| local composite_part_main = classdef.composite_part_main or "Body" | |
| for _, part_preset in ipairs(part_presets) do | |
| if not composite_part_target or composite_part_target == part_preset.Target then | |
| if (part_preset.Parts or empty_table)[composite_part_main] then | |
| return part_preset.Entity | |
| end | |
| end | |
| end | |
| end | |
| return classdef.entity or classdef.class | |
| end | |
| function ResolveTemplateEntity(self) | |
| local entity = IsValid(self) and self:GetEntity() | |
| if IsValidEntity(entity) then | |
| return entity | |
| end | |
| local class = self.id or self.class | |
| local classdef = g_Classes[class] | |
| if not classdef then return end | |
| entity = ResolveCompositeMainEntity(classdef) | |
| return IsValidEntity(entity) and entity | |
| end | |
| function TemplateSpotItems(self) | |
| local entity = ResolveTemplateEntity(self) | |
| if not entity then return {} end | |
| local spots = {{ value = false, text = "" }} | |
| local seen = {} | |
| local spbeg, spend = GetAllSpots(entity) | |
| for spot = spbeg, spend do | |
| local name = GetSpotName(entity, spot) | |
| if not seen[name] then | |
| seen[name] = true | |
| spots[#spots + 1] = { value = name, text = name } | |
| end | |
| end | |
| table.sortby_field(spots, "text") | |
| return spots | |
| end | |
| function CompositeBody:CollectBodyParts(part_to_preset, seed) | |
| local target = self.composite_part_target or self.class | |
| local composite_part_groups = self.composite_part_groups or { self.class } | |
| local part_presets = Presets.CompositeBodyPreset | |
| for _, part_name in ipairs(self.composite_part_names) do | |
| if not part_to_preset[part_name] then | |
| local matched_preset, matched_presets | |
| for _, part_group in ipairs(composite_part_groups) do | |
| for _, part_preset in ipairs(part_presets[part_group]) do | |
| if (not target or part_preset.Target == target) and (part_preset.Parts or empty_table)[part_name] then | |
| local matched = true | |
| for _, filter in ipairs(part_preset.Filters) do | |
| if not filter:Match(self) then | |
| matched = false | |
| break | |
| end | |
| end | |
| if matched then | |
| if not matched_preset or matched_preset.ZOrder < part_preset.ZOrder then | |
| matched_preset = part_preset | |
| matched_presets = nil | |
| elseif matched_preset.ZOrder == part_preset.ZOrder then | |
| if matched_presets then | |
| matched_presets[#matched_presets + 1] = part_preset | |
| else | |
| matched_presets = { matched_preset, part_preset } | |
| end | |
| end | |
| end | |
| end | |
| end | |
| end | |
| if matched_presets then | |
| seed = self:ComposeBodyRand(seed) | |
| matched_preset = table.weighted_rand(matched_presets, "Weight", seed) | |
| end | |
| if matched_preset then | |
| part_to_preset[part_name] = matched_preset | |
| end | |
| end | |
| end | |
| return seed | |
| end | |
| function CompositeBody:GetConstructionCopyObjectData(copy_data) | |
| table.rawset_values(copy_data, self, "composite_seed", "colorization_offset") | |
| end | |
| function CompositeBody:GetConstructionCursorDynamicData(controller, cursor_data) | |
| table.rawset_values(cursor_data, controller, "composite_seed", "colorization_offset") | |
| end | |
| function CompositeBody:GetConstructionControllerDynamicData(controller_data) | |
| table.rawset_values(controller_data, self, "composite_seed", "colorization_offset") | |
| end | |
| function OnMsg.GatherConstructionInitData(construction_init_data) | |
| rawset(construction_init_data, "composite_seed", true) | |
| rawset(construction_init_data, "colorization_offset", true) | |
| end | |
| function CompositeBody:ComposeBodyRand(seed, ...) | |
| seed = seed or self.composite_seed or self:RandSeed("Body") | |
| self.composite_seed = self.composite_seed or seed | |
| return BraidRandom(seed, ...) | |
| end | |
| function CompositeBody:GetPartFXTarget(part) | |
| return self | |
| end | |
| function CompositeBody:ComposeBodyParts(seed) | |
| if self:ChangeEntityDisabled() then | |
| return | |
| end | |
| local part_to_preset = { } | |
| -- collect the best matched body presets for the remaining parts without equipment | |
| seed = self:CollectBodyParts(part_to_preset, seed) or seed | |
| -- apply the main body entity (all others are attached to this one) | |
| local main_name = self.composite_part_main | |
| local main_preset = main_name and part_to_preset[main_name] | |
| if not main_preset and not IsValidEntity(self:GetEntity()) then | |
| return | |
| end | |
| local applied_presets = {} | |
| local changed | |
| if main_preset then | |
| local changed_i, seed_i = self:ApplyBodyPart(self, main_preset, main_name, seed) | |
| assert(IsValidEntity(self:GetEntity())) | |
| changed = changed_i or changed | |
| seed = seed_i or seed | |
| applied_presets = { [main_preset] = true } | |
| end | |
| local last_part_class, part_def | |
| local override_parts = self.override_parts or empty_table | |
| -- apply all the remaining as attaches (removing the unused ones from the previous procedure) | |
| local attached_parts = self.attached_parts or {} | |
| attached_parts[main_name] = self | |
| self.attached_parts = attached_parts | |
| for _, part_name in ipairs(self.composite_part_names) do | |
| if part_name == main_name then | |
| goto continue | |
| end | |
| local part_obj = attached_parts[part_name] | |
| --body part overriding | |
| local override = override_parts[part_name] | |
| if override then | |
| if override ~= part_obj then | |
| if part_obj then | |
| self:RemoveBodyPart(part_obj, part_name) | |
| end | |
| attached_parts[part_name] = override | |
| local parent = self | |
| if override:GetParent() ~= parent then | |
| local spot = self.override_parts_spot and self.override_parts_spot[part_name] | |
| spot = spot or self.composite_part_spots[part_name] | |
| local spot_idx = spot and parent:GetSpotBeginIndex(spot) | |
| parent:Attach(override, spot_idx) | |
| end | |
| end | |
| goto continue | |
| end | |
| --preset search | |
| local preset = part_to_preset[part_name] | |
| if preset and not applied_presets[preset] then | |
| applied_presets[preset] = true | |
| if preset.Entity ~= "" then | |
| local part_class = preset.PartClass or "CompositeBodyPart" | |
| if not IsValid(part_obj) or part_obj.class ~= part_class then | |
| if last_part_class ~= part_class then | |
| last_part_class = part_class | |
| part_def = g_Classes[part_class] | |
| assert(part_def) | |
| part_def = part_def or CompositeBodyPart | |
| end | |
| DoneObject(part_obj) | |
| part_obj = part_def:new() | |
| attached_parts[part_name] = part_obj | |
| changed = true | |
| end | |
| local changed_i, seed_i = self:ApplyBodyPart(part_obj, preset, part_name, seed) | |
| changed = changed_i or changed | |
| seed = seed_i or seed | |
| goto continue | |
| end | |
| end | |
| -- 1) body part preset not found | |
| -- 2) part already covered, should be removed | |
| -- 3) part used to specify a missing part | |
| if part_obj then | |
| attached_parts[part_name] = nil | |
| self:RemoveBodyPart(part_obj, part_name) | |
| end | |
| ::continue:: | |
| end | |
| if changed then | |
| self:NetUpdateHash("BodyChanged", seed) | |
| end | |
| self:InitBodyParts() | |
| return changed | |
| end | |
| local def_scale = range(100, 100) | |
| function CompositeBody:ChangeBodyPartEntity(part, preset, name) | |
| local entity = preset.Entity | |
| if (preset.AffectedBy or "") ~= "" and (preset.EntityWhenAffected or "") ~= "" and self.attached_parts[preset.AffectedBy] then | |
| entity = preset.EntityWhenAffected | |
| end | |
| local current_entity = part:GetEntity() | |
| if current_entity == entity or not IsValidEntity(entity) then | |
| return | |
| end | |
| if current_entity ~= "" then | |
| PlayFX("ApplyBodyPart", "end", part, self:GetPartFXTarget(part)) | |
| end | |
| local state = part:GetGameFlags(const.gofSyncState) == 0 and EntityStates.idle or nil | |
| part:ChangeEntity(entity, state) | |
| return true | |
| end | |
| function CompositeBody:ChangeBodyPartScale(part, name, scale) | |
| if part:GetScale() ~= scale then | |
| part:SetScale(scale) | |
| return true | |
| end | |
| end | |
| function CompositeBody:ApplyBodyPart(part, preset, name, seed) | |
| -- entity | |
| local changed_entity = self:ChangeBodyPartEntity(part, preset, name) | |
| local changed = changed_entity | |
| -- mirrored | |
| if part:GetMirrored() ~= preset.Mirrored then | |
| part:SetMirrored(preset.Mirrored) | |
| changed = true | |
| end | |
| -- scale | |
| local scale = 100 | |
| local scale_range = preset.Scale | |
| if scale_range ~= def_scale then | |
| local scale_min, scale_max = scale_range.from, scale_range.to | |
| if scale_min == scale_max then | |
| scale = scale_min | |
| else | |
| scale, seed = self:ComposeBodyRand(seed, scale_min, scale_max) | |
| end | |
| end | |
| if self:ChangeBodyPartScale(part, name, scale) then | |
| changed = true | |
| end | |
| -- color | |
| seed = self:ColorizeBodyPart(part, preset, name, seed) or seed | |
| -- attach | |
| if part ~= self then | |
| local axis = preset.Axis | |
| if axis and part:GetAxisLocal() ~= axis then | |
| part:SetAxis(axis) | |
| changed = true | |
| end | |
| local angle = preset.Angle | |
| if angle and part:GetAngleLocal() ~= angle then | |
| part:SetAngle(angle) | |
| changed = true | |
| end | |
| local spot_name = preset.AttachSpot or "" | |
| if spot_name == "" then | |
| local spots = self.composite_part_spots | |
| spot_name = spots and spots[name] or "" | |
| if spot_name == "" then | |
| spot_name = "Origin" | |
| end | |
| end | |
| local sync_state = preset.SyncState | |
| if sync_state == "auto" then | |
| sync_state = spot_name == "Origin" | |
| end | |
| if not sync_state then | |
| part:ClearGameFlags(const.gofSyncState) | |
| else | |
| part:SetGameFlags(const.gofSyncState) | |
| end | |
| local prev_parent, prev_spot_idx = part:GetParent(), part:GetAttachSpot() | |
| local parents = self.composite_part_parent | |
| local parent_part = preset.Parent or parents and parents[name] or "" | |
| local parent = parent_part ~= "" and self.attached_parts[parent_part] or self | |
| local spot_idx = parent:GetSpotBeginIndex(spot_name) | |
| assert(spot_idx ~= -1, string.format("Failed to attach body part %s to spot %s of %s with state %s", name, spot_name, parent:GetEntity(), parent:GetStateText())) | |
| if prev_parent ~= parent or prev_spot_idx ~= spot_idx then | |
| parent:Attach(part, spot_idx) | |
| changed = true | |
| end | |
| local attach_offset = preset.AttachOffset or point30 | |
| local attach_axis = preset.AttachAxis or axis_z | |
| local attach_angle = preset.AttachAngle or 0 | |
| if attach_offset ~= part:GetAttachOffset() or attach_axis ~= part:GetAttachAxis() or attach_angle ~= part:GetAttachAngle() then | |
| part:SetAttachOffset(attach_offset) | |
| part:SetAttachAxis(attach_axis) | |
| part:SetAttachAngle(attach_angle) | |
| changed = true | |
| end | |
| end | |
| local changed_fx | |
| local fx_actor_class = (preset.FxActor or "") ~= "" and preset.FxActor or nil | |
| local current_fx_actor = rawget(part, "fx_actor_class") -- avoid clearing class fx actor with the default FxActor value | |
| if current_fx_actor ~= fx_actor_class then | |
| if current_fx_actor then | |
| PlayFX("ApplyBodyPart", "end", part, self:GetPartFXTarget(part)) | |
| end | |
| part.fx_actor_class = fx_actor_class | |
| changed_fx = true | |
| end | |
| if changed_fx or changed_entity then | |
| PlayFX("ApplyBodyPart", "start", part, self:GetPartFXTarget(part)) | |
| end | |
| return changed, seed | |
| end | |
| function CompositeBody:ColorizeBodyPart(part, preset, name, seed) | |
| local inherit_from = preset.ColorInherit | |
| local colorization = inherit_from ~= "" and table.get(self.attached_parts, inherit_from) | |
| if not colorization then | |
| seed = self:ComposeBodyRand(seed) | |
| local colors = preset.Colors or empty_table | |
| local idx | |
| colorization, idx = table.weighted_rand(colors, "Weight", seed) | |
| local offset = self.colorization_offset | |
| if idx and offset then | |
| idx = ((idx + offset - 1) % #colors) + 1 | |
| colorization = colors[idx] | |
| end | |
| end | |
| part:SetColorization(colorization) | |
| return seed | |
| end | |
| function CompositeBody:SetColorizationOffset(offset) | |
| local part_to_preset = {} | |
| local seed = self.composite_seed | |
| self:CollectBodyParts(part_to_preset, seed) | |
| local attached_parts = self.attached_parts | |
| self.colorization_offset = offset | |
| for _, part_name in ipairs(self.composite_part_names) do | |
| local preset = part_to_preset[part_name] | |
| if preset then | |
| local part = attached_parts[part_name] | |
| self:ColorizeBodyPart(part, preset, part_name, seed, offset) | |
| end | |
| end | |
| end | |
| function CompositeBody:RemoveBodyPart(part, name) | |
| DoneObject(part) | |
| end | |
| function CompositeBody:OverridePart(name, obj, spot) | |
| if not IsValid(self) or IsBeingDestructed(self) then | |
| return | |
| end | |
| assert(table.find(self.composite_part_names, name), "Invalid part name") | |
| if type(obj) == "string" and IsValidEntity(obj) then | |
| local entity = obj | |
| obj = CompositeBodyPart:new() | |
| obj:ChangeEntity(entity) | |
| AutoAttachObjects(obj) | |
| end | |
| if IsValid(obj) then | |
| self.override_parts = self.override_parts or {} | |
| assert(not self.override_parts[name], "Part already overridden") | |
| self.override_parts[name] = obj | |
| self.override_parts_spot = self.override_parts_spot or {} | |
| self.override_parts_spot[name] = spot | |
| elseif self.override_parts then | |
| obj = self.override_parts[name] | |
| if self.attached_parts[name] == obj then | |
| self.attached_parts[name] = nil | |
| end | |
| self.override_parts[name] = nil | |
| self.override_parts_spot[name] = nil | |
| end | |
| self:ComposeBodyParts() | |
| return obj | |
| end | |
| function CompositeBody:RemoveOverridePart(name) | |
| local part = self:OverridePart(name, false) | |
| if IsValid(part) then | |
| self:RemoveBodyPart(part) | |
| end | |
| end | |
| local composite_body_targets, composite_body_filters, composite_body_parts, composite_body_defs | |
| function CompositeBody:OnEditorSetProperty(prop_id, old_value, ged) | |
| local prop_meta = self:GetPropertyMetadata(prop_id) or empty_table | |
| if prop_meta.body_part_match then | |
| composite_body_targets = nil | |
| end | |
| if prop_meta.body_part_filter then | |
| self:ComposeBodyParts() | |
| end | |
| return Object.OnEditorSetProperty(self, prop_id, old_value, ged) | |
| end | |
| ---- | |
| -- Editor only code: | |
| local function UpdateItems() | |
| if composite_body_targets then | |
| return | |
| end | |
| composite_body_filters, composite_body_parts, composite_body_defs = {}, {}, {} | |
| ClassDescendantsList("CompositeBody", function(class, def) | |
| local target = def.composite_part_target or class | |
| local filters = composite_body_filters[target] or {} | |
| for _, prop in ipairs(def:GetProperties()) do | |
| if prop.body_part_filter then | |
| filters[prop.id] = filters[prop.id] or prop | |
| end | |
| end | |
| composite_body_filters[target] = filters | |
| local defs = composite_body_defs[target] or {} | |
| if not defs[class] then | |
| defs[class] = true | |
| table.insert(defs, def) | |
| end | |
| composite_body_defs[target] = defs | |
| local parts = composite_body_parts[target] or {} | |
| for _, part in ipairs(def.composite_part_names) do | |
| table.insert_unique(parts, part) | |
| end | |
| composite_body_parts[target] = parts | |
| end, "") | |
| composite_body_targets = table.keys2(composite_body_parts, true, "") | |
| end | |
| function GetBodyPartEntityItems() | |
| local items = {} | |
| for entity in pairs(GetAllEntities()) do | |
| local data = EntityData[entity] | |
| if data then | |
| items[#items + 1] = entity | |
| end | |
| end | |
| table.sort(items) | |
| table.insert(items, 1, "") | |
| return items | |
| end | |
| function GetBodyPartNameItems(preset) | |
| UpdateItems() | |
| return composite_body_parts[preset.Target] | |
| end | |
| function GetBodyPartNameCombo(preset) | |
| local items = table.copy(GetBodyPartNameItems(preset) or empty_table) | |
| table.insert(items, 1, "") | |
| return items | |
| end | |
| function GetBodyPartTargetItems(preset) | |
| UpdateItems() | |
| return composite_body_targets | |
| end | |
| function EntityStatesCombo(entity, ...) | |
| entity = entity or "" | |
| if entity == "" then | |
| return { ... } | |
| end | |
| local anims = GetStates(entity) | |
| table.sort(anims) | |
| table.insert(anims, 1, "") | |
| return anims | |
| end | |
| function EntityStateMomentsCombo(entity, anim, ...) | |
| entity = entity or "" | |
| anim = anim or "" | |
| if entity == "" or anim == "" then | |
| return { ... } | |
| end | |
| local moments = GetStateMomentsNames(entity, anim) | |
| table.insert(moments, 1, "") | |
| return moments | |
| end | |
| ---- | |
| DefineClass.CompositeBodyPreset = { | |
| __parents = { "Preset" }, | |
| properties = { | |
| { id = "Target", name = "Target", editor = "choice", default = "", items = GetBodyPartTargetItems }, | |
| { id = "Parts", name = "Covered Parts", editor = "set", default = false, items = GetBodyPartNameItems }, | |
| { id = "CustomMatch", name = "Custom Match", editor = "bool", default = false, }, | |
| { id = "BodiesFound", name = "Bodies Found", editor = "text", default = "", dont_save = true, read_only = 0, lines = 1, max_lines = 3, no_edit = PropChecker("CustomMatch", true) }, | |
| { id = "Parent", name = "Parent Part", editor = "choice", default = false, items = GetBodyPartNameItems }, | |
| { id = "Entity", name = "Entity", editor = "choice", default = "", items = GetBodyPartEntityItems }, | |
| { id = "PartClass", name = "Custom Class", editor = "text", default = false, translate = false, validate = function(self) return self.PartClass and not g_Classes[self.PartClass] and "Invalid class" end }, | |
| { id = "AttachSpot", name = "Attach Spot", editor = "text", default = "", translate = false, help = "Force attach spot" }, | |
| { id = "Scale", name = "Scale", editor = "range", default = def_scale }, | |
| { id = "Axis", name = "Axis", editor = "point", default = false, help = "Force a specific axis" }, | |
| { id = "Angle", name = "Angle", editor = "number", default = false, scale = "deg", min = -180*60, max = 180*60, slider = true, help = "Force a specific angle" }, | |
| { id = "Mirrored", name = "Mirrored", editor = "bool", default = false }, | |
| { id = "SyncState", name = "Sync State", editor = "choice", default = "auto", items = {true, false, "auto"}, help = "Force sync state" }, | |
| { id = "ZOrder", name = "ZOrder", editor = "number", default = 0, }, | |
| { id = "Weight", name = "Weight", editor = "number", default = 1000, min = 0, scale = 10 }, | |
| { id = "FxActor", name = "Fx Actor", editor = "combo", default = "", items = ActorFXClassCombo }, | |
| { id = "Filters", name = "Filters", editor = "nested_list", default = false, base_class = "CompositeBodyPresetFilter", inclusive = true }, | |
| { id = "ColorInherit", name = "Color Inherit", editor = "choice", default = "", items = GetBodyPartNameCombo }, | |
| { id = "Colors", name = "Colors", editor = "nested_list", default = false, base_class = "CompositeBodyPresetColor", inclusive = true, no_edit = function(self) return self.ColorInherit ~= "" end }, | |
| { id = "Lights", name = "Lights", editor = "nested_list", default = false, base_class = "CompositeBodyPresetLight", inclusive = true }, | |
| { id = "AffectedBy", name = "Affected by", editor = "choice", default = "", items = GetBodyPartNameCombo }, | |
| { id = "EntityWhenAffected", name = "Entity when affected", editor = "choice", default = "", items = GetBodyPartEntityItems, no_edit = function(o) return not o.AffectedBy end }, | |
| { id = "AttachOffset", name = "Attach Offset", editor = "point", default = point30, }, | |
| { id = "AttachAxis", name = "Attach Axis", editor = "point", default = axis_z, }, | |
| { id = "AttachAngle", name = "Attach Angle", editor = "number", default = 0, scale = "deg", min = -180*60, max = 180*60, slider = true }, | |
| { id = "ApplyAnim", name = "Apply Anim", editor = "choice", default = "", items = function(self) return EntityStatesCombo(self.AnimTestEntity, "") end }, | |
| { id = "UnapplyAnim", name = "Unapply Anim", editor = "choice", default = "", items = function(self) return EntityStatesCombo(self.AnimTestEntity, "") end }, | |
| { id = "ApplyAnimMoment", name = "Apply Anim Moment", editor = "choice", default = "hit", items = function(self) return EntityStateMomentsCombo(self.AnimTestEntity, self.ApplyAnim, "", "hit") end, }, | |
| { id = "AnimTestEntity", name = "Anim Test Entity", editor = "text", default = false }, | |
| }, | |
| GlobalMap = "CompositeBodyPresets", | |
| EditorMenubar = "Editors.Art", | |
| EditorMenubarName = "Composite Body Parts", | |
| EditorIcon = "CommonAssets/UI/Icons/atom molecule science.png", | |
| StoreAsTable = false, | |
| } | |
| CompositeBodyPreset.Documentation = [[The composite body system is a matching system for attaching parts to a body. | |
| A body collects its potential parts not from all part presets, but from a specified preset <style GedHighlight>Group</style>. The matched parts are those having the same <style GedHighlight>Target</style> property as the body target property. | |
| If no matching information is specified in the body, then its class name is used instead for all matching. | |
| Each part can contain filters for additional conditions during the matching process. | |
| Each part covers a specific named location on the body specified by <style GedHighlight>Covered Parts</style> property. If several parts are matched for the same location, a single one is chosen based on the <style GedHighlight>ZOrder</style> property. If there are still multiple parts with equal ZOrder, then a part is randomly selected based on the <style GedHighlight>Weight</style> property.]] | |
| function CompositeBodyPreset:GetError() | |
| if self.CustomMatch then | |
| return | |
| end | |
| local parts = self.Parts | |
| if not next(parts) then | |
| return "No covered parts specified!" | |
| end | |
| UpdateItems() | |
| local defs = composite_body_defs[self.Target] | |
| if not defs then | |
| return string.format("No composite bodies found with target '%s'", self.Target) | |
| end | |
| local group = self.group | |
| local count_group = 0 | |
| local count_part = 0 | |
| for _, def in ipairs(defs) do | |
| local composite_part_groups = def.composite_part_groups or { def.class } | |
| if table.find(composite_part_groups, group) then | |
| count_group = count_group + 1 | |
| for _, part_name in ipairs(def.composite_part_names) do | |
| if parts[part_name] then | |
| count_part = count_part + 1 | |
| break | |
| end | |
| end | |
| end | |
| end | |
| if count_group == 0 then | |
| return string.format("No composite bodies found with group '%s'", tostring(group)) | |
| end | |
| if count_part == 0 then | |
| return string.format("No composite bodies found with parts %s", table.concat(table.keys(parts, true))) | |
| end | |
| end | |
| function CompositeBodyPreset:GetBodiesFound() | |
| UpdateItems() | |
| local parts = self.Parts | |
| if not next(parts) then | |
| return 0 | |
| end | |
| local found = {} | |
| for _, def in ipairs(composite_body_defs[self.Target]) do | |
| local composite_part_groups = def.composite_part_groups or { def.class } | |
| if table.find(composite_part_groups, self.group) then | |
| for _, part_name in ipairs(def.composite_part_names) do | |
| if parts[part_name] then | |
| found[def.class] = true | |
| break | |
| end | |
| end | |
| end | |
| end | |
| return table.concat(table.keys(found, true), ", ") | |
| end | |
| function CompositeBodyPreset:OnEditorSetProperty(prop_id, old_value, ged) | |
| if prop_id == "Entity" then | |
| for _, obj in ipairs(self.Colors) do | |
| ObjModified(obj) -- properties for modifiable colors have changed | |
| end | |
| end | |
| end | |
| local function FindParentPreset(obj, member) | |
| return GetParentTableOfKind(obj, "CompositeBodyPreset") | |
| end | |
| function OnMsg.ClassesGenerate() | |
| DefineModItemPreset("CompositeBodyPreset", { | |
| EditorSubmenu = "Other", | |
| EditorName = "Composite body", | |
| EditorShortcut = false, | |
| }) | |
| end | |
| ---- | |
| local function GetBodyFilters(filter) | |
| UpdateItems() | |
| local parent = FindParentPreset(filter) | |
| local props = parent and composite_body_filters[parent.Target] | |
| if not props then | |
| return {} | |
| end | |
| local filters = {} | |
| for _, def in ipairs(composite_body_defs[parent.Target]) do | |
| for name, prop in pairs(props) do | |
| local items | |
| if prop.items then | |
| items = prop_eval(prop.items, def, prop) | |
| elseif prop.preset_class then | |
| local filter = prop.preset_filter | |
| items = {} | |
| ForEachPreset(prop.preset_class, function(preset, group, items) | |
| if not filter or filter(preset) then | |
| items[#items + 1] = preset.id | |
| end | |
| end, items) | |
| table.sort(items) | |
| end | |
| if items and #items > 0 then | |
| local prev_filters = filters[name] | |
| if not prev_filters then | |
| filters[name] = items | |
| else | |
| for _, value in ipairs(items) do | |
| table.insert_unique(prev_filters, value) | |
| end | |
| end | |
| end | |
| end | |
| end | |
| return filters | |
| end | |
| local function GetFilterNameItems(filter) | |
| local filters = GetBodyFilters(filter) | |
| local items = filters and table.keys(filters, true) | |
| if items[1] ~= "" then | |
| table.insert(items, 1, "") | |
| end | |
| return items | |
| end | |
| local function GetFilterValueItems(filter) | |
| local filters = GetBodyFilters(filter) | |
| return filters and filters[filter.Name] or {""} | |
| end | |
| DefineClass.CompositeBodyPresetFilter = { | |
| __parents = { "PropertyObject" }, | |
| properties = { | |
| { id = "Name", name = "Name", editor = "choice", default = "", items = GetFilterNameItems, }, | |
| { id = "Value", name = "Value", editor = "choice", default = "", items = GetFilterValueItems, }, | |
| { id = "Test", name = "Test", editor = "choice", default = "=", items = {"=", ">", "<"}, }, | |
| }, | |
| EditorView = Untranslated("<Name> <Test> <Value>"), | |
| } | |
| function CompositeBodyPresetFilter:Match(obj) | |
| local obj_value, value, test = obj[self.Name], self.Value, self.Test | |
| if test == '=' then | |
| return obj_value == value | |
| elseif test == '>' then | |
| return obj_value > value | |
| elseif test == '<' then | |
| return obj_value < value | |
| end | |
| end | |
| ---- | |
| DefineClass.CompositeBodyPresetColor = { | |
| __parents = { "ColorizationPropSet" }, | |
| properties = { | |
| { id = "Weight", name = "Weight", editor = "number", default = 1000, min = 0, scale = 10 }, | |
| }, | |
| } | |
| function CompositeBodyPresetColor:GetMaxColorizationMaterials() | |
| PopulateParentTableCache(self) | |
| if not ParentTableCache[self] then | |
| return ColorizationPropSet.GetMaxColorizationMaterials(self) | |
| end | |
| local parent = FindParentPreset(self) | |
| return parent and ColorizationMaterialsCount(parent.Entity) or 0 | |
| end | |
| function CompositeBodyPresetColor:GetError() | |
| if self:GetMaxColorizationMaterials() == 0 then | |
| local parent = FindParentPreset(self) | |
| if not parent or parent.Entity == "" then | |
| return "The composite body entity is not set." | |
| else | |
| return "There are no modifiable colors in the composite body entity." | |
| end | |
| end | |
| end | |
| ---- | |
| local light_props = {} | |
| function OnMsg.ClassesBuilt() | |
| local function RegisterProps(class, classdef) | |
| local props = {} | |
| for _, prop in ipairs(classdef:GetProperties()) do | |
| if prop.category == "Visuals" | |
| and not prop_eval(prop.no_edit, classdef, prop) | |
| and not prop_eval(prop.read_only, classdef, prop) then | |
| props[#props + 1] = prop | |
| props[prop.id] = classdef:GetDefaultPropertyValue(prop.id, prop) | |
| end | |
| end | |
| light_props[class] = props | |
| end | |
| RegisterProps("Light", Light) | |
| ClassDescendants("Light", RegisterProps) | |
| end | |
| function OnMsg.GatherFXActors(list) | |
| for _, preset in pairs(CompositeBodyPresets) do | |
| if (preset.FxActor or "") ~= "" then | |
| list[#list + 1] = preset.FxActor | |
| end | |
| end | |
| end | |
| function OnMsg.DataLoaded() | |
| PopulateParentTableCache(Presets.CompositeBodyPreset) | |
| end | |
| local function GetEntitySpotsItems(light) | |
| local parent = FindParentPreset(light) | |
| local entity = parent and parent.Entity or "" | |
| local states = IsValidEntity(entity) and GetStates(entity) or "" | |
| if #states == 0 then return empty_table end | |
| local idx = table.find(states, "idle") | |
| local spots = {} | |
| local spbeg, spend = GetAllSpots(entity, states[idx] or states[1]) | |
| for spot = spbeg, spend do | |
| spots[GetSpotName(entity, spot)] = true | |
| end | |
| return table.keys(spots, true) | |
| end | |
| DefineClass.CompositeBodyPresetLight = { | |
| __parents = { "PropertyObject" }, | |
| properties = { | |
| { id = "LightType", name = "Light Type", editor = "choice", default = "Light", items = ToCombo(light_props) }, | |
| { id = "LightSpot", name = "Light Spot", editor = "combo", default = "Origin", items = GetEntitySpotsItems }, | |
| { id = "LightSIEnable", name = "SI Apply", editor = "bool", default = true }, | |
| { id = "LightSIModulation", name = "SI Modulation", editor = "number", default = 255, min = 0, max = 255, slider = true, no_edit = function(self) return not self.LightSIEnable end }, | |
| { id = "night_mode", name = "Night mode", editor = "dropdownlist", items = { "Off", "On" }, default = "On" }, | |
| { id = "day_mode", name = "Day mode", editor = "dropdownlist", items = { "Off", "On" }, default = "Off" }, | |
| }, | |
| EditorView = Untranslated("<LightType>: <LightSpot>"), | |
| } | |
| function CompositeBodyPresetLight:GetError() | |
| if not light_props[self.LightType] then | |
| return "Invalid light type selected!" | |
| end | |
| end | |
| function CompositeBodyPresetLight:ApplyToLight(light) | |
| local props = light_props[self.LightType] or empty_table | |
| for _, prop in ipairs(props) do | |
| local prop_id = prop.id | |
| local prop_value = rawget(self, prop_id) | |
| if prop_value ~= nil then | |
| light:SetProperty(prop_id, prop_value) | |
| end | |
| end | |
| end | |
| function CompositeBodyPresetLight:GetProperties() | |
| local props = table.icopy(self.properties) | |
| table.iappend(props, light_props[self.LightType] or empty_table) | |
| return props | |
| end | |
| function CompositeBodyPresetLight:GetDefaultPropertyValue(prop_id, prop_meta) | |
| local def = table.get(light_props, self.LightType, prop_id) | |
| if def ~= nil then | |
| return def | |
| end | |
| return PropertyObject.GetDefaultPropertyValue(self, prop_id, prop_meta) | |
| end | |
| DefineClass.BaseLightObject = { | |
| __parents = { "Object" }, | |
| } | |
| function BaseLightObject:UpdateLight(lm, delayed) | |
| end | |
| function BaseLightObject:GameInit() | |
| Game:AddToLabel("Lights", self) | |
| end | |
| function BaseLightObject:Done() | |
| Game:RemoveFromLabel("Lights", self) | |
| end | |
| if FirstLoad then | |
| UpdateLightsThread = false | |
| end | |
| function OnMsg.DoneMap() | |
| UpdateLightsThread = false | |
| end | |
| function UpdateLights(lm, delayed) | |
| local lights = table.get(Game, "labels", "Lights") | |
| for _, obj in ipairs(lights) do | |
| obj:UpdateLight(lm, delayed) | |
| end | |
| end | |
| function UpdateLightsDelayed(lm, delayed_time) | |
| DeleteThread(UpdateLightsThread) | |
| UpdateLightsThread = false | |
| if delayed_time > 0 then | |
| UpdateLightsThread = CreateGameTimeThread(function(lm, delayed_time) | |
| Sleep(delayed_time) | |
| UpdateLights(lm, true) | |
| UpdateLightsThread = false | |
| end, lm, delayed_time) | |
| else | |
| UpdateLights(lm) | |
| end | |
| end | |
| function OnMsg.LightmodelChange(view, lm, time) | |
| UpdateLightsDelayed(lm, time/2) | |
| end | |
| function OnMsg.GatherAllLabels(labels) | |
| labels.Lights = true | |
| end | |
| DefineClass.CompositeLightObject = { | |
| __parents = { "CompositeBody", "BaseLightObject" }, | |
| light_parts = false, | |
| light_objs = false, | |
| } | |
| function CompositeLightObject:ComposeBodyParts(seed) | |
| self.light_parts = nil | |
| local changed = CompositeBody.ComposeBodyParts(self, seed) | |
| local light_parts = self.light_parts | |
| local light_objs = self.light_objs | |
| for i = #(light_objs or ""),1,-1 do | |
| local config = light_objs[i] | |
| local part = light_parts and light_parts[config] | |
| if not part then | |
| DoneObject(light_objs[config]) | |
| light_objs[config] = nil | |
| table.remove_value(light_objs, config) | |
| end | |
| end | |
| for _, config in ipairs(light_parts) do | |
| light_objs = light_objs or {} | |
| if light_objs[config] == nil then | |
| light_objs[config] = false | |
| light_objs[#light_objs + 1] = config | |
| end | |
| end | |
| self.light_objs = light_objs | |
| return changed | |
| end | |
| function CompositeLightObject:ApplyBodyPart(part, preset, ...) | |
| local light_parts = self.light_parts | |
| for _, config in ipairs(preset.Lights) do | |
| light_parts = light_parts or {} | |
| light_parts[config] = part | |
| light_parts[#light_parts + 1] = config | |
| end | |
| self.light_parts = light_parts | |
| return CompositeBody.ApplyBodyPart(self, part, preset, ...) | |
| end | |
| function CompositeLightObject:IsBodyPartLightOn(config) | |
| local mode = GameState.Night and config.night_mode or config.day_mode | |
| return mode == "On" | |
| end | |
| function CompositeLightObject:UpdateLight(delayed) | |
| local light_objs = self.light_objs or empty_table | |
| local IsBodyPartLightOn = self.IsBodyPartLightOn | |
| for _, config in ipairs(light_objs) do | |
| local light = light_objs[config] | |
| local part = self.light_parts[config] | |
| local turned_on = IsBodyPartLightOn(self, config) | |
| if turned_on and not light then | |
| light = PlaceObject(config.LightType) | |
| config:ApplyToLight(light) | |
| part:Attach(light, GetSpotBeginIndex(part, config.LightSpot)) | |
| light_objs[config] = light | |
| elseif not turned_on and light then | |
| DoneObject(light) | |
| light_objs[config] = false | |
| end | |
| if config.LightSIEnable then | |
| part:SetSIModulation(turned_on and config.LightSIModulation or 0) | |
| end | |
| end | |
| end | |
| ---- | |
| DefineClass.BlendedCompositeBody = { | |
| __parents = { "CompositeBody", "Object" }, | |
| composite_part_blend = false, | |
| blended_body_parts_params = false, | |
| blended_body_parts = false, | |
| } | |
| function BlendedCompositeBody:Init() | |
| self.blended_body_parts_params = { } | |
| self.blended_body_parts = { } | |
| end | |
| function BlendedCompositeBody:ForceComposeBlendedBodyParts() | |
| self.blended_body_parts_params = { } | |
| self.blended_body_parts = { } | |
| self:ComposeBodyParts() | |
| end | |
| function BlendedCompositeBody:ForceRevertBlendedBodyParts() | |
| if next(self.attached_parts) then | |
| local part_to_preset = {} | |
| self:CollectBodyParts(part_to_preset) | |
| for name,preset in sorted_pairs(part_to_preset) do | |
| local part = self.attached_parts[name] | |
| local entity = preset.Entity | |
| if IsValid(part) and IsValidEntity(entity) then | |
| Msg("RevertBlendedBodyPart", part) | |
| part:ChangeEntity(entity) | |
| end | |
| end | |
| end | |
| end | |
| function BlendedCompositeBody:UpdateBlendPartParams(params, part, preset, name, seed) | |
| return part:GetEntity() | |
| end | |
| function BlendedCompositeBody:ShouldBlendPart(params, part, preset, name, seed) | |
| return false | |
| end | |
| if FirstLoad then | |
| g_EntityBlendLocks = { } | |
| --g_EntityBlendLog = { } | |
| end | |
| local function BlendedEntityLocksGet(entity_name) | |
| return g_EntityBlendLocks[entity_name] or 0 | |
| end | |
| function BlendedEntityIsLocked(entity_name) | |
| --table.insert(g_EntityBlendLog, GameTime() .. " lock " .. entity_name) | |
| return BlendedEntityLocksGet(entity_name) > 0 | |
| end | |
| function BlendedEntityLock(entity_name) | |
| --table.insert(g_EntityBlendLog, GameTime() .. " unlock " .. entity_name) | |
| g_EntityBlendLocks[entity_name] = BlendedEntityLocksGet(entity_name) + 1 | |
| end | |
| function BlendedEntityUnlock(entity_name) | |
| local locks_count = BlendedEntityLocksGet(entity_name) | |
| assert(locks_count >= 1, "Unlocking a blended entity that isn't locked") | |
| if locks_count > 1 then | |
| g_EntityBlendLocks[entity_name] = locks_count - 1 | |
| else | |
| g_EntityBlendLocks[entity_name] = nil | |
| end | |
| end | |
| function WaitBlendEntityLocks(obj, entity_name) | |
| while BlendedEntityIsLocked(entity_name) do | |
| if obj and not IsValid(obj) then | |
| return false | |
| end | |
| WaitNextFrame(1) | |
| end | |
| return true | |
| end | |
| function BlendedCompositeBody:BlendEntity(t, e1, e2, e3, w1, w2, w3, m2, m3) | |
| --table.insert(g_EntityBlendLog, GameTime() .. " " .. self.class .. " blend " .. t) | |
| assert(BlendedEntityIsLocked(t), "To blend an entity you must lock it using BlendedEntityLock") | |
| assert(t ~= e1 and t ~= e2 and t ~= e3) | |
| SetMaterialBlendMaterials( | |
| GetEntityIdleMaterial(t), --target | |
| GetEntityIdleMaterial(e1), --base | |
| m2, GetEntityIdleMaterial(e2), --weight 1, material | |
| m3, GetEntityIdleMaterial(e3)) --weight 2, material | |
| WaitNextFrame(1) | |
| local err = AsyncOpWait(nil, nil, "AsyncMeshBlend", | |
| t, 0, --target, LOD | |
| e1, w1, --entity 1, weight | |
| e2, w2, --entity 2, weight | |
| e3, w3) --entity 3, weight | |
| if err then print("Failed to blend meshes: ", err) end | |
| end | |
| function BlendedCompositeBody:AsyncBlendEntity(obj, t, e1, e2, e3, w1, w2, w3, m2, m3, callback) | |
| return CreateRealTimeThread(function(self, obj, t, e1, e2, e3, w1, w2, w3, m2, m3, callback) | |
| WaitBlendEntityLocks(obj, t) | |
| BlendedEntityLock(t) | |
| self:BlendEntity(t, e1, e2, e3, w1, w2, w3, m2, m3) | |
| if callback then | |
| callback(self, obj, t, e1, e2, e3, w1, w2, w3, m2, m3) | |
| end | |
| BlendedEntityUnlock(t) | |
| end, self, obj, t, e1, e2, e3, w1, w2, w3, m2, m3, callback) | |
| end | |
| function BlendedCompositeBody:ApplyBlendBodyPart(blended_entity, part, preset, name, seed) | |
| return CompositeBody.ApplyBodyPart(self, preset, name, seed) | |
| end | |
| function BlendedCompositeBody:BlendBodyPartFailed(blended_entity, part, preset, name, seed) | |
| return CompositeBody.ApplyBodyPart(self, part, preset, name, seed) | |
| end | |
| -- if the body part is declared as "to be blended" | |
| function BlendedCompositeBody:IsBlendBodyPart(name) | |
| return self.composite_part_blend and self.composite_part_blend[name] | |
| end | |
| -- if the body part is using a blended entity or is being blended at the moment | |
| function BlendedCompositeBody:IsCurrentlyBlendedBodyPart(name) | |
| return self.blended_body_parts and self.blended_body_parts[name] | |
| end | |
| function BlendedCompositeBody:ColorizeBodyPart(part, preset, name, seed) | |
| if self:IsCurrentlyBlendedBodyPart(name) then | |
| return | |
| end | |
| return CompositeBody.ColorizeBodyPart(self, part, preset, name, seed) | |
| end | |
| function BlendedCompositeBody:ChangeBodyPartEntity(part, preset, name) | |
| if self:IsCurrentlyBlendedBodyPart(name) then | |
| return | |
| end | |
| return CompositeBody.ChangeBodyPartEntity(self, part, preset, name) | |
| end | |
| function BlendedCompositeBody:ApplyBodyPart(part, preset, name, seed) | |
| if self:IsBlendBodyPart(name) then | |
| self.blended_body_parts_params = self.blended_body_parts_params or { } | |
| local params = self.blended_body_parts_params[name] | |
| if not params or self:ShouldBlendPart(params, part, preset, name, seed) then | |
| params = params or { } | |
| local blended_entity = self:UpdateBlendPartParams(params, part, preset, name, seed) | |
| if IsValidEntity(blended_entity) then | |
| self.blended_body_parts_params[name] = params | |
| self.blended_body_parts[name] = (self.blended_body_parts[name] or 0) + 1 | |
| return self:ApplyBlendBodyPart(blended_entity, part, preset, name, seed) | |
| else | |
| self.blended_body_parts[name] = nil | |
| return self:BlendBodyPartFailed(blended_entity, part, preset, name, seed) | |
| end | |
| end | |
| end | |
| return CompositeBody.ApplyBodyPart(self, part, preset, name, seed) | |
| end | |
| function BlendedCompositeBody:RemoveBodyPart(part, name) | |
| if self:IsBlendBodyPart(name) and self.blended_body_parts_params then | |
| self.blended_body_parts_params[name] = nil | |
| end | |
| return CompositeBody.RemoveBodyPart(self, part, name) | |
| end | |
| function ForceRecomposeAllBlendedBodies() | |
| local objs = MapGet("map", "BlendedCompositeBody") | |
| for i,obj in ipairs(objs) do | |
| obj:ForceRevertBlendedBodyParts() | |
| end | |
| for i,obj in ipairs(objs) do | |
| obj:ForceComposeBlendedBodyParts() | |
| end | |
| end | |
| function OnMsg.PostLoadGame() | |
| ForceRecomposeAllBlendedBodies() | |
| end | |
| function OnMsg.AdditionalEntitiesLoaded() | |
| if type(__cobjectToCObject) ~= "table" then return end | |
| ForceRecomposeAllBlendedBodies() | |
| end | |
| local body_to_states | |
| function CompositeBodyAnims(classdef) | |
| local id = classdef.id or classdef.class | |
| body_to_states = body_to_states or {} | |
| local states = body_to_states[id] | |
| if not states then | |
| local entity = ResolveTemplateEntity(classdef) | |
| states = IsValidEntity(entity) and GetStates(entity) or empty_table | |
| table.sort(states) | |
| body_to_states[id] = states | |
| end | |
| return states | |
| end | |
| function SavegameFixups.BlendedBodyPartsList() | |
| MapForEach(true, "BlendedCompositeBody", function(obj) | |
| obj.blended_body_parts = {} | |
| end) | |
| end | |
| function SavegameFixups.BlendedBodyBlendIDs() | |
| MapForEach(true, "BlendedCompositeBody", function(obj) | |
| for name in pairs(obj.blended_body_parts) do | |
| obj.blended_body_parts[name] = 1 | |
| end | |
| end) | |
| end | |
| function SavegameFixups.FixSyncStateFlag2() | |
| MapForEach(true, "CompositeBody", "Building", function(obj) | |
| obj:ClearGameFlags(const.gofSyncState) | |
| obj:SetGameFlags(const.gofPropagateState) | |
| end) | |
| end | |