DefineClass.AppearanceObjectPart = { __parents = { "CObject", "ComponentAnim", "ComponentAttach", "ComponentCustomData" }, flags = { gofSyncState = true, cofComponentColorizationMaterial = true }, } function ValidAnimationsCombo(character) local all_anims = character:GetStatesTextTable() local valid_anims = {} for _, anim in ipairs(all_anims) do if anim:sub(-1, -1) ~= "*" then table.insert(valid_anims, anim) end end return valid_anims end DefineClass.AppearanceObject = { __parents = { "Shapeshifter", "StripComponentAttachProperties", "ComponentAnim"}, flags = { gofSyncState = true, cofComponentColorizationMaterial = true }, properties = { { category = "Animation", id = "Appearance", name = "Appearance", editor = "preset_id", preset_class = "AppearancePreset", default = "" }, { category = "Animation", id = "anim", name = "Animation", editor = "dropdownlist", items = ValidAnimationsCombo, default = "idle" }, { category = "Animation Blending", id = "animWeight", name = "Animation Weight", editor = "number", slider = true, min = 0, max = 100, default = 100, help = "100 means only Animation is played, 0 means only Animation 2 is played, 50 means both animations are blended equally" }, { category = "Animation Blending", id = "animBlendTime", name = "Animation Blend Time", editor = "number", min = 0, default = 0 }, { category = "Animation Blending", id = "anim2", name = "Animation 2", editor = "dropdownlist", items = function(character) local list = character:GetStatesTextTable() table.insert(list, 1, "") return list end, default = "" }, { category = "Animation Blending", id = "anim2BlendTime", name = "Animation 2 Blend Time", editor = "number", min = 0, default = 0 }, }, fallback_body = config.DefaultAppearanceBody, parts = false, animFlags = 0, animCrossfade = 0, anim2Flags = 0, anim2Crossfade = 0, attached_parts = { "Head", "Pants", "Shirt", "Armor", "Hat", "Hat2", "Hair", "Chest", "Hip" }, animated_parts = { "Head", "Pants", "Shirt", "Armor" }, appearance_applied = false, anim_speed = 1000, } function AppearanceObject:PostLoad() self:ApplyAppearance() end function AppearanceObject:OnEditorSetProperty(prop_id) if prop_id == "Appearance" then self:ApplyAppearance() end end function AppearanceObject:Setanim(anim) self.anim = anim self:SetAnimHighLevel() end function AppearanceObject:Setanim2(anim) self.anim2 = anim self:SetAnimHighLevel() end function AppearanceObject:SetanimFlags(anim_flags) self.animFlags = anim_flags end function AppearanceObject:SetanimCrossfade(crossfade) self.animCrossfade = crossfade end function AppearanceObject:Setanim2Flags(anim_flags) self.anim2Flags = anim_flags end function AppearanceObject:Setanim2Crossfade(crossfade) self.anim2Crossfade = crossfade end function AppearanceObject:SetanimWeight(weight) self.animWeight = weight self:SetAnimHighLevel() end function AppearanceObject:SetAnimChannel(channel, anim, anim_flags, crossfade, weight, blend_time) if not self:HasState(anim) then if self:GetEntity() ~= "" then StoreErrorSource(self, "Missing object state " .. self:GetEntity() .. "." .. anim) end return end Shapeshifter.SetAnim(self, channel, anim, anim_flags, crossfade) Shapeshifter.SetAnimWeight(self, channel, 100) Shapeshifter.SetAnimWeight(self, channel, weight, blend_time) self:SetAnimSpeed(channel, self.anim_speed) local parts = self.parts if parts then for _, part_name in ipairs(self.animated_parts) do local part = parts[part_name] if part then part:SetAnim(channel, anim, anim_flags, crossfade) part:SetAnimWeight(channel, 100) part:SetAnimWeight(channel, weight, blend_time) part:SetAnimSpeed(channel, self.anim_speed) end end end return GetAnimDuration(self:GetEntity(), self:GetAnim(channel)) end function AppearanceObject:SetAnimLowLevel() self:ApplyAppearance() local time = self:SetAnimChannel(1, self.anim, self.animFlags, self.animCrossfade, self.animWeight, self.animBlendTime) if self.anim2 ~= "" then local time2, duration2 = self:SetAnimChannel(2, self.anim2, self.anim2Flags, self.anim2Crossfade, 100 - self.animWeight, self.anim2BlendTime) time = Max(time, time2) end return time end function AppearanceObject:SetAnimHighLevel() self:SetAnimLowLevel() end function AppearanceObject:SetEntity() end local function get_part_offset_angle(appearance, prop_name) local x = appearance[prop_name .. "AttachOffsetX"] or 0 local y = appearance[prop_name .. "AttachOffsetY"] or 0 local z = appearance[prop_name .. "AttachOffsetZ"] or 0 local angle = appearance[prop_name .. "AttachOffsetAngle"] or 0 if x ~= 0 or y ~= 0 or z ~= 0 or angle ~= 0 then return point(x, y, z), angle end end -- overload this in project config.DefaultAppearanceBody = "ErrorAnimatedMesh" function AppearanceObject:ColorizePart(part_name) local appearance = AppearancePresets[self.Appearance] local prop_color_name = string.format("%sColor", part_name) if not appearance:HasMember(prop_color_name) then return end local part = self.parts[part_name] local color_member = appearance[prop_color_name] if not color_member then print("once", string.format("[WARNING] No color specified for %s in %s", part_name, self.Appearance)) return end local palette = color_member["ColorizationPalette"] part:SetColorizationPalette(palette) for i = 1, const.MaxColorizationMaterials do if string.match(part_name, "Hair") then local custom = {} for i = 1, 4 do custom[i] = appearance["HairParam" .. i] end part:SetHairCustomParams(custom) end local color = color_member[string.format("EditableColor%d", i)] local roughness = color_member[string.format("EditableRoughness%d", i)] local metallic = color_member[string.format("EditableMetallic%d", i)] part:SetColorizationMaterial(i, color, roughness, metallic) end end function AppearanceObject:ApplyPartSpotAttachments(part_name) local appearance = AppearancePresets[self.Appearance] local part = self.parts[part_name] local spot_prop = part_name .. "Spot" local prop_spot = appearance:HasMember(spot_prop) and appearance[spot_prop] local spot_name = prop_spot or "Origin" self:Attach(part, self:GetSpotBeginIndex(spot_name)) if part_name == "Hat" or part_name == "Hat2" then local offset, angle = get_part_offset_angle(appearance, part_name) if offset and angle then part:SetAttachOffset(offset) part:SetAttachAngle(angle) end end end function AppearanceObject:ApplyAppearance(appearance, force) appearance = appearance or self.Appearance if not appearance then return end if not force and self.appearance_applied == appearance then return end self.appearance_applied = appearance if type(appearance) == "string" then self.Appearance = appearance appearance = AppearancePresets[appearance] else self.Appearance = appearance.id end if not appearance then local prop_meta = AppearanceObject:GetPropertyMetadata("Appearance") appearance = AppearancePresets[prop_meta.default] if not appearance then StoreErrorSource(self, "Default Appearance can't be invalid!") end appearance = AppearancePresets[self.Appearance] if not appearance then StoreErrorSource(self, string.format("Invalid appearance '%s'", self.Appearance)) return end end for _, part in pairs(self.parts) do DoneObject(part) end self:ChangeEntity(appearance.Body or self.fallback_body or config.DefaultAppearanceBody) if not IsValidEntity(self:GetEntity()) then StoreErrorSource(self, string.format("Invalid entity '%s'(%s) for Appearance '%s'", self:GetEntity(), appearance.Body, appearance.id)) printf("Invalid entity '%s'(%s) for Appearance '%s'", self:GetEntity(), appearance.Body, appearance.id) end if appearance:HasMember("BodyColor") and appearance.BodyColor then self:SetColorization(appearance.BodyColor, true) end self.parts = {} local real_time_animated = self:GetGameFlags(const.gofRealTimeAnim) ~= 0 for _, part_name in ipairs(self.attached_parts) do if IsValidEntity(appearance[part_name]) then local part = PlaceObject("AppearanceObjectPart") if real_time_animated then part:SetGameFlags(const.gofRealTimeAnim) end part:ChangeEntity(appearance[part_name]) if not IsValidEntity(part:GetEntity()) then StoreErrorSource(part, string.format("Invalid entity part '%s'(%s) for Appearance '%s'", part:GetEntity(), appearance[part_name], appearance.id)) printf("Invalid entity part '%s'(%s) for Appearance '%s'", part:GetEntity(), appearance[part_name], appearance.id) end self.parts[part_name] = part self:ColorizePart(part_name) self:ApplyPartSpotAttachments(part_name) end end self:Setanim(self.anim) end function AppearanceObject:PlayAnim(anim) self:Setanim(anim) local vec = self:GetStepVector() local time = self:GetAnimDuration() if vec:Len() > 0 then self:SetPos(self:GetPos() + vec, time) end Sleep(time) end function AppearanceObject:SetPhaseHighLevel(phase) self:SetAnimPhase(1, phase) local parts = self.parts if parts then for _, part_name in ipairs(self.attached_parts) do local part = parts[part_name] if part then part:SetAnimPhase(1, phase) end end end end function AppearanceObject:SetAnimPose(anim, phase) self:Setanim(anim) self.anim_speed = 0 self:SetPhaseHighLevel(phase) end