Download CommonLua/Editor/AnimationMomentsEditor.lua from sirnii/myspace: direct link, hf CLI and curl.
- Browser
- Download file 34.5 kB
-
https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Editor/AnimationMomentsEditor.lua
- Command line
-
hf download hf://spaces/sirnii/myspace/CommonLua/Editor/AnimationMomentsEditor.lua
-
curl -L -o AnimationMomentsEditor.lua https://huggingface.co/spaces/sirnii/myspace/resolve/main/CommonLua/Editor/AnimationMomentsEditor.lua
34.5 kB
| local function WipeDeleted() | |
| local to_delete = {} | |
| ForEachPreset("AnimMetadata", function(preset) | |
| local entity, anim = preset.group, preset.id | |
| if not (IsValidEntity(entity) and HasState(entity, anim)) then | |
| to_delete[#to_delete + 1] = preset | |
| end | |
| end) | |
| for _, preset in ipairs(to_delete) do | |
| preset:delete() | |
| end | |
| end | |
| DefineClass.AnimMoment = { | |
| __parents = { "PropertyObject" }, | |
| properties = { | |
| { id = "Type", name = "Type", editor = "choice", default = "Moment", items = ActionMomentNamesCombo }, | |
| { id = "Time", name = "Time (ms)", editor = "number", default = 0 }, | |
| { id = "FX", name = "FX", editor = "choice", default = false, items = ActionFXClassCombo }, | |
| { id = "Actor", name = "Actor", editor = "choice", default = false, items = ActorFXClassCombo }, | |
| { id = "AnimRevision", name = "Animation Revision", editor = "number", default = 0, read_only = true }, | |
| { id = "Reconfirm", editor = "buttons", default = false, | |
| no_edit = function(obj) return not (obj:GetWarning() or obj:GetError()) end, | |
| buttons = { { name = "Reconfirm", func = function(self, root, prop_id, ged) | |
| self.AnimRevision = GetAnimationMomentsEditorObject().AnimRevision | |
| ObjModified(self) | |
| ObjModified(ged:ResolveObj("AnimationMetadata")) | |
| ObjModified(ged:ResolveObj("Animations")) | |
| end, | |
| } } }, | |
| }, | |
| } | |
| function AnimMoment:GetEditorView() | |
| if GetParentTableOfKind(self, "AnimMetadata").SpeedModifier ~= 100 then | |
| local character = GetAnimationMomentsEditorObject() | |
| return T{Untranslated("<Type> at <Time>ms(mod <ModTime>ms)"), | |
| Type = self.Type, Time = self.Time, ModTime = character:GetModifiedTime(self.Time)} | |
| else | |
| return Untranslated("<Type> at <Time>ms", self) | |
| end | |
| end | |
| function AnimMoment:GetError() | |
| local parent = GetParentTableOfKind(self, "AnimMetadata") | |
| -- local anim_revision = EntitySpec:GetAnimRevision(parent.group, parent.id) | |
| -- if anim_revision and self.AnimRevision < anim_revision thenbash | |
| -- return string.format("The animation changed in revision %d, but the moment was set back in revision %d.\nReadjust or click the button below to reconfirm.", anim_revision, self.AnimRevision) | |
| -- end | |
| if self.Time > GetAnimDuration(parent.group, parent.id) then | |
| return "Action moment's time is beyond the animation duration." | |
| end | |
| end | |
| function AnimMoment:OnAfterEditorNew() | |
| local parent = GetParentTableOfKind(self, "AnimMetadata") | |
| self.AnimRevision = EntitySpec:GetAnimRevision(parent.group, parent.id) | |
| end | |
| function GetAllAnimatedEntities(exclude) | |
| local entities = GetAllEntities() | |
| local animated_entities = {} | |
| for entity in pairs(entities) do | |
| if CObject.IsAnimated(entity) then | |
| table.insert(animated_entities, entity) | |
| end | |
| end | |
| table.remove_value(animated_entities, "ErrorAnimatedMesh") | |
| if exclude then | |
| animated_entities = table.subtraction(animated_entities, ClassLeafDescendantsList(exclude)) | |
| end | |
| table.sort(animated_entities) | |
| return animated_entities | |
| end | |
| function AllAppearancesComboItems() | |
| local list = PresetsCombo("AppearancePreset")() | |
| table.insert(list, 1, "Appearance Presets:") | |
| table.insert(list, 2, "------------------") | |
| table.insert(list, "") | |
| table.insert(list, "Animated Entities:") | |
| table.insert(list, "------------------") | |
| table.iappend(list, GetAllAnimatedEntities("CharacterEntity")) | |
| return list | |
| end | |
| MapVar("s_DelayedBindMomentsThread", false) | |
| function DelayedBindMoments(obj) | |
| DeleteThread(s_DelayedBindMomentsThread) | |
| s_DelayedBindMomentsThread = CreateMapRealTimeThread(function() | |
| Sleep(500) | |
| AnimationMomentsEditorBindObjects(obj) | |
| s_DelayedBindMomentsThread = false | |
| end) | |
| end | |
| -- this class is only for the Anim Metadata Editor purposes | |
| DefineClass.BaseObjectAME = { | |
| __parents = { "Object" }, | |
| properties = { | |
| { category = "Animation", id = "AnimRevision", name = "Animation Revision", editor = "number", default = 0, read_only = true }, | |
| { category = "Animation", id = "SpeedModifier", name = "Speed Modifier", editor = "number" , slider = true , min = 10, max = 1000, default = 100}, | |
| { category = "Animation", id = "StepModifier", name = "Step Modifier", editor = "number" , slider = true , min = 10, max = 1000, default = 100}, | |
| { category = "Animation", id = "StepDelta", name = "Step Delta", editor = "point" , default = point30, read_only = true}, | |
| { category = "Animation", id = "DisableCompensation", name = "Disable Compensation", editor = "bool" , default = false, dont_save = true}, | |
| { category = "Animation", id = "VariationWeight", name = "Variation Weight", editor = "number" , default = 100}, | |
| { category = "Animation", id = "button1", editor = "buttons" , default = false, dont_save = true, read_only = true, sort_order = 2, | |
| buttons = { | |
| { | |
| name = "Save", func = function(self, root, prop_id, ged) | |
| local preset = self:TransferToPreset() | |
| if preset then | |
| preset:Save("user request") | |
| end | |
| end, | |
| is_hidden = function(self, prop_meta) return self:GetAnimPreset() == empty_table end, | |
| }, { | |
| name = "New", func = function(self, root, prop_id, ged) | |
| if self:GetAnimPreset() ~= empty_table then | |
| return | |
| end | |
| WipeDeleted() | |
| local character = GetAnimationMomentsEditorObject() | |
| local _, _, preset = GetOrCreateAnimMetadata(character) | |
| preset:Save() | |
| AnimationMomentsEditorBindObjects(character) | |
| ged:SetSelection("Animations", PresetGetPath(preset)) | |
| end, | |
| is_hidden = function(self, prop_meta) return self:GetAnimPreset() ~= empty_table end, | |
| }, { | |
| name = "Delete", func = function(self, root, prop_id, ged) | |
| local character = GetAnimationMomentsEditorObject() | |
| local _, _, preset = GetOrCreateAnimMetadata(character) | |
| preset:delete() | |
| WipeDeleted() | |
| AnimationMomentsEditorBindObjects(character) | |
| ObjModified(Presets.AnimMetadata) | |
| end, | |
| is_hidden = function(self, prop_meta) return self:GetAnimPreset() == empty_table end, | |
| }, { | |
| name = "Reconfirm", func = function(self, root, prop_id, ged) | |
| SuspendObjModified("ReconfirmMoments") | |
| local character = GetAnimationMomentsEditorObject() | |
| local _, _, preset = GetOrCreateAnimMetadata(character) | |
| preset:ReconfirmMoments(root, prop_id, ged) | |
| ResumeObjModified("ReconfirmMoments") | |
| end, | |
| is_hidden = function(self, prop_meta) return self:GetAnimPreset() == empty_table end, | |
| }, { | |
| name = "Reconfirm All", func = function(self, root, prop_id, ged) | |
| SuspendObjModified("ReconfirmMoments") | |
| local character = GetAnimationMomentsEditorObject() | |
| local entity = character:GetInheritedEntity() | |
| for _, preset in ipairs(Presets.AnimMetadata[entity]) do | |
| local anim = preset.id | |
| local revision = EntitySpec:GetAnimRevision(entity, anim) | |
| for _, moment in ipairs(preset.Moments or empty_table) do | |
| if moment.AnimRevision ~= revision then | |
| moment.AnimRevision = revision | |
| ObjModified(moment) | |
| end | |
| end | |
| ObjModified(preset) | |
| ObjModified(ged:ResolveObj("Animations")) | |
| end | |
| ResumeObjModified("ReconfirmMoments") | |
| end | |
| }, { | |
| name = "Wipe Out Deleted", func = function(self, root, prop_id, ged) | |
| SuspendObjModified("ReconfirmMoments") | |
| WipeDeleted() | |
| AnimMetadata:SaveAll("save all", "user request") | |
| ResumeObjModified("ReconfirmMoments") | |
| end}, | |
| }, | |
| }, | |
| { category = "FX", id = "FXInherits", name = "FX Inherits", editor = "string_list" , | |
| default = empty_table, items = function(self) | |
| return ValidAnimationsCombo(self) | |
| end, | |
| }, | |
| }, | |
| Frame = 0, | |
| anim_thread = false, | |
| anim_duration = 0, | |
| loop_anim = true, | |
| preview_speed = 100, | |
| } | |
| function BaseObjectAME:Done() | |
| self:OnEditorClose() | |
| end | |
| function BaseObjectAME:OnEditorOpen(editor) | |
| end | |
| function BaseObjectAME:OnEditorClose() | |
| DeleteThread(self.anim_thread) | |
| --self:RevertPreviewSpeed(nil, "from preset") | |
| --AnimMetadataEditorTimelineSelectedControl = false | |
| end | |
| function BaseObjectAME:UpdateAnimRevision(anim) | |
| local anim_rev = EntitySpec:GetAnimRevision(self:GetEntity(), anim) | |
| if anim_rev then | |
| self:SetProperty("AnimRevision", anim_rev) | |
| end | |
| end | |
| function BaseObjectAME:Setanim(anim) | |
| local old_frame, old_duration = self.Frame, self.anim_duration | |
| local timeline = GetDialog("AnimMetadataEditorTimeline") | |
| if timeline then | |
| timeline:CreateMomentControls() | |
| end | |
| self:UpdateAnimRevision(anim) | |
| if self.anim_speed == 0 then | |
| if old_duration == 0 then | |
| self:SetFrame(old_frame) | |
| else | |
| self:SetFrame(MulDivTrunc(self.anim_duration, old_frame, old_duration)) | |
| end | |
| end | |
| AnimationMomentsEditorBindObjects(self) | |
| end | |
| function BaseObjectAME:GetInheritedEntity(anim) | |
| return GetAnimEntity(self:GetEntity(), GetStateIdx(anim or self:GetProperty("anim"))) | |
| end | |
| function BaseObjectAME:GetEntityAnimSpeed(anim) | |
| anim = anim or self:GetProperty("anim") | |
| local entity = self:GetInheritedEntity() | |
| local state_speed = entity and GetStateSpeedModifier(entity, GetStateIdx(anim)) or const.AnimSpeedScale | |
| return state_speed | |
| end | |
| function BaseObjectAME:GetModifiedTime(absolute_time) | |
| return MulDivTrunc(absolute_time, const.AnimSpeedScale, self:GetEntityAnimSpeed()) | |
| end | |
| function BaseObjectAME:GetAbsoluteTime(modified_time) | |
| return MulDivTrunc(modified_time, self:GetEntityAnimSpeed(), const.AnimSpeedScale) | |
| end | |
| function BaseObjectAME:SetFrame(frame, delayed_moments_binding) | |
| self.Frame = frame | |
| self.anim_speed = 0 | |
| self:SetAnimHighLevel() | |
| UpdateTimeline() | |
| if delayed_moments_binding then | |
| DelayedBindMoments(self) | |
| end | |
| end | |
| function BaseObjectAME:GetFrame() | |
| if self.anim_speed == 0 then | |
| return self.Frame | |
| else | |
| return self.anim_duration - self:TimeToAnimEnd() | |
| end | |
| end | |
| function BaseObjectAME:SetAnimLowLevel(resume) | |
| local anim = self:GetProperty("anim") | |
| local time, duration = self:SetAnimChannel(1, anim, self.animFlags, self.animCrossfade, self.animWeight, self.animBlendTime, resume) | |
| if self.anim2 ~= "" then | |
| local time2, duration2 = self:SetAnimChannel(2, self.anim2, self.anim2Flags, self.anim2Crossfade, 100 - self.animWeight, self.anim2BlendTime, resume) | |
| time = Max(time, time2) | |
| duration = Max(duration, duration2) | |
| end | |
| return time, duration | |
| end | |
| function BaseObjectAME:AnimAdjustPos() | |
| end | |
| function BaseObjectAME:SetAnimHighLevel(resume) | |
| local time, duration = self:SetAnimLowLevel(resume) | |
| time = Max(time, 1) | |
| self.anim_duration = duration | |
| UpdateTimelineDuration(duration) | |
| local dlg = GetDialog("AnimMetadataEditorTimeline") | |
| if dlg then | |
| dlg.idAnimationName:SetText(self.anim) | |
| end | |
| if IsValidThread(self.anim_thread) then | |
| DeleteThread(self.anim_thread) | |
| self.anim_thread = nil | |
| end | |
| self.anim_thread = CreateRealTimeThread(function() | |
| local metadata = self:GetAnimPreset() | |
| while IsValid(self) and IsValidEntity(self:GetEntity()) do | |
| self:AnimAdjustPos(time) | |
| local dt, last_moment_time = 0, 0 | |
| local anim_obj = rawget(self, "obj") or self | |
| local moment, time_to_moment = anim_obj:TimeToNextMoment(1, 1) | |
| while IsValid(self) and dt < time do | |
| Sleep(1) | |
| UpdateTimeline() | |
| dt = dt + 1 | |
| if time_to_moment and self:GetAnimPhase(1) > last_moment_time + time_to_moment then | |
| local action, actor, target = GetProperty(metadata, "Action"), GetProperty(metadata, "Actor"), GetProperty(metadata, "Target") | |
| anim_obj.fx_actor_class = actor | |
| PlayFX(action or FXAnimToAction(metadata.id), moment, anim_obj, target) | |
| moment, time_to_moment = anim_obj:TimeToNextMoment(1, 1) | |
| last_moment_time = self:GetAnimPhase(1) | |
| end | |
| end | |
| if not IsValid(self) then return end | |
| if not self.loop_anim then | |
| -- if not looped anim - freeze at the last frame | |
| if self.anim_speed > 0 then | |
| self.Frame = self.anim_duration - 1 | |
| self.anim_speed = 0 | |
| self:SetAnimLowLevel() | |
| end | |
| while IsValid(self) and not self.loop_anim do | |
| Sleep(20) | |
| end | |
| if not IsValid(self) then return end | |
| end | |
| time, self.anim_duration = self:SetAnimLowLevel() | |
| time = Max(time, 1) | |
| UpdateTimelineDuration(self.anim_duration) | |
| end | |
| end) | |
| end | |
| function BaseObjectAME:GetAnimPreset() | |
| local anim = self:GetProperty("anim") | |
| local entity = self:GetInheritedEntity(anim) | |
| local preset_group = Presets.AnimMetadata[entity] or empty_table | |
| return preset_group[anim] or empty_table | |
| end | |
| function BaseObjectAME:GetAnimMoments() | |
| local preset_anim = self:GetAnimPreset() | |
| return preset_anim.Moments or empty_table | |
| end | |
| function BaseObjectAME:UpdateAnimMetadataSelection() | |
| local anim = self:GetProperty("anim") | |
| local inherited_entity = self:GetInheritedEntity(anim) | |
| local group = Presets.AnimMetadata or {} | |
| local group_idx = table.find(group, group[inherited_entity]) | |
| local item_idx = group_idx and table.find(group[group_idx], "id", anim) | |
| if group_idx and item_idx then | |
| AnimationMomentsEditor:SetSelection("Animations", {group_idx, item_idx}) | |
| end | |
| end | |
| function BaseObjectAME:SetPreviewSpeed(speed) | |
| self.preview_speed = speed | |
| local anim = self:GetProperty("anim") | |
| local modifier = MulDivTrunc(self.SpeedModifier * self.preview_speed, const.AnimSpeedScale, 100 * 100) | |
| SetStateSpeedModifier(self:GetEntity(), GetStateIdx(anim), modifier) | |
| self:SetAnimHighLevel() | |
| end | |
| function BaseObjectAME:RevertPreviewSpeed(anim, from_preset) | |
| anim = anim or self:GetProperty("anim") | |
| local entity = self:GetInheritedEntity() | |
| local old_anim_speed_modifier | |
| if from_preset then | |
| local preset = self:GetAnimPreset() | |
| old_anim_speed_modifier = MulDivTrunc(1000, preset.SpeedModifier or 100, 100) | |
| else | |
| old_anim_speed_modifier = GetStateSpeedModifier(entity, GetStateIdx(anim)) | |
| old_anim_speed_modifier = MulDivTrunc(old_anim_speed_modifier, 100, self.preview_speed) | |
| end | |
| SetStateSpeedModifier(entity, GetStateIdx(anim), old_anim_speed_modifier) | |
| end | |
| function BaseObjectAME:ApplyPreviewSpeed(anim) | |
| local anim1 = self:GetProperty("anim") | |
| anim = anim or anim1 | |
| local entity = self:GetInheritedEntity() | |
| local state_speed = GetStateSpeedModifier(entity, GetStateIdx(anim)) | |
| self.SpeedModifier = MulDivTrunc(state_speed, 100, const.AnimSpeedScale) | |
| self.StepModifier = GetStateStepModifier(entity, GetStateIdx(anim1)) | |
| self:SetPreviewSpeed(self.preview_speed) | |
| end | |
| function BaseObjectAME:GetStepCompensation() | |
| return self.DisableCompensation and point30 or self:GetStepVector() | |
| end | |
| function BaseObjectAME:OnAnimChanged(anim, old_anim) | |
| self:RevertPreviewSpeed(old_anim) | |
| self:ApplyPreviewSpeed(anim) | |
| self:SetProperty("StepDelta", self:GetStepVector()) | |
| local preset = self:GetAnimPreset() | |
| self:SetProperty("FXInherits", preset.FXInherits) | |
| end | |
| function BaseObjectAME:ChangeAnim(anim, old_anim) | |
| if self.anim_speed == 0 then | |
| local old_duration = Max(GetAnimDuration(self:GetEntity(), old_anim), 1) | |
| if not self.loop_anim and self.Frame == old_duration - 1 then | |
| self.anim_speed = 1000 | |
| self:Setanim(anim) | |
| else | |
| self:SetFrame(self.anim_duration * self.Frame / old_duration) | |
| end | |
| end | |
| self:OnAnimChanged(anim, old_anim) | |
| self:UpdateAnimMetadataSelection() | |
| end | |
| function BaseObjectAME:TransferToPreset() | |
| local preset = self:GetAnimPreset() | |
| if preset == empty_table then | |
| if not IsRealTimeThread() then | |
| CreateRealTimeThread(function() | |
| WaitMessage(terminal.desktop, | |
| T(313839116468, "No Anim Metata"), | |
| T(857146618172, "Use 'New Animation Metadata' button first"), | |
| T(1000136, "OK") | |
| ) | |
| end) | |
| end | |
| return | |
| end | |
| WipeDeleted() | |
| local character = GetAnimationMomentsEditorObject() | |
| preset.SpeedModifier = character.SpeedModifier | |
| preset.StepModifier = character.StepModifier | |
| preset.VariationWeight = character.VariationWeight | |
| preset.FXInherits = character.FXInherits | |
| ObjModified(preset) | |
| return preset | |
| end | |
| function BaseObjectAME:OnEditorSetProperty(prop_id, old_value, ged) | |
| if prop_id == "anim" then | |
| local anim = self:GetProperty("anim") | |
| self:ChangeAnim(anim, old_value) | |
| LocalStorage.AME_LastAnim = anim | |
| SaveLocalStorage() | |
| elseif prop_id == "Appearance" then | |
| LocalStorage.AME_LastAppearance = self:GetProperty("Appearance") | |
| SaveLocalStorage() | |
| elseif prop_id == "SpeedModifier" then | |
| self:TransferToPreset() | |
| self:SetPreviewSpeed(self.preview_speed) | |
| elseif prop_id == "StepModifier" then | |
| self:TransferToPreset() | |
| SetStateStepModifier(self:GetEntity(), GetStateIdx(self:GetProperty("anim")), self.StepModifier) | |
| elseif prop_id == "Time" or prop_id == "Type" or prop_id == "VariationWeight" then | |
| local character = GetAnimationMomentsEditorObject() | |
| self.AnimRevision = character.AnimRevision | |
| AnimationMomentsEditorBindObjects(character) | |
| end | |
| end | |
| function FormatTimeline(frame, precision) | |
| local character = GetAnimationMomentsEditorObject() | |
| local absolute_frame = MulDivTrunc(frame, character.preview_speed, 100) | |
| precision = precision or 1 | |
| if precision == 1 then | |
| return string.format("%.1fs", absolute_frame / 1000.0) | |
| elseif precision == 2 then | |
| return string.format("%.2fs", absolute_frame / 1000.0) | |
| else | |
| return string.format("%.3fs", absolute_frame / 1000.0) | |
| end | |
| end | |
| function UpdateTimeline() | |
| local timeline = GetDialog("AnimMetadataEditorTimeline") | |
| if timeline then | |
| timeline:Invalidate() | |
| end | |
| end | |
| function UpdateTimelineDuration(time) | |
| local timeline = GetDialog("AnimMetadataEditorTimeline") | |
| if timeline then | |
| timeline.idDuration:SetText(FormatTimeline(time, 3)) | |
| end | |
| end | |
| ---- | |
| -- this class is only for the Anim Metadata Editor purposes | |
| DefineClass.AppearanceObjectAME = { | |
| __parents = { "AppearanceObject", "StripObjectProperties", "BaseObjectAME" }, | |
| flags = { gofRealTimeAnim = true, }, | |
| properties = { | |
| { category = "Animation", id = "Appearance", name = "Entity/Appearance", editor = "dropdownlist", | |
| items = AllAppearancesComboItems, default = GetAllAnimatedEntities()[1], | |
| buttons = {{name = "Edit", func = function(self, root, prop_id, ged) | |
| local appearance = self.Appearance | |
| local preset = AppearancePresets[appearance] or EntitySpecPresets[appearance] | |
| if preset then | |
| preset:OpenEditor() | |
| end | |
| end}}, | |
| }, | |
| { id = "DetailClass" }, | |
| }, | |
| init_pos = false, | |
| } | |
| function AppearanceObjectAME:Setanim(anim) | |
| if anim:sub(-1, -1) == "*" then return end | |
| anim = IsValidAnim(self, anim) and anim or "idle" | |
| AppearanceObject.Setanim(self, anim) | |
| BaseObjectAME.Setanim(self, anim) | |
| end | |
| function AppearanceObjectAME:SetAnimHighLevel(...) | |
| return BaseObjectAME.SetAnimHighLevel(self, ...) | |
| end | |
| function AppearanceObjectAME:SetAnimLowLevel(...) | |
| return BaseObjectAME.SetAnimLowLevel(self, ...) | |
| end | |
| function AppearanceObjectAME:GetAnimMoments(...) | |
| return BaseObjectAME.GetAnimMoments(self, ...) | |
| end | |
| function AppearanceObjectAME:ApplyAppearance(appearance) | |
| appearance = appearance or LocalStorage.AME_LastAppearance or self.Appearance | |
| local preset_appearance = AppearancePresets[appearance] | |
| if preset_appearance then | |
| local copy = preset_appearance:Clone("Appearance") | |
| copy.id = preset_appearance.id | |
| AppearanceObject.ApplyAppearance(self, copy) | |
| else | |
| appearance = IsValidEntity(appearance) and appearance or GetAllAnimatedEntities()[1] | |
| local entity_appearance = Appearance:new{id = appearance, Body = appearance} | |
| AppearanceObject.ApplyAppearance(self, entity_appearance) | |
| end | |
| end | |
| function AppearanceObjectAME:AnimAdjustPos(time) | |
| if self.anim_speed > 0 then | |
| local step = self:GetStepCompensation() | |
| self:SetPos(self.init_pos) | |
| local pos = terrain.ClampPoint(self.init_pos + step) | |
| self:SetPos(pos, time) | |
| else | |
| local frame_step = self:GetStepVector(self:GetAnim(), self:GetAngle(), 0, self:GetAbsoluteTime(self.Frame)) | |
| self:SetPos(self.init_pos + frame_step) | |
| end | |
| end | |
| function AppearanceObjectAME:SetAnimChannel(channel, anim, anim_flags, crossfade, weight, blend_time, resume) | |
| AppearanceObject.SetAnimChannel(self, channel, anim, anim_flags, crossfade, weight, blend_time) | |
| local frame = self.Frame | |
| if resume or self.anim_speed == 0 then | |
| self:SetAnimPhase(channel, frame) | |
| for _, part_name in ipairs(self.animated_parts) do | |
| local part = self.parts[part_name] | |
| if part then | |
| part:SetAnimPhase(channel, frame) | |
| end | |
| end | |
| end | |
| local duration = GetAnimDuration(self:GetEntity(), self:GetAnim(channel)) | |
| return duration - (resume and frame or 0), duration | |
| end | |
| function AppearanceObjectAME:GetSize() | |
| local bbox = self:GetEntityBBox() | |
| if self.parts then | |
| for _, part_name in ipairs(self.attached_parts) do | |
| local part = self.parts[part_name] | |
| if part then | |
| local part_bbox = part:GetEntityBBox() | |
| bbox = Extend(bbox, part_bbox:min()) | |
| bbox = Extend(bbox, part_bbox:max()) | |
| end | |
| end | |
| end | |
| return bbox | |
| end | |
| function AppearanceObjectAME:OnEditorSetProperty(prop_id, old_value, ged) | |
| BaseObjectAME.OnEditorSetProperty(self, prop_id, old_value, ged) | |
| if prop_id == "Appearance" then | |
| self:RevertPreviewSpeed() | |
| self:ApplyAppearance() | |
| self:ApplyPreviewSpeed() | |
| else | |
| AppearanceObject.OnEditorSetProperty(self, prop_id, old_value, ged) | |
| end | |
| end | |
| function AppearanceObjectAME:OnAnimMetadataSelect(obj) | |
| local entity, anim = obj.group, obj.id | |
| if obj.group ~= self:GetInheritedEntity(anim) then | |
| self:ApplyAppearance(entity) | |
| end | |
| local old_anim = self:GetProperty("anim") | |
| self:SetProperty("anim", anim) | |
| self:OnAnimChanged(anim, old_anim) | |
| end | |
| if FirstLoad then | |
| AnimationMetadataEditorsStoredCamera = false | |
| end | |
| function AppearanceObjectAME:OnEditorOpen(editor) | |
| AnimationMetadataEditorsStoredCamera = {GetCamera()} | |
| BaseObjectAME.OnEditorOpen(self, editor) | |
| GedOpCharacterCamThreeQuarters(editor, self) | |
| local last_anim = LocalStorage.AME_LastAnim | |
| self:SetProperty("anim", last_anim and IsValidAnim(self, last_anim) and last_anim or "idle") | |
| end | |
| function AppearanceObjectAME:OnEditorClose() | |
| BaseObjectAME.OnEditorClose(self) | |
| SetCamera(unpack_params(AnimationMetadataEditorsStoredCamera)) | |
| end | |
| function GetOrCreateAnimMetadata(obj) | |
| local entity = obj:GetInheritedEntity() | |
| local anim = obj:GetProperty("anim") | |
| local group = Presets.AnimMetadata[entity] | |
| local preset = group and group[anim] | |
| if not preset then | |
| preset = AnimMetadata:new{ group = entity, id = anim } | |
| preset:OnEditorNew(Presets.AnimMetadata, AnimationMomentsEditor) | |
| end | |
| return entity, anim, preset, group | |
| end | |
| ---- | |
| DefineClass.SelectionObjectAME = { | |
| __parents = { "BaseObjectAME", "StripObjectProperties" }, | |
| properties = { | |
| { category = "Animation", id = "InheritedEntity", name = "Anim Entity", editor = "text", default = "", read_only = true }, | |
| { category = "Animation", id = "anim", name = "Animation", editor = "dropdownlist", items = ValidAnimationsCombo, default = "idle" }, | |
| { category = "Animation", 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", id = "animBlendTime", name = "Animation Blend Time", editor = "number", min = 0, default = 0 }, | |
| { category = "Animation", id = "anim2", name = "Animation 2", editor = "dropdownlist", items = function(character) local list = character:GetStatesTextTable() table.insert(list, 1, "") return list end, default = "" }, | |
| { category = "Animation", id = "anim2BlendTime", name = "Animation 2 Blend Time", editor = "number", min = 0, default = 0 }, | |
| { category = "Animation", id = "AnimDuration", name = "Anim Duration", editor = "number", default = 0, read_only = true }, | |
| { category = "Animation", id = "StepVector", name = "Step Vector", editor = "point", default = point30, read_only = true }, | |
| { category = "Animation", id = "StepAngle", name = "Step Angle (deg)", editor = "number", default = 0, scale = 60, read_only = true }, | |
| { category = "Animation", id = "Looping", name = "Looping", editor = "bool", default = false, read_only = true }, | |
| { category = "Animation", id = "Compensate", name = "Compensate", editor = "text", default = "None", read_only = true }, | |
| }, | |
| obj = false, | |
| animFlags = 0, | |
| animCrossfade = 0, | |
| anim2Flags = 0, | |
| anim2Crossfade = 0, | |
| anim_speed = 1000, | |
| } | |
| function SelectionObjectAME:Getanim() | |
| return IsValid(self.obj) and self.obj:GetStateText() or "" | |
| end | |
| function SelectionObjectAME:GetStepVector(...) | |
| return IsValid(self.obj) and self.obj:GetStepVector(...) or point30 | |
| end | |
| function SelectionObjectAME:GetStepAngle(...) | |
| return IsValid(self.obj) and self.obj:GetStepAngle(...) or 0 | |
| end | |
| function SelectionObjectAME:GetLooping() | |
| return IsValid(self.obj) and self.obj:IsAnimLooping() or false | |
| end | |
| function SelectionObjectAME:GetCompensate() | |
| return IsValid(self.obj) and self.obj:GetAnimCompensate() or "None" | |
| end | |
| function SelectionObjectAME:GetAnimDuration() | |
| return IsValid(self.obj) and self.obj:GetAnimDuration() or point30 | |
| end | |
| function SelectionObjectAME:Setanim(anim) | |
| if IsValid(self.obj) then | |
| self.obj:SetStateText(anim) | |
| BaseObjectAME.Setanim(self, anim) | |
| end | |
| end | |
| function SelectionObjectAME:GetEntity() | |
| return IsValid(self.obj) and self.obj:GetEntity() or "" | |
| end | |
| function SelectionObjectAME:OnEditorOpen(editor) | |
| BaseObjectAME.OnEditorOpen(self, editor) | |
| self:UpdateSelectedObj() | |
| end | |
| function SelectionObjectAME:SetSelectedObj(obj) | |
| local prev_obj = self.obj | |
| if IsValid(prev_obj) then | |
| prev_obj:ClearGameFlags(const.gofRealTimeAnim) | |
| self:Detach() | |
| end | |
| self.obj = obj | |
| if obj then | |
| obj:Attach(self) | |
| obj:SetGameFlags(const.gofRealTimeAnim) | |
| end | |
| end | |
| function SelectionObjectAME:UpdateSelectedObj() | |
| local obj = self.obj | |
| if not IsValid(obj) then | |
| return | |
| end | |
| local anim = obj:GetStateText() | |
| BaseObjectAME.Setanim(self, anim) | |
| self.Frame = obj:GetAnimPhase(1) | |
| self:ApplyPreviewSpeed(anim) | |
| end | |
| function SelectionObjectAME:OnEditorClose() | |
| BaseObjectAME.OnEditorClose(self) | |
| self.obj.fx_actor_class = nil | |
| self:SetSelectedObj(false) | |
| end | |
| function SelectionObjectAME:SetAnimChannel(channel, anim, anim_flags, crossfade, weight, blend_time, resume) | |
| local obj = self.obj | |
| if not IsValid(obj) then | |
| return 0, 0 | |
| end | |
| obj:SetAnim(channel, anim, anim_flags, crossfade) | |
| obj:SetAnimWeight(channel, 100) | |
| obj:SetAnimWeight(channel, weight, blend_time) | |
| obj:SetAnimSpeed(channel, self.anim_speed) | |
| local frame = self.Frame | |
| if resume or self.anim_speed == 0 then | |
| obj:SetAnimPhase(channel, frame) | |
| end | |
| local duration = GetAnimDuration(obj:GetEntity(), obj:GetAnim(channel)) | |
| return duration - (resume and frame or 0), duration | |
| end | |
| function SelectionObjectAME:GetSize() | |
| return IsValid(self.obj) and ObjectHierarchyBBox(self.obj) or box() | |
| end | |
| function SelectionObjectAME:GetAnimPhase(...) | |
| return IsValid(self.obj) and self.obj:GetAnimPhase(...) or 0 | |
| end | |
| function SelectionObjectAME:GetStatesTextTable(...) | |
| return IsValid(self.obj) and self.obj:GetStatesTextTable(...) or {} | |
| end | |
| function SelectionObjectAME:TimeToAnimEnd(...) | |
| return IsValid(self.obj) and self.obj:TimeToAnimEnd(...) or 0 | |
| end | |
| function SelectionObjectAME:OnAnimMetadataSelect(anim_meta) | |
| local obj = self.obj | |
| if not IsValid(obj) then | |
| return | |
| end | |
| local anim = anim_meta.id | |
| if not self.obj:HasState(anim) then | |
| return | |
| end | |
| local old_anim = self:GetProperty("anim") | |
| self:SetProperty("anim", anim) | |
| self:OnAnimChanged(anim, old_anim) | |
| end | |
| ---- | |
| function GedOpCharacterCamThreeQuarters(socket, character) | |
| CreateRealTimeThread(function() | |
| local old_update_inactive = hr.MaxCameraUpdateInactive | |
| hr.MaxCameraUpdateInactive = true | |
| local center, radius = character:GetBSphere() | |
| cameraMax.Activate(1) | |
| cameraMax.SetCamera(center + character:GetFaceDir(radius), center) | |
| local size = character:GetSize() | |
| local height = size:sizez() | |
| local desired_height = UIL.GetScreenSize():y() * 3 / 4 | |
| local lo, hi = 20, 100 | |
| while hi - lo > 1 do | |
| local scale = (lo + hi) / 2 | |
| cameraMax.SetCameraViewAt(center, radius * scale / 10) | |
| WaitNextFrame() | |
| local _, feet_height = GameToScreen(character:GetPos()) | |
| local _, head_height = GameToScreen(character:GetPos() + point(0, 0, height)) | |
| local screen_height = feet_height:y() - head_height:y() | |
| if screen_height < desired_height then | |
| hi = scale | |
| else | |
| lo = scale | |
| end | |
| end | |
| hr.MaxCameraUpdateInactive = old_update_inactive | |
| end) | |
| end | |
| function GedOpCharacterCamClosest(socket, character) | |
| local center, radius = character:GetBSphere() | |
| cameraMax.Activate(1) | |
| cameraMax.SetCamera(center + character:GetFaceDir(radius), center) | |
| cameraTac.Activate(1) | |
| cameraTac.SetCamera(center - character:GetFaceDir(radius), center) | |
| cameraTac.Normalize() | |
| cameraTac.SetLookAtAngle(hr.CameraTacLookAtAngle) | |
| cameraTac.SetFloor(0) | |
| end | |
| function GedOpAnimMetadataEditorPlay(socket, character) | |
| character.anim_speed = 1000 | |
| if not character.loop_anim and character.Frame == character.anim_duration - 1 then | |
| character.Frame = 0 | |
| end | |
| character:SetAnimHighLevel("resume") | |
| local control = GetDialog("AnimMetadataEditorTimeline"):ResolveId("idMoment-NewMoment") | |
| if control then | |
| control:delete() | |
| end | |
| end | |
| function GedOpAnimMetadataEditorStop(socket, character) | |
| character.Frame = character:GetAnimPhase(1) | |
| character.anim_speed = 0 | |
| character:SetAnimHighLevel() | |
| GetDialog("AnimMetadataEditorTimeline"):CreateNewMomentControl() | |
| end | |
| function GedOpAnimationMomentsEditorToggleLoop(socket, character) | |
| character.loop_anim = not character.loop_anim | |
| if character.loop_anim and character.Frame == character.anim_duration - 1 then | |
| character.anim_speed = 1000 | |
| character:Setanim(character.anim or character:Getanim()) | |
| end | |
| end | |
| function GedOpAnimationMomentsEditorToggleSpeed(socket, speed) | |
| local character = GetAnimationMomentsEditorObject() | |
| character:SetPreviewSpeed(speed) | |
| GedObjectModified(character) | |
| end | |
| function GedOpOpenAppearanceEditor(socket, character) | |
| OpenAppearanceEditor(character.Appearance) | |
| end | |
| function GedOpSaveAnimMetadata() | |
| WipeDeleted() | |
| AnimMetadata:SaveAll("save all", "user request") | |
| end | |
| if FirstLoad then | |
| AnimationMomentsEditor = false | |
| AnimationMomentsEditorMode = false | |
| AnimMetadataEditorTimelineDragging = false -- indicates dragging over the timeline bar | |
| AnimMetadataEditorTimelineSelectedControl = false -- selected/dragged moment control | |
| end | |
| function GetAnimationMomentsEditorObject() | |
| return AnimationMomentsEditor and AnimationMomentsEditor.bound_objects["root"] | |
| end | |
| function AnimationMomentsEditorBindObjects(character) | |
| if not AnimationMomentsEditor then return end | |
| local anim = character:GetProperty("anim") | |
| local entity = character:GetInheritedEntity(anim) | |
| AnimationMomentsEditor:BindObj("Animations", Presets.AnimMetadata) | |
| if AnimationMomentsEditorMode == "selection" then | |
| AnimationMomentsEditor:rfnBindFilterObj("Animations|tree", "AnimationsFilter", | |
| GedFilter:new{ FilterObject = function(self, obj) return obj.group == entity end }) | |
| end | |
| local group = Presets.AnimMetadata[entity] or empty_table | |
| local preset = group[anim] | |
| if not preset then return end | |
| AnimationMomentsEditor:BindObj("AnimationMetadata", preset) | |
| GedObjectModified(character) | |
| GedObjectModified(Presets.AnimMetadata) | |
| GedObjectModified(preset) | |
| if preset.Moments then | |
| GedObjectModified(preset.Moments) | |
| for _, moment in ipairs(preset.Moments) do | |
| GedObjectModified(moment) | |
| end | |
| end | |
| end | |
| function AppearanceHasAnimation(appearance, animation) | |
| return appearance and appearance.Body and table.find(GetStates(appearance.Body), animation) | |
| end | |
| function AppearanceLocateByAnimation(animation, default) | |
| local appearance = default or LocalStorage.AME_LastAppearance | |
| if not AppearanceHasAnimation(AppearancePresets[appearance], animation) then | |
| local found | |
| ForEachPreset("AppearancePreset", function(preset) | |
| if AppearanceHasAnimation(preset, animation) then | |
| appearance = preset.id | |
| found = true | |
| return "break" | |
| end | |
| end) | |
| if not found then | |
| return | |
| end | |
| end | |
| return appearance | |
| end | |
| function OpenAnimationMomentsEditor(target, animation) | |
| local mode = IsValid(target) and "selection" or "appearance" | |
| if mode == "appearance" and animation then | |
| target = AppearanceLocateByAnimation(animation, target) | |
| if not target then return end | |
| end | |
| PopulateParentTableCache(Presets.AnimMetadata) | |
| CreateRealTimeThread(function() | |
| AnimationMomentsEditorMode = mode | |
| local obj | |
| if mode == "appearance" then | |
| local pos, dir = camera.GetEye(), camera.GetDirection() | |
| local pos = terrain.IntersectRay(pos, pos + dir) or pos:SetTerrainZ() | |
| obj = AppearanceObjectAME:new{init_pos = pos} | |
| obj:ApplyAppearance(target) | |
| obj:SetPos(pos) | |
| obj:SetGameFlags(const.gofRealTimeAnim) | |
| else | |
| obj = SelectionObjectAME:new() | |
| obj:SetSelectedObj(target) | |
| end | |
| if not AnimationMomentsEditor then | |
| AnimationMomentsEditor = OpenGedApp("AnimMetadataEditor", obj, { PresetClass = "AnimMetadata", WarningsUpdateRoot = "Animations" }) or false | |
| OpenDialog("AnimMetadataEditorTimeline", GetDevUIViewport()) | |
| else | |
| local old = GetAnimationMomentsEditorObject() | |
| AnimationMomentsEditor:BindObj("root", obj) | |
| DoneObject(old) | |
| end | |
| obj:OnEditorOpen(AnimationMomentsEditor) | |
| if mode == "appearance" or animation then | |
| obj:Setanim(animation or "idle") | |
| end | |
| obj:UpdateAnimMetadataSelection() | |
| InitializeWarningsForGedEditor(AnimationMomentsEditor, "initial") | |
| end) | |
| return true | |
| end | |
| function CloseAnimationMomentsEditor(wait) | |
| if AnimationMomentsEditor then | |
| AnimationMomentsEditor:Send("rfnApp", "Exit") | |
| end | |
| if wait then | |
| while AnimationMomentsEditor do | |
| Sleep(10) | |
| end | |
| end | |
| end | |
| function OnMsg.GedClosing(ged_id) | |
| if AnimationMomentsEditor and AnimationMomentsEditor.ged_id == ged_id then | |
| CloseDialog("AnimMetadataEditorTimeline") | |
| local character = GetAnimationMomentsEditorObject() | |
| if IsValid(character) then | |
| DoneObject(character) | |
| end | |
| AnimationMomentsEditorMode = false | |
| AnimationMomentsEditor = false | |
| end | |
| end | |
| function OnMsg.GedOnEditorSelect(obj, selected, ged_editor) | |
| if selected and ged_editor == AnimationMomentsEditor then | |
| if IsKindOf(obj, "AnimMetadata") then | |
| SuspendObjModified("GedOnEditorSelect") | |
| local character = GetAnimationMomentsEditorObject() | |
| character:OnAnimMetadataSelect(obj) | |
| GedObjectModified(character) | |
| ResumeObjModified("GedOnEditorSelect") | |
| end | |
| end | |
| end | |
| local function EditorSelectionChanged() | |
| if AnimationMomentsEditor and AnimationMomentsEditorMode == "selection" then | |
| local sel_obj = editor.GetSel()[1] | |
| local character = GetAnimationMomentsEditorObject() | |
| if not IsValid(sel_obj) or not character then | |
| CloseAnimationMomentsEditor() | |
| else | |
| character:SetSelectedObj(sel_obj) | |
| character:UpdateSelectedObj() | |
| end | |
| end | |
| end | |
| function OnMsg.EditorSelectionChanged(objects) | |
| if AnimationMomentsEditor and AnimationMomentsEditorMode == "selection" then | |
| DelayedCall(0, EditorSelectionChanged) | |
| end | |
| end | |
| function OnMsg.ChangingMap(map, mapdata, handler_fns) | |
| table.insert(handler_fns, CloseAnimationMomentsEditor) | |
| end |