From c7e33fa7bdb0ca602c62b0866dfe56ddaf3462b7 Mon Sep 17 00:00:00 2001 From: admiralnelson Date: Sun, 14 Dec 2025 11:44:14 +0700 Subject: [PATCH 1/5] now it can launch the animation editor (not fully tested yet) --- .../AnimationKeyframeEditorViewModel.cs | 176 +++++++++--------- .../InterpolateBetweenPose.cs | 4 +- .../DependencyInjectionContainer.cs | 21 ++- .../Examples/DbSchemaBuilder.cs | 26 +-- GameWorld/View3D/WpfWindow/WpfGame.cs | 3 +- 5 files changed, 123 insertions(+), 107 deletions(-) diff --git a/Editors/AnimationEditor/AnimationKeyframeEditor/AnimationKeyframeEditorViewModel.cs b/Editors/AnimationEditor/AnimationKeyframeEditor/AnimationKeyframeEditorViewModel.cs index 56086a2a9..74f4c6ea9 100644 --- a/Editors/AnimationEditor/AnimationKeyframeEditor/AnimationKeyframeEditorViewModel.cs +++ b/Editors/AnimationEditor/AnimationKeyframeEditor/AnimationKeyframeEditorViewModel.cs @@ -4,6 +4,7 @@ using System.Windows.Forms; using AnimationEditor.AnimationKeyframeEditor; using AnimationEditor.MountAnimationCreator.ViewModels; +using CommunityToolkit.Mvvm.ComponentModel; using Editors.Shared.Core.Common; using Editors.Shared.Core.Common.AnimationPlayer; using Editors.Shared.Core.Common.BaseControl; @@ -15,6 +16,7 @@ using GameWorld.Core.Components.Selection; using GameWorld.Core.Services; using Microsoft.Xna.Framework; +using Shared.Core.Events; using Shared.Core.Misc; using Shared.Core.PackFiles; using Shared.GameFormats.Animation; @@ -24,9 +26,10 @@ namespace Editors.AnimationVisualEditors.AnimationKeyframeEditor { - public class AnimationKeyframeEditorViewModel : NotifyPropertyChangedImpl, IHostedEditor + public partial class AnimationKeyframeEditorViewModel : EditorHostBase, IDisposable { - public Type EditorViewModelType => typeof(EditorView); + public override Type EditorViewModelType => typeof(EditorView); + private readonly SceneObjectViewModelBuilder _sceneObjectViewModelBuilder; private readonly AnimationPlayerViewModel _animationPlayerViewModel; private readonly SceneObjectEditor _sceneObjectBuilder; @@ -36,6 +39,7 @@ public class AnimationKeyframeEditorViewModel : NotifyPropertyChangedImpl, IHost private CopyPastePose _copyPastePose; private CopyPasteFromClipboardPose _copyPasteClipboardPose; private InterpolateBetweenPose _interpolateBetweenPose; + private readonly AnimationToolInput _animationToolInput; public SelectionComponent SelectionComponent { get => _selectionComponent; private set { _selectionComponent = value; } } private SelectionComponent _selectionComponent; @@ -69,29 +73,22 @@ public class AnimationKeyframeEditorViewModel : NotifyPropertyChangedImpl, IHost public NotifyAttr EnableInverseKinematics { get; set; } = new NotifyAttr(false); public NotifyAttr IncrementFrameAfterCopyOperation { get; set; } = new NotifyAttr(false); - public bool CopyMoreThanSingleFrame + [ObservableProperty] + private bool _copyMoreThanSingleFrame; + partial void OnCopyMoreThanSingleFrameChanged(bool value) { - get => _copyMoreThanSingleFrame; - set - { - SetAndNotify(ref _copyMoreThanSingleFrame, value); - EnableFrameNrStartTextboxOnPaste.Value = value && PasteUsingFormBelow; - } + EnableFrameNrStartTextboxOnPaste.Value = value && PasteUsingFormBelow; } - bool _copyMoreThanSingleFrame = false; public NotifyAttr DontWarnDifferentSkeletons { get; set; } = new(false); public NotifyAttr DontWarnIncomingFramesBigger { get; set; } = new(false); - public bool PasteUsingFormBelow + + [ObservableProperty] + private bool _pasteUsingFormBelow; + partial void OnPasteUsingFormBelowChanged(bool value) { - get => _pasteUsingFormBelow; - set - { - SetAndNotify(ref _pasteUsingFormBelow, value); - EnableFrameNrStartTextboxOnPaste.Value = !value && CopyMoreThanSingleFrame; - } + EnableFrameNrStartTextboxOnPaste.Value = !value && CopyMoreThanSingleFrame; } - bool _pasteUsingFormBelow = false; public NotifyAttr PastePosition { get; set; } = new(true); public NotifyAttr PasteRotation { get; set; } = new(true); @@ -105,60 +102,35 @@ public bool PasteUsingFormBelow public NotifyAttr CurrentFrameNumber { get; set; } = new(""); public NotifyAttr TotalFrameNumber { get; set; } = new(""); - public string FrameNrLength { get => _frameNrLength; set => SetAndNotify(ref _frameNrLength, value); } + [ObservableProperty] private string _frameNrLength = "0"; - public string FramesDurationInSeconds - { - get => _txtEditDurationInSeconds; - set - { - SetAndNotify(ref _txtEditDurationInSeconds, value); - } - } - private string _txtEditDurationInSeconds = ""; + [ObservableProperty] + private string _framesDurationInSeconds = ""; + + public NotifyAttr SelectedFrameAInterpolation { get; set; } = new("Not set"); public NotifyAttr SelectedFrameBInterpolation { get; set; } = new("Not set"); - public bool PreviewInterpolation - { - get => _previewInterpolation; - set - { - SetAndNotify(ref _previewInterpolation, value); - } - } + [ObservableProperty] private bool _previewInterpolation; - public bool InterpolationOnlyOnSelectedBones - { - get => _interpolationOnlyOnSelectedBones; - set - { - SetAndNotify(ref _interpolationOnlyOnSelectedBones, value); - } - } + [ObservableProperty] private bool _interpolationOnlyOnSelectedBones; - public float InterpolationValue - { - get => _interpolationValue; - set - { - SetAndNotify(ref _interpolationValue, value); - if (PreviewInterpolation) ApplyInterpolationOnCurrentFrame(); - } - } - private float _interpolationValue; - + public NotifyAttr InterpolationValue { get; set; } = new NotifyAttr(0f); public FilterCollection ModelBoneListForIKEndBone { get; set; } = new FilterCollection(null); public AnimationSettingsViewModel AnimationSettings { get; set; } = new AnimationSettingsViewModel(); - public FilterCollection SelectedRiderBone { get; set; } + public FilterCollection SelectedRiderBone { get; set; } public FilterCollection ActiveOutputFragment { get; set; } - public AnimationKeyframeEditorViewModel(IPackFileService pfs, + private readonly IEventHub _eventHub; + + public AnimationKeyframeEditorViewModel( + IEditorHostParameters editorHostParameters, + IPackFileService pfs, ISkeletonAnimationLookUpHelper skeletonAnimationLookUpHelper, SelectionComponent selectionComponent, SceneObjectViewModelBuilder sceneObjectViewModelBuilder, @@ -168,14 +140,17 @@ public AnimationKeyframeEditorViewModel(IPackFileService pfs, SelectionManager selectionManager, GizmoComponent gizmoComponent, CommandExecutor commandExecutor, - IFileSaveService packFileSaveService) + IFileSaveService packFileSaveService, + IEventHub eventHub, + AnimationToolInput animationToolInput) : base(editorHostParameters) { + DisplayName = "Keyframing Editor"; + _sceneObjectViewModelBuilder = sceneObjectViewModelBuilder; _animationPlayerViewModel = animationPlayerViewModel; _sceneObjectBuilder = sceneObjectBuilder; _pfs = pfs; - _skeletonAnimationLookUpHelper = skeletonAnimationLookUpHelper; _selectionComponent = selectionComponent; _commandFactory = commandFactory; @@ -193,6 +168,33 @@ public AnimationKeyframeEditorViewModel(IPackFileService pfs, ActiveFragmentSlot = new FilterCollection(null, (x) => UpdateCanSaveAndPreviewStates()); ActiveFragmentSlot.SearchFilter = (value, rx) => { return rx.Match(value.SlotName).Success; }; + _eventHub = eventHub; + + _eventHub.Register(this, OnSceneObjectUpdated); + + _animationToolInput = animationToolInput; + + Initialize(); + } + + void Initialize() + { + var riderItem = _sceneObjectViewModelBuilder.CreateAsset("IDK", true, "Rider", Color.Black, _animationToolInput); + var mountItem = _sceneObjectViewModelBuilder.CreateAsset("IDK", true, "Mount", Color.Black, _animationToolInput); + mountItem.Data.IsSelectable = true; + + var newAnimItem = _sceneObjectBuilder.CreateAsset("IDK", "New Anim", Color.Red); + _animationPlayerViewModel.RegisterAsset(newAnimItem); + + Create(riderItem.Data, mountItem.Data, newAnimItem); + SceneObjects.Add(riderItem); + SceneObjects.Add(mountItem); + //SceneObjects.Add(newAnimItem); + + _gizmoToolbox = new(this); + _copyPastePose = new(this); + _copyPasteClipboardPose = new(this); + _interpolateBetweenPose = new(this); } private void OutputAnimationSetSelected(IAnimationBinGenericFormat newValue) @@ -207,21 +209,7 @@ private void OutputAnimationSetSelected(IAnimationBinGenericFormat newValue) public void Initialize(EditorHost owner) { - //var riderItem = _sceneObjectViewModelBuilder.CreateAsset(true, "Rider", Color.Black, null); - //var mountItem = _sceneObjectViewModelBuilder.CreateAsset(true, "Mount", Color.Black, null); - //mountItem.Data.IsSelectable = true; - // - //var propAsset = _sceneObjectBuilder.CreateAsset("New Anim", Color.Red); - //_animationPlayerViewModel.RegisterAsset(propAsset); - // - //Create(riderItem.Data, mountItem.Data, propAsset); - //owner.SceneObjects.Add(riderItem); - //owner.SceneObjects.Add(mountItem); - // - //_gizmoToolbox = new(this); - //_copyPastePose = new(this); - //_copyPasteClipboardPose = new(this); - //_interpolateBetweenPose = new(this); + // Legacy init kept for compatibility; new Initialize() is used in ctor } internal void Create(SceneObject rider, SceneObject mount, SceneObject newAnimation) @@ -263,18 +251,18 @@ private void MountSkeletonChanged(GameSkeleton newValue) private void TryReGenerateAnimation(AnimationClip newValue = null) { - // if (_newAnimation != null) - // _sceneObjectBuilder.SetAnimation(_newAnimation, null); - // - // if (newValue != null) - // { - // _originalClip = newValue.Clone(); - // FramesDurationInSeconds = _originalClip.PlayTimeInSec.ToString(); - // SetFrameLengthMax(); - // } - // - // IsDirty.Value = false; - // _interpolateBetweenPose.Reset(); + // if (_newAnimation != null) + // _sceneObjectBuilder.SetAnimation(_newAnimation, null); + // + // if (newValue != null) + // { + // _originalClip = newValue.Clone(); + // FramesDurationInSeconds = _originalClip.PlayTimeInSec.ToString(); + // SetFrameLengthMax(); + // } + // + // IsDirty.Value = false; + // _interpolateBetweenPose.Reset(); } private void RiderSkeletonChanges(GameSkeleton newValue) @@ -312,6 +300,15 @@ private void RiderSkeletonChanges(GameSkeleton newValue) _skeleton = newValue; } + private void OnSceneObjectUpdated(SceneObjectUpdateEvent e) + { + // Keep player frame sync if needed; extend later for mesh/skeleton updates + if (e.Owner == _rider && (e.SkeletonChanged || e.AnimationChanged)) + RiderSkeletonChanges(_rider.Skeleton); + if (e.Owner == _mount && e.SkeletonChanged) + MountSkeletonChanged(_mount.Skeleton); + } + public List GetSelectedBones() => _gizmoToolbox.PreviousSelectedBones; public List GetModifiedBones() => _gizmoToolbox.ModifiedBones; @@ -672,7 +669,7 @@ public void ResetInterpolationTool() public void ResetInterpolationSlider() { PreviewInterpolation = false; - InterpolationValue = 0; + InterpolationValue.Value = 0; } public void ApplyInterpolationOnCurrentFrame() @@ -721,5 +718,10 @@ public void SaveAs() _packFileSaveService.SaveAs(".anim", bytes); IsDirty.Value = false; } + + public void Dispose() + { + _eventHub?.UnRegister(this); + } } } diff --git a/Editors/AnimationEditor/AnimationKeyframeEditor/InterpolateBetweenPose.cs b/Editors/AnimationEditor/AnimationKeyframeEditor/InterpolateBetweenPose.cs index d40d4ba26..4cb843045 100644 --- a/Editors/AnimationEditor/AnimationKeyframeEditor/InterpolateBetweenPose.cs +++ b/Editors/AnimationEditor/AnimationKeyframeEditor/InterpolateBetweenPose.cs @@ -102,7 +102,7 @@ private void ApplyOnCurrentFrame() _keyframeNrA, _keyframeNrB, _parent.Skeleton, - _parent.InterpolationValue, + _parent.InterpolationValue.Value, _parent.PastePosition.Value, _parent.PasteRotation.Value, _parent.PasteScale.Value)).BuildAndExecute(); @@ -119,7 +119,7 @@ private void ApplyOnCurrentFrameSelectedBones() _keyframeNrA, _keyframeNrB, _parent.Skeleton, - _parent.InterpolationValue, + _parent.InterpolationValue.Value, _parent.GetSelectedBones(), _parent.PastePosition.Value, _parent.PasteRotation.Value, diff --git a/Editors/AnimationEditor/DependencyInjectionContainer.cs b/Editors/AnimationEditor/DependencyInjectionContainer.cs index 94adf4bbb..b1e9c45e4 100644 --- a/Editors/AnimationEditor/DependencyInjectionContainer.cs +++ b/Editors/AnimationEditor/DependencyInjectionContainer.cs @@ -14,13 +14,25 @@ public class DependencyInjectionContainer : DependencyContainer { public override void Register(IServiceCollection serviceCollection) { + + //serviceCollection.AddScoped(); + //serviceCollection.AddScoped(); + //serviceCollection.AddScoped(); + //serviceCollection.AddScoped(); + //serviceCollection.AddScoped(); + + //RegisterWindow(serviceCollection); + //RegisterWindow(serviceCollection); + + serviceCollection.AddScoped(); + serviceCollection.AddScoped>(); serviceCollection.AddScoped(); serviceCollection.AddScoped>(); serviceCollection.AddScoped(); - serviceCollection.AddScoped>(); + // Use the new EditorHostBase-based view model directly for the Keyframe editor serviceCollection.AddScoped(); RegisterAllAsInterface(serviceCollection, ServiceLifetime.Transient); @@ -30,7 +42,7 @@ public override void RegisterTools(IEditorDatabase database) { EditorInfoBuilder .Create, EditorHostView>(EditorEnums.MountTool_Editor) - .AddToToolbar("Mount Tool", false) + .AddToToolbar("Mount Tool", true) .Build(database); EditorInfoBuilder @@ -38,9 +50,10 @@ public override void RegisterTools(IEditorDatabase database) .AddToToolbar("Campagin Aanimation Tool", false) .Build(database); + // Register the Keyframe editor using its EditorHostBase-based type EditorInfoBuilder - .Create, EditorHostView>(EditorEnums.AnimationKeyFrame_Editor) - .AddToToolbar("KeyFrame Tool", false) + .Create(EditorEnums.AnimationKeyFrame_Editor) + .AddToToolbar("KeyFrame Tool", true) .Build(database); } } diff --git a/Editors/DatabaseEditor/Utility.DatabaseSchemaGenerator/Examples/DbSchemaBuilder.cs b/Editors/DatabaseEditor/Utility.DatabaseSchemaGenerator/Examples/DbSchemaBuilder.cs index 165d38db2..67e2379ca 100644 --- a/Editors/DatabaseEditor/Utility.DatabaseSchemaGenerator/Examples/DbSchemaBuilder.cs +++ b/Editors/DatabaseEditor/Utility.DatabaseSchemaGenerator/Examples/DbSchemaBuilder.cs @@ -252,23 +252,23 @@ public void PopulateTable(IPackFileService packFileService, SQLiteConnection sql { try { - using (var copy = new SqlBulkCopy(sqlConnection.ConnectionString)) - { + //using (var copy = new SqlBulkCopy(sqlConnection.ConnectionString)) + //{ - //var _ravi = dt.NewRow(); - //_ravi["Name"] = "ravi"; - //_ravi["Marks"] = "500"; - //dt.Rows.Add(_ravi); - // - ////copy.BatchSize - //copy.DestinationTableName = "dbo.Customers"; - //copy.ColumnMappings.Add(nameof(Customer.Id), "Id"); - //copy.ColumnMappings.Add(nameof(Customer.FirstName), "FirstName"); + // //var _ravi = dt.NewRow(); + // //_ravi["Name"] = "ravi"; + // //_ravi["Marks"] = "500"; + // //dt.Rows.Add(_ravi); + // // + // ////copy.BatchSize + // //copy.DestinationTableName = "dbo.Customers"; + // //copy.ColumnMappings.Add(nameof(Customer.Id), "Id"); + // //copy.ColumnMappings.Add(nameof(Customer.FirstName), "FirstName"); - // copy.WriteToServer(dt); - } + // // copy.WriteToServer(dt); + //} Console.WriteLine($"{tableSchema.Name} - {parsedTables}/{tableSchemas.Count}"); diff --git a/GameWorld/View3D/WpfWindow/WpfGame.cs b/GameWorld/View3D/WpfWindow/WpfGame.cs index e8867aa08..8df7a4aa7 100644 --- a/GameWorld/View3D/WpfWindow/WpfGame.cs +++ b/GameWorld/View3D/WpfWindow/WpfGame.cs @@ -225,7 +225,8 @@ public T GetComponent() where T : IGameComponent public T AddComponent(T comp) where T : IGameComponent { - Components.Add(comp); + if (!Components.Contains(comp)) + Components.Add(comp); return comp; } From 4bdb6170eedd7e6caac7630f65ea8b2c598194c2 Mon Sep 17 00:00:00 2001 From: admiralnelson Date: Fri, 22 May 2026 20:33:16 +0700 Subject: [PATCH 2/5] the editor loads, i guess --- .../DependencyInjectionContainer.cs | 9 +- .../DevConfig/MountTool.cs | 2 +- .../MountAnimationCreatorViewModel.cs | 100 +++++++++++------- 3 files changed, 64 insertions(+), 47 deletions(-) diff --git a/Editors/AnimationEditor/DependencyInjectionContainer.cs b/Editors/AnimationEditor/DependencyInjectionContainer.cs index af2088b60..6b2a45d56 100644 --- a/Editors/AnimationEditor/DependencyInjectionContainer.cs +++ b/Editors/AnimationEditor/DependencyInjectionContainer.cs @@ -25,9 +25,8 @@ public override void Register(IServiceCollection serviceCollection) serviceCollection.AddScoped(); - serviceCollection.AddScoped>(); - serviceCollection.AddScoped(); - serviceCollection.AddScoped>(); + //serviceCollection.AddScoped>(); + //serviceCollection.AddScoped(); serviceCollection.AddScoped(); // Use the new EditorHostBase-based view model directly for the Keyframe editor @@ -39,10 +38,10 @@ public override void Register(IServiceCollection serviceCollection) public override void RegisterTools(IEditorDatabase database) { EditorInfoBuilder - .Create, EditorHostView>(EditorEnums.MountTool_Editor) + .Create(EditorEnums.MountTool_Editor) .AddToToolbar("Mount Tool", true) .Build(database); - + // Register the Keyframe editor using its EditorHostBase-based type EditorInfoBuilder .Create(EditorEnums.AnimationKeyFrame_Editor) diff --git a/Editors/AnimationEditor/MountAnimationCreator/DevConfig/MountTool.cs b/Editors/AnimationEditor/MountAnimationCreator/DevConfig/MountTool.cs index 3818c7eac..9a1124c17 100644 --- a/Editors/AnimationEditor/MountAnimationCreator/DevConfig/MountTool.cs +++ b/Editors/AnimationEditor/MountAnimationCreator/DevConfig/MountTool.cs @@ -42,7 +42,7 @@ static void CreateLionAndHu01b(IEditorManager creator, IPackFileService packfile Mesh = packfileService.FindFile(@"variantmeshes\variantmeshdefinitions\hef_war_lion.variantmeshdefinition") }; - creator.Create(EditorEnums.MountTool_Editor, x => (x as EditorHost).Editor.SetDebugInputParameters(riderInput, mountInput)); + creator.Create(EditorEnums.MountTool_Editor, x => (x as MountAnimationCreatorViewModel)?.SetDebugInputParameters(riderInput, mountInput)); } } } diff --git a/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs b/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs index eec1d6793..6c3d936f5 100644 --- a/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs +++ b/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs @@ -31,9 +31,10 @@ namespace AnimationEditor.MountAnimationCreator { - public class MountAnimationCreatorViewModel : NotifyPropertyChangedImpl, IHostedEditor + public partial class MountAnimationCreatorViewModel : EditorHostBase { - public Type EditorViewModelType => typeof(EditorView); + public override Type EditorViewModelType => typeof(EditorView); + private readonly SceneObjectViewModelBuilder _sceneObjectViewModelBuilder; private readonly SceneObjectEditor _sceneObjectBuilder; private readonly IFileSaveService _fileSaveService; @@ -46,16 +47,16 @@ public class MountAnimationCreatorViewModel : NotifyPropertyChangedImpl, IHosted SceneObject _mount; SceneObject _rider; SceneObject _newAnimation; - + List _mountVertexes = new(); Rmv2MeshNode _mountVertexOwner; AnimationToolInput _inputRiderData; AnimationToolInput _inputMountData; + bool _initializedFromDebugInputs = false; public AnimationSettingsViewModel AnimationSettings { get; set; } = new AnimationSettingsViewModel(); public MountLinkViewModel MountLinkController { get; set; } - public string EditorName => "Mount Animation Creator"; public FilterCollection SelectedRiderBone { get; set; } @@ -76,15 +77,19 @@ public class MountAnimationCreatorViewModel : NotifyPropertyChangedImpl, IHosted public FilterCollection ActiveOutputFragment { get; set; } public FilterCollection ActiveFragmentSlot { get; set; } - public MountAnimationCreatorViewModel(IPackFileService pfs, - ISkeletonAnimationLookUpHelper skeletonAnimationLookUpHelper, + public MountAnimationCreatorViewModel( + IEditorHostParameters editorHostParameters, + IPackFileService pfs, + ISkeletonAnimationLookUpHelper skeletonAnimationLookUpHelper, SelectionManager selectionManager, SceneObjectViewModelBuilder sceneObjectViewModelBuilder, AnimationPlayerViewModel animationPlayerViewModel, SceneObjectEditor sceneObjectBuilder, IFileSaveService fileSaveService, - IUiCommandFactory uiCommandFactory) + IUiCommandFactory uiCommandFactory) : base(editorHostParameters) { + DisplayName = "Mount Animation Creator"; + _sceneObjectViewModelBuilder = sceneObjectViewModelBuilder; _animationPlayerViewModel = animationPlayerViewModel; _sceneObjectBuilder = sceneObjectBuilder; @@ -95,8 +100,8 @@ public MountAnimationCreatorViewModel(IPackFileService pfs, _skeletonAnimationLookUpHelper = skeletonAnimationLookUpHelper; _selectionManager = selectionManager; - DisplayGeneratedSkeleton = new NotifyAttr(true, (value) => _newAnimation.ShowSkeleton.Value = value); - DisplayGeneratedMesh = new NotifyAttr(true, (value) => { if (_newAnimation.MainNode != null) _newAnimation.ShowMesh.Value = value; }); + DisplayGeneratedSkeleton = new NotifyAttr(true, (value) => { if (_newAnimation != null) _newAnimation.ShowSkeleton.Value = value; }); + DisplayGeneratedMesh = new NotifyAttr(true, (value) => { if (_newAnimation?.MainNode != null) _newAnimation.ShowMesh.Value = value; }); SelectedRiderBone = new FilterCollection(null, (x) => UpdateCanSaveAndPreviewStates()); @@ -108,26 +113,42 @@ public MountAnimationCreatorViewModel(IPackFileService pfs, ActiveFragmentSlot.SearchFilter = (value, rx) => { return rx.Match(value.SlotName).Success; }; AnimationSettings.SettingsChanged += () => TryReGenerateAnimation(null); + + // If debug inputs were provided before ctor finished, initialize now + if (_inputRiderData != null && _inputMountData != null) + InitializeFromDebugInputs(); } public void SetDebugInputParameters(AnimationToolInput rider, AnimationToolInput mount) { _inputRiderData = rider; _inputMountData = mount; + + InitializeFromDebugInputs(); + } + + void InitializeFromDebugInputs() + { + if (_initializedFromDebugInputs) return; + if (_inputRiderData == null || _inputMountData == null) return; + + var riderItem = _sceneObjectViewModelBuilder.CreateAsset("IDK", true, "Rider", Color.Black, _inputRiderData); + var mountItem = _sceneObjectViewModelBuilder.CreateAsset("IDK", true, "Mount", Color.Black, _inputMountData); + mountItem.Data.IsSelectable = true; + + var propAsset = _sceneObjectBuilder.CreateAsset("IDK", "New Anim", Color.Red); + _animationPlayerViewModel.RegisterAsset(propAsset); + + Create(riderItem.Data, mountItem.Data, propAsset); + SceneObjects.Add(riderItem); + SceneObjects.Add(mountItem); + + _initializedFromDebugInputs = true; } public void Initialize(EditorHost owner) { - // var riderItem = _sceneObjectViewModelBuilder.CreateAsset(true, "Rider", Color.Black, _inputRiderData); - // var mountItem = _sceneObjectViewModelBuilder.CreateAsset(true, "Mount", Color.Black, _inputMountData); - // mountItem.Data.IsSelectable = true; - // - // var propAsset = _sceneObjectBuilder.CreateAsset("New Anim", Color.Red); - // _animationPlayerViewModel.RegisterAsset(propAsset); - // - // Create(riderItem.Data, mountItem.Data, propAsset); - // owner.SceneObjects.Add(riderItem); - // owner.SceneObjects.Add(mountItem); + // Legacy init kept for compatibility; new initialization is handled in ctor or SetDebugInputParameters } internal void Create(SceneObject rider, SceneObject mount, SceneObject newAnimation) @@ -149,14 +170,14 @@ internal void Create(SceneObject rider, SceneObject mount, SceneObject newAnimat private void TryReGenerateAnimation(AnimationClip newValue = null) { - // UpdateCanSaveAndPreviewStates(); - // if (CanPreview.Value) - // CreateMountAnimationAction(); - // else - // { - // if (_newAnimation != null) - // _sceneObjectBuilder.SetAnimation(_newAnimation, null); - // } + // UpdateCanSaveAndPreviewStates(); + // if (CanPreview.Value) + // CreateMountAnimationAction(); + // else + // { + // if (_newAnimation != null) + _sceneObjectBuilder.SetAnimation(_newAnimation, null); + // } } private void MountSkeletonChanged(GameSkeleton newValue) @@ -232,14 +253,14 @@ public void SetMountVertex() public void CreateMountAnimationAction() { - //var newRiderAnim = CreateAnimationGenerator().GenerateMountAnimation(_mount.AnimationClip, _rider.AnimationClip); - // - //// Apply - //_sceneObjectBuilder.CopyMeshFromOther(_newAnimation, _rider); - //_sceneObjectBuilder.SetAnimationClip(_newAnimation, newRiderAnim, new SkeletonAnimationLookUpHelper.AnimationReference("Generated animation", null)); - //_newAnimation.ShowSkeleton.Value = DisplayGeneratedSkeleton.Value; - //_newAnimation.ShowMesh.Value = DisplayGeneratedMesh.Value; - //UpdateCanSaveAndPreviewStates(); + //var newRiderAnim = CreateAnimationGenerator().GenerateMountAnimation(_mount.AnimationClip, _rider.AnimationClip); + // + //// Apply + //_sceneObjectBuilder.CopyMeshFromOther(_newAnimation, _rider); + //_sceneObjectBuilder.SetAnimationClip(_newAnimation, newRiderAnim, new SkeletonAnimationLookUpHelper.AnimationReference("Generated animation", null)); + //_newAnimation.ShowSkeleton.Value = DisplayGeneratedSkeleton.Value; + //_newAnimation.ShowMesh.Value = DisplayGeneratedMesh.Value; + //UpdateCanSaveAndPreviewStates(); } MountAnimationGeneratorService CreateAnimationGenerator() @@ -274,7 +295,7 @@ public void AddAnimationToFragment() //var bytes = AnimationPackSerializer.ConvertToBytes(ActiveOutputFragment.SelectedItem.Parent); //SaveHelper.Save(_pfs, "animations\\animation_tables\\" + ActiveOutputFragment.SelectedItem.Parent.FileName, null, bytes, false); // - //// Update status for the slot thing + //// Update status for the slot thing //var possibleValues = ActiveOutputFragment.SelectedItem.Fragments.Select(x => new FragmentStatusSlotItem(x)); //ActiveFragmentSlot.UpdatePossibleValues(possibleValues); //MountLinkController.ReloadFragments(true, false); @@ -301,8 +322,8 @@ public void RefreshViewAction() public void SaveCurrentAnimationAction() { - // var service = new BatchProcessorService(_pfs, _skeletonAnimationLookUpHelper, CreateAnimationGenerator(), new BatchProcessOptions { SavePrefix = SavePrefixText.Value }, _fileSaveService, SelectedAnimationOutputFormat.Value); - // service.SaveSingleAnim(_mount.AnimationClip, _rider.AnimationClip, _rider.AnimationName.Value.AnimationFile); + // var service = new BatchProcessorService(_pfs, _skeletonAnimationLookUpHelper, CreateAnimationGenerator(), new BatchProcessOptions { SavePrefix = SavePrefixText.Value }, _fileSaveService, SelectedAnimationOutputFormat.Value); + // service.SaveSingleAnim(_mount.AnimationClip, _rider.AnimationClip, _rider.AnimationName.Value.AnimationFile); } public void BatchProcessUsingFragmentsAction() @@ -342,6 +363,3 @@ public void CopyAnimation() } } } - - - From d38e9a22e1a02eb2a464f09af6e1872cc15c68bd Mon Sep 17 00:00:00 2001 From: admiralnelson Date: Fri, 22 May 2026 21:02:04 +0700 Subject: [PATCH 3/5] can use the mount editor again --- .../MountAnimationCreatorViewModel.cs | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs b/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs index 6c3d936f5..1b373d975 100644 --- a/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs +++ b/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs @@ -116,15 +116,43 @@ public MountAnimationCreatorViewModel( // If debug inputs were provided before ctor finished, initialize now if (_inputRiderData != null && _inputMountData != null) + { InitializeFromDebugInputs(); + } + else + { + // Default initialization when running inside the app (no debug inputs provided). + // This mirrors other editors (eg. AnimationKeyframeEditor) and ensures + // MountLinkController and scene objects are created so the view model + // is fully usable after the applet loads. + var riderItem = _sceneObjectViewModelBuilder.CreateAsset("IDK", true, "Rider", Color.Black, null); + var mountItem = _sceneObjectViewModelBuilder.CreateAsset("IDK", true, "Mount", Color.Black, null); + mountItem.Data.IsSelectable = true; + + var propAsset = _sceneObjectBuilder.CreateAsset("IDK", "New Anim", Color.Red); + _animationPlayerViewModel.RegisterAsset(propAsset); + + Create(riderItem.Data, mountItem.Data, propAsset); + SceneObjects.Add(riderItem); + SceneObjects.Add(mountItem); + } } public void SetDebugInputParameters(AnimationToolInput rider, AnimationToolInput mount) + { + _inputRiderData = rider; + _inputMountData = mount; - InitializeFromDebugInputs(); + + + // Only initialize from debug inputs if we haven't already initialized the view model. + // This prevents creating duplicate scene objects when the app performs the default init in the ctor. + if (!_initializedFromDebugInputs && MountLinkController == null) + InitializeFromDebugInputs(); + } void InitializeFromDebugInputs() From f6384416dd77ff615f1e04b56f1cc4c78ba68fc2 Mon Sep 17 00:00:00 2001 From: admiralnelson Date: Fri, 22 May 2026 21:02:21 +0700 Subject: [PATCH 4/5] added slopping guide --- AnimationEditor/how to/01-create-applet.md | 91 +++++++++++++ .../how to/02-register-and-debug.md | 128 ++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 AnimationEditor/how to/01-create-applet.md create mode 100644 AnimationEditor/how to/02-register-and-debug.md diff --git a/AnimationEditor/how to/01-create-applet.md b/AnimationEditor/how to/01-create-applet.md new file mode 100644 index 000000000..08a60b9f2 --- /dev/null +++ b/AnimationEditor/how to/01-create-applet.md @@ -0,0 +1,91 @@ +# 01 — Create a new Animation applet (editor) + +This guide shows how to add a new Animation applet (editor) following the same structure as +`AnimationKeyframeEditorViewModel` and `MountAnimationCreatorViewModel`. + +Where to put code +- Place your editor-specific files under `Editors/AnimationEditor//` (this is consistent with the existing editors). +- Example existing editors: + - `Editors/AnimationEditor/AnimationKeyframeEditor/AnimationKeyframeEditorViewModel.cs` + - `Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs` + +High level steps +1. Create a ViewModel class (recommended: derive from `EditorHostBase`). +2. (Optional) Create a custom view `UserControl` if you want a custom UI; otherwise the shared `EditorHostView` is used. +3. Add a `Create(...)` method and wire your SceneObjects and services exactly like the existing editors. +4. Add any helper classes (ViewModels, sub-ViewModels) under your folder. + +Minimal ViewModel skeleton (copy & adapt) + +```csharp +// file: Editors/AnimationEditor/MyApplet/MyAppletViewModel.cs +using System; +using Editors.Shared.Core.Common.BaseControl; +using Editors.Shared.Core.Common; +using Editors.Shared.Core.Common.AnimationPlayer; +using Shared.Core.ToolCreation; +using Microsoft.Extensions.DependencyInjection; // (for DI registration example later) + +namespace Editors.AnimationVisualEditors.MyApplet +{ + public partial class MyAppletViewModel : EditorHostBase + { + public override Type EditorViewModelType => typeof(AnimationEditor.Common.BaseControl.EditorHostView); + + // Dependencies you typically need — match constructor to what DI can provide. + public MyAppletViewModel( + IEditorHostParameters editorHostParameters, + SceneObjectViewModelBuilder sceneObjectViewModelBuilder, + AnimationPlayerViewModel animationPlayerViewModel, + SceneObjectEditor sceneObjectEditor, + IPackFileService pfs, + ISkeletonAnimationLookUpHelper skeletonAnimationLookUpHelper, + SelectionManager selectionManager, + IFileSaveService fileSaveService, + IUiCommandFactory uiCommandFactory) : base(editorHostParameters) + { + DisplayName = "My Applet"; + + // Use the provided builders/services to create default scene objects the same way + // other editors do to ensure things like MountLinkController are initialized. + var riderItem = sceneObjectViewModelBuilder.CreateAsset("IDK", true, "Rider", Microsoft.Xna.Framework.Color.Black, null); + var mountItem = sceneObjectViewModelBuilder.CreateAsset("IDK", true, "Mount", Microsoft.Xna.Framework.Color.Black, null); + mountItem.Data.IsSelectable = true; + + var newAnimAsset = sceneObjectEditor.CreateAsset("IDK", "New Anim", Microsoft.Xna.Framework.Color.Red); + animationPlayerViewModel.RegisterAsset(newAnimAsset); + + Create(riderItem.Data, mountItem.Data, newAnimAsset); + SceneObjects.Add(riderItem); + SceneObjects.Add(mountItem); + } + + internal void Create(SceneObject rider, SceneObject mount, SceneObject newAnimation) + { + // store refs, hook events, create sub-viewmodels etc. + } + } +} +``` + +Notes about using `EditorHostBase` +- The shared `EditorHostView` is used by many editors and is a good default unless you need custom UI. +- Put logic that wires SceneObjects and registers with the `AnimationPlayerViewModel` into the constructor, or into a helper `Create(...)` method similar to existing editors. + +Common pitfalls +- If you leave essential components (like the MountLink controller in the Mount editor) uncreated until some manual initialization, code that expects them on startup can get NullReferenceExceptions. The pattern in the repo is to create at least default Rider/Mount/NewAnim scene objects in the constructor so the editor is ready. +- Be mindful of the non-nullable reference warnings. You can: + - initialize fields eagerly, + - mark them nullable if they legitimately start null, or + - ensure constructors always initialize them. + +Testing +- Build the solution. If there are DI or registration errors the build/run will tell you which types are missing. +- Next step is to register the applet in DI and the editor database (see `02-register-and-debug.md`). + +References +- Example ViewModels to copy from: + - `Editors/AnimationEditor/AnimationKeyframeEditor/AnimationKeyframeEditorViewModel.cs` + - `Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs` + +-- diff --git a/AnimationEditor/how to/02-register-and-debug.md b/AnimationEditor/how to/02-register-and-debug.md new file mode 100644 index 000000000..8e45cf232 --- /dev/null +++ b/AnimationEditor/how to/02-register-and-debug.md @@ -0,0 +1,128 @@ +# 02 — Register your applet in DI and add a dev-config for quick debug + +After you add your ViewModel (see `01-create-applet.md`), register it so the editor host can create and show it. + +1) Add the ViewModel to the animation editors DI container + +Edit the container at: + +``` +TheAssetEditor/Editors/AnimationEditor/DependencyInjectionContainer.cs +``` + +Inside `Register(IServiceCollection serviceCollection)` add a scoped registration for your view model: + +```csharp +serviceCollection.AddScoped(); +``` + +This mirrors how the repo registers existing editors: + +```csharp +serviceCollection.AddScoped(); +serviceCollection.AddScoped(); +``` + +2) Register the editor in the editor database (so it appears in the toolbar/menu) + +In the same `DependencyInjectionContainer`, implement `RegisterTools(IEditorDatabase database)` and add an EditorInfo entry. Example: + +```csharp +EditorInfoBuilder + .Create(EditorEnums.MyApplet_Editor) + .AddToToolbar("My Applet", true) + .Build(database); +``` + +Notes: +- `EditorHostView` is the default shared host view. Use a custom UserControl type here if you created a custom UI view for your applet. +- `EditorEnums.MyApplet_Editor` must exist in the `EditorEnums` enum (see next step). + +3) Add an enum value for your editor + +Open: + +``` +Shared/SharedCore/Shared.Core/ToolCreation/EditorEnums.cs +``` + +Add an entry (for example): + +```csharp +public enum EditorEnums +{ + ... + MyApplet_Editor, + None, +} +``` + +Make sure the enum value is unique and update any usages if necessary. + +4) (Optional) Add a dev-config so your app opens pre-configured for development + +To open your editor automatically (with sample inputs), add a dev-config like the repo's `MountTool` example. Create a class under `Editors/AnimationEditor/MyApplet/DevConfig/MyAppletDevConfig.cs` implementing `IDeveloperConfiguration`. + +Sample DevConfig (copy & adapt from `MountTool`): + +```csharp +using Shared.Core.DevConfig; +using Shared.Core.ToolCreation; +using Shared.Core.PackFiles; // for packfile lookups + +namespace Editors.AnimationVisualEditors.MyApplet.DevConfig +{ + internal class MyAppletDevConfig : IDeveloperConfiguration + { + private readonly IEditorManager _editorManager; + private readonly IPackFileService _packFileService; + + public MyAppletDevConfig(IEditorManager editorManager, IPackFileService packFileService) + { + _editorManager = editorManager; + _packFileService = packFileService; + } + + public void OverrideSettings(ApplicationSettings currentSettings) + { + // if you need to change game settings for development/testing + } + + public void OpenFileOnLoad() + { + // create a debug AnimationToolInput (like MountTool does) + var input = new AnimationToolInput() + { + Mesh = _packFileService.FindFile("variantmeshes\\...\\some.variantmeshdefinition"), + Animation = _packFileService.FindFile("animations\\...\\some.anim") + }; + + _editorManager.Create(EditorEnums.MyApplet_Editor, x => (x as MyAppletViewModel)?.SetDebugInputParameters(input)); + } + } +} +``` + +This is useful for quickly launching your editor preloaded with assets. + +5) Build and run + +- Build the solution and run the host application. +- The editor toolbar should show your applet (if you used `.AddToToolbar(...)`). +- Use the toolbar button or the editor manager to create your editor. + +Troubleshooting +- If constructor dependencies are missing, ensure the required services are registered in other DI containers that the top-level bootstrapping includes (the project has many specialized DI containers; the Animation DI container depends on other shared containers). +- If your editor throws NREs at startup related to missing controllers, follow the pattern in `MountAnimationCreatorViewModel` and create default Rider/Mount/NewAnim objects in the constructor so dependent controllers are created immediately. + +Extra: register a custom view +- If you create a custom `UserControl` for your applet (e.g. `MyAppletView`), register it (AddTransient) in the DI container and change the EditorInfo registration to: + +```csharp +EditorInfoBuilder + .Create(EditorEnums.MyApplet_Editor) + .AddToToolbar("My Applet", true) + .Build(database); +``` + +That's it — you should now be able to add and iterate on an applet using the same patterns as the repo's keyframe and mount editors. From 1c17f6f03038401e61c82b3976e5263c499b7d91 Mon Sep 17 00:00:00 2001 From: admiralnelson Date: Mon, 25 May 2026 08:17:58 +0700 Subject: [PATCH 5/5] bugfix cant regenerate animation --- .../MountAnimationCreatorViewModel.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs b/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs index 1b373d975..a1031a1a4 100644 --- a/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs +++ b/Editors/AnimationEditor/MountAnimationCreator/MountAnimationCreatorViewModel.cs @@ -43,6 +43,7 @@ public partial class MountAnimationCreatorViewModel : EditorHostBase private readonly IPackFileService _pfs; private readonly SelectionManager _selectionManager; private readonly ISkeletonAnimationLookUpHelper _skeletonAnimationLookUpHelper; + private readonly IGlobalEventHub _globalEventHub; SceneObject _mount; SceneObject _rider; @@ -86,7 +87,8 @@ public MountAnimationCreatorViewModel( AnimationPlayerViewModel animationPlayerViewModel, SceneObjectEditor sceneObjectBuilder, IFileSaveService fileSaveService, - IUiCommandFactory uiCommandFactory) : base(editorHostParameters) + IUiCommandFactory uiCommandFactory, + IGlobalEventHub globalEventHub) : base(editorHostParameters) { DisplayName = "Mount Animation Creator"; @@ -99,6 +101,7 @@ public MountAnimationCreatorViewModel( _skeletonAnimationLookUpHelper = skeletonAnimationLookUpHelper; _selectionManager = selectionManager; + _globalEventHub = globalEventHub; DisplayGeneratedSkeleton = new NotifyAttr(true, (value) => { if (_newAnimation != null) _newAnimation.ShowSkeleton.Value = value; }); DisplayGeneratedMesh = new NotifyAttr(true, (value) => { if (_newAnimation?.MainNode != null) _newAnimation.ShowMesh.Value = value; }); @@ -281,14 +284,14 @@ public void SetMountVertex() public void CreateMountAnimationAction() { - //var newRiderAnim = CreateAnimationGenerator().GenerateMountAnimation(_mount.AnimationClip, _rider.AnimationClip); + var newRiderAnim = CreateAnimationGenerator().GenerateMountAnimation(_mount.AnimationClip, _rider.AnimationClip); // //// Apply - //_sceneObjectBuilder.CopyMeshFromOther(_newAnimation, _rider); - //_sceneObjectBuilder.SetAnimationClip(_newAnimation, newRiderAnim, new SkeletonAnimationLookUpHelper.AnimationReference("Generated animation", null)); - //_newAnimation.ShowSkeleton.Value = DisplayGeneratedSkeleton.Value; - //_newAnimation.ShowMesh.Value = DisplayGeneratedMesh.Value; - //UpdateCanSaveAndPreviewStates(); + _sceneObjectBuilder.CopyMeshFromOther(_newAnimation, _rider); + _sceneObjectBuilder.SetAnimationClip(_newAnimation, newRiderAnim, "Gemerated anim"); + _newAnimation.ShowSkeleton.Value = DisplayGeneratedSkeleton.Value; + _newAnimation.ShowMesh.Value = DisplayGeneratedMesh.Value; + UpdateCanSaveAndPreviewStates(); } MountAnimationGeneratorService CreateAnimationGenerator()