From f4ccde6fbf02c113f7d579d880b443952da8757a Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Fri, 24 Dec 2021 03:33:21 -0700 Subject: [PATCH 01/14] Initial animation support --- Anamnesis/Core/Memory/AddressService.cs | 2 + Anamnesis/MainWindow.xaml | 10 +++ Anamnesis/Memory/ActorActionMemory.cs | 24 +++++++ Anamnesis/ServiceManager.cs | 1 + Anamnesis/Services/ActorActionsService.cs | 84 +++++++++++++++++++++++ Anamnesis/Views/ActorActionView.xaml | 33 +++++++++ Anamnesis/Views/ActorActionView.xaml.cs | 30 ++++++++ 7 files changed, 184 insertions(+) create mode 100644 Anamnesis/Memory/ActorActionMemory.cs create mode 100644 Anamnesis/Services/ActorActionsService.cs create mode 100644 Anamnesis/Views/ActorActionView.xaml create mode 100644 Anamnesis/Views/ActorActionView.xaml.cs diff --git a/Anamnesis/Core/Memory/AddressService.cs b/Anamnesis/Core/Memory/AddressService.cs index d26d3bf48..03a68e3af 100644 --- a/Anamnesis/Core/Memory/AddressService.cs +++ b/Anamnesis/Core/Memory/AddressService.cs @@ -36,6 +36,7 @@ public class AddressService : ServiceBase public static IntPtr TimeAsm { get; private set; } public static IntPtr TimeReal { get; set; } public static IntPtr PlayerTargetSystem { get; set; } + public static IntPtr ActorActionTable { get; set; } public static IntPtr Camera { @@ -131,6 +132,7 @@ public static async Task Scan() tasks.Add(GetAddressFromSignature("GPose", "48 39 0D ?? ?? ?? ?? 75 28", 0, (p) => { GPose = p + 0x20; })); tasks.Add(GetAddressFromSignature("Camera", "48 8D 35 ?? ?? ?? ?? 48 8B 09", 0, (p) => { cameraManager = p; })); // CameraAddress tasks.Add(GetAddressFromSignature("PlayerTargetSystem", "48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 0, (p) => { PlayerTargetSystem = p; })); + tasks.Add(GetAddressFromSignature("ActorActionTable", "48 8B 0D ?? ?? ?? ?? 41 B1 0A", 0, (p) => { ActorActionTable = p; })); tasks.Add(GetAddressFromTextSignature( "TimeAsm", diff --git a/Anamnesis/MainWindow.xaml b/Anamnesis/MainWindow.xaml index d4aec1c9a..f659c5b7f 100644 --- a/Anamnesis/MainWindow.xaml +++ b/Anamnesis/MainWindow.xaml @@ -385,6 +385,16 @@ + + + + + + + diff --git a/Anamnesis/Memory/ActorActionMemory.cs b/Anamnesis/Memory/ActorActionMemory.cs new file mode 100644 index 000000000..fb795b880 --- /dev/null +++ b/Anamnesis/Memory/ActorActionMemory.cs @@ -0,0 +1,24 @@ +// © Anamnesis. +// Licensed under the MIT license. + +namespace Anamnesis.Memory +{ + using System; + + public class ActorActionMemory : MemoryBase + { + public static readonly int ActionMemoryLength = 0xA0; + public static readonly int EntriesInActionTable = 0x40; + + public enum ActionTypes : byte + { + Emote = 0x1, + Action = 0x2, + } + + [Bind(0x00)] public IntPtr ActorPtr { get; set; } + [Bind(0x08)] public uint ActorObjectId { get; set; } + [Bind(0x10)] public ActionTypes ActionType { get; set; } + [Bind(0x14)] public ushort ActionId { get; set; } + } +} diff --git a/Anamnesis/ServiceManager.cs b/Anamnesis/ServiceManager.cs index 12022a85a..ba7e87fdf 100644 --- a/Anamnesis/ServiceManager.cs +++ b/Anamnesis/ServiceManager.cs @@ -67,6 +67,7 @@ public async Task InitializeServices() await Add(); await Add(); await Add(); + await Add(); await Add(); IsInitialized = true; diff --git a/Anamnesis/Services/ActorActionsService.cs b/Anamnesis/Services/ActorActionsService.cs new file mode 100644 index 000000000..fec631d1e --- /dev/null +++ b/Anamnesis/Services/ActorActionsService.cs @@ -0,0 +1,84 @@ +// © Anamnesis. +// Licensed under the MIT license. + +namespace Anamnesis.Services +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Anamnesis.Core.Memory; + using Anamnesis.Memory; + + public class ActorActionsService : ServiceBase + { + public override Task Start() + { + return base.Start(); + } + + public IEnumerable GetTable() + { + IntPtr tableStart = MemoryService.ReadPtr(AddressService.ActorActionTable) + 0x5B0; + + List result = new(); + for(int i = 0; i < ActorActionMemory.EntriesInActionTable; ++i) + { + IntPtr address = tableStart + (i * ActorActionMemory.ActionMemoryLength); + ActorActionMemory actorActionMemory = new(); + actorActionMemory.SetAddress(address); + + result.Add(actorActionMemory); + } + + return result; + } + + public void SetActorAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes actionType, ushort actionId) + { + ActorActionMemory? toSet = null; + var table = this.GetTable(); + + foreach (var entry in table.Take(5)) + { + toSet = entry; + toSet.ActorPtr = actor.Address; + toSet.ActorObjectId = actor.ObjectId; + toSet.ActionType = actionType; + toSet.ActionId = actionId; + } + + // First search for an existing entry + foreach (var entry in table) + { + if (entry.ActorPtr == actor.Address || entry.ActorObjectId == actor.ObjectId) + { + toSet = entry; + break; + } + } + + if (toSet == null) + { + // Next we search for an empty entry + foreach (var entry in table) + { + if (entry.ActorPtr == IntPtr.Zero || entry.ActorObjectId == 0xE) + { + toSet = entry; + break; + } + } + } + + // If there is no space maybe we should write a random one? + if (toSet == null) + return; + + toSet.ActorPtr = actor.Address; + toSet.ActorObjectId = actor.ObjectId; + toSet.ActionType = actionType; + toSet.ActionId = actionId; + } + } +} diff --git a/Anamnesis/Views/ActorActionView.xaml b/Anamnesis/Views/ActorActionView.xaml new file mode 100644 index 000000000..bc7de98e9 --- /dev/null +++ b/Anamnesis/Views/ActorActionView.xaml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + Type + + None + Emote + Action + + + ID + + + + + diff --git a/Anamnesis/Views/ActorActionView.xaml.cs b/Anamnesis/Views/ActorActionView.xaml.cs new file mode 100644 index 000000000..e7f056451 --- /dev/null +++ b/Anamnesis/Views/ActorActionView.xaml.cs @@ -0,0 +1,30 @@ +// © Anamnesis. +// Licensed under the MIT license. + +namespace Anamnesis.Views +{ + using System.Windows.Controls; + using Anamnesis.Memory; + using Anamnesis.Services; + + public partial class ActorActionView : UserControl + { + public ActorActionView() + { + this.DataContext = this; + this.InitializeComponent(); + } + + public ActorActionMemory.ActionTypes ActionType { get; set; } = ActorActionMemory.ActionTypes.Emote; + public ushort ActionId { get; set; } = 232; + + private void ApplyAction_Click(object sender, System.Windows.RoutedEventArgs e) + { + var selectedActor = TargetService.Instance.SelectedActor; + if (selectedActor != null) + { + ActorActionsService.Instance.SetActorAction(selectedActor, this.ActionType, this.ActionId); + } + } + } +} From f37634204cf1239f3c058901a842f9f8abab0723 Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Fri, 24 Dec 2021 03:51:46 -0700 Subject: [PATCH 02/14] Fix bindings --- Anamnesis/Views/ActorActionView.xaml | 2 +- Anamnesis/Views/ActorActionView.xaml.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Anamnesis/Views/ActorActionView.xaml b/Anamnesis/Views/ActorActionView.xaml index bc7de98e9..b4f6c8763 100644 --- a/Anamnesis/Views/ActorActionView.xaml +++ b/Anamnesis/Views/ActorActionView.xaml @@ -19,7 +19,7 @@ Type - + None Emote Action diff --git a/Anamnesis/Views/ActorActionView.xaml.cs b/Anamnesis/Views/ActorActionView.xaml.cs index e7f056451..ed6766751 100644 --- a/Anamnesis/Views/ActorActionView.xaml.cs +++ b/Anamnesis/Views/ActorActionView.xaml.cs @@ -15,7 +15,7 @@ public ActorActionView() this.InitializeComponent(); } - public ActorActionMemory.ActionTypes ActionType { get; set; } = ActorActionMemory.ActionTypes.Emote; + public int ActionType { get; set; } = (int)ActorActionMemory.ActionTypes.Emote; public ushort ActionId { get; set; } = 232; private void ApplyAction_Click(object sender, System.Windows.RoutedEventArgs e) @@ -23,7 +23,7 @@ private void ApplyAction_Click(object sender, System.Windows.RoutedEventArgs e) var selectedActor = TargetService.Instance.SelectedActor; if (selectedActor != null) { - ActorActionsService.Instance.SetActorAction(selectedActor, this.ActionType, this.ActionId); + ActorActionsService.Instance.SetActorAction(selectedActor, (ActorActionMemory.ActionTypes)this.ActionType, this.ActionId); } } } From 807597cffc33f1a339502f82a5f41bac3380d361 Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Fri, 24 Dec 2021 03:59:28 -0700 Subject: [PATCH 03/14] Remove test code --- Anamnesis/Services/ActorActionsService.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/Anamnesis/Services/ActorActionsService.cs b/Anamnesis/Services/ActorActionsService.cs index fec631d1e..62e1d2cbf 100644 --- a/Anamnesis/Services/ActorActionsService.cs +++ b/Anamnesis/Services/ActorActionsService.cs @@ -39,19 +39,10 @@ public void SetActorAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes ActorActionMemory? toSet = null; var table = this.GetTable(); - foreach (var entry in table.Take(5)) - { - toSet = entry; - toSet.ActorPtr = actor.Address; - toSet.ActorObjectId = actor.ObjectId; - toSet.ActionType = actionType; - toSet.ActionId = actionId; - } - // First search for an existing entry foreach (var entry in table) { - if (entry.ActorPtr == actor.Address || entry.ActorObjectId == actor.ObjectId) + if (entry.ActorPtr == actor.Address && entry.ActorObjectId == actor.ObjectId) { toSet = entry; break; @@ -63,7 +54,7 @@ public void SetActorAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes // Next we search for an empty entry foreach (var entry in table) { - if (entry.ActorPtr == IntPtr.Zero || entry.ActorObjectId == 0xE) + if (entry.ActorPtr == IntPtr.Zero) { toSet = entry; break; From 0887648385cade9df1b3b8484de48ed98145afbd Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Fri, 24 Dec 2021 04:06:26 -0700 Subject: [PATCH 04/14] Fix up empty check --- Anamnesis/Services/ActorActionsService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Anamnesis/Services/ActorActionsService.cs b/Anamnesis/Services/ActorActionsService.cs index 62e1d2cbf..c77ed79f5 100644 --- a/Anamnesis/Services/ActorActionsService.cs +++ b/Anamnesis/Services/ActorActionsService.cs @@ -54,7 +54,7 @@ public void SetActorAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes // Next we search for an empty entry foreach (var entry in table) { - if (entry.ActorPtr == IntPtr.Zero) + if (entry.ActorPtr == IntPtr.Zero && entry.ActorObjectId == 0xE0000000) { toSet = entry; break; From 4d05d731ec02fca7a2e9c18290328dbd3a841daa Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Fri, 24 Dec 2021 05:49:33 -0700 Subject: [PATCH 05/14] Some cleanup --- Anamnesis/Services/ActorActionsService.cs | 91 +++++++++++++++-------- Anamnesis/Views/ActorActionView.xaml.cs | 2 +- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/Anamnesis/Services/ActorActionsService.cs b/Anamnesis/Services/ActorActionsService.cs index c77ed79f5..e4c295f78 100644 --- a/Anamnesis/Services/ActorActionsService.cs +++ b/Anamnesis/Services/ActorActionsService.cs @@ -12,64 +12,93 @@ namespace Anamnesis.Services public class ActorActionsService : ServiceBase { - public override Task Start() - { - return base.Start(); - } + private IEnumerable? actionTable = null; - public IEnumerable GetTable() + public override async Task Start() { - IntPtr tableStart = MemoryService.ReadPtr(AddressService.ActorActionTable) + 0x5B0; - - List result = new(); - for(int i = 0; i < ActorActionMemory.EntriesInActionTable; ++i) - { - IntPtr address = tableStart + (i * ActorActionMemory.ActionMemoryLength); - ActorActionMemory actorActionMemory = new(); - actorActionMemory.SetAddress(address); - - result.Add(actorActionMemory); - } - - return result; + await base.Start(); } - public void SetActorAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes actionType, ushort actionId) + public bool SetAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes actionType, ushort actionId) { - ActorActionMemory? toSet = null; - var table = this.GetTable(); + ActorActionMemory? targetAction = null; + + // Make sure we have the latest version + this.RefreshTable(); // First search for an existing entry - foreach (var entry in table) + foreach (var entry in this.actionTable!) { if (entry.ActorPtr == actor.Address && entry.ActorObjectId == actor.ObjectId) { - toSet = entry; + targetAction = entry; break; } } - if (toSet == null) + if (targetAction == null) { // Next we search for an empty entry - foreach (var entry in table) + foreach (var entry in this.actionTable!) { if (entry.ActorPtr == IntPtr.Zero && entry.ActorObjectId == 0xE0000000) { - toSet = entry; + targetAction = entry; break; } } } // If there is no space maybe we should write a random one? - if (toSet == null) + if (targetAction == null) + return false; + + // Update table entry + targetAction.ActorPtr = actor.Address; + targetAction.ActorObjectId = actor.ObjectId; + targetAction.ActionType = actionType; + targetAction.ActionId = actionId; + + return true; + } + + public IEnumerable GetTable(bool refresh = false) + { + if (refresh || this.actionTable == null) + this.RefreshTable(); + + return this.actionTable!; + } + + public void RefreshTable() + { + if (this.actionTable == null) + { + this.actionTable = this.ReadTable(); return; + } + + foreach (var action in this.actionTable) + { + action.Tick(); + } + } + + private IEnumerable ReadTable() + { + IntPtr tableStart = MemoryService.ReadPtr(AddressService.ActorActionTable) + 0x5B0; - toSet.ActorPtr = actor.Address; - toSet.ActorObjectId = actor.ObjectId; - toSet.ActionType = actionType; - toSet.ActionId = actionId; + List result = new(); + for(int i = 0; i < ActorActionMemory.EntriesInActionTable; ++i) + { + IntPtr address = tableStart + (i * ActorActionMemory.ActionMemoryLength); + ActorActionMemory actorActionMemory = new(); + actorActionMemory.SetAddress(address); + + result.Add(actorActionMemory); + } + + return result; } } } diff --git a/Anamnesis/Views/ActorActionView.xaml.cs b/Anamnesis/Views/ActorActionView.xaml.cs index ed6766751..29121f10f 100644 --- a/Anamnesis/Views/ActorActionView.xaml.cs +++ b/Anamnesis/Views/ActorActionView.xaml.cs @@ -23,7 +23,7 @@ private void ApplyAction_Click(object sender, System.Windows.RoutedEventArgs e) var selectedActor = TargetService.Instance.SelectedActor; if (selectedActor != null) { - ActorActionsService.Instance.SetActorAction(selectedActor, (ActorActionMemory.ActionTypes)this.ActionType, this.ActionId); + ActorActionsService.Instance.SetAction(selectedActor, (ActorActionMemory.ActionTypes)this.ActionType, this.ActionId); } } } From 0b0fe1017228ffb8e4840a96f8e0a89e21990f4a Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Fri, 24 Dec 2021 05:54:24 -0700 Subject: [PATCH 06/14] Tweaks --- Anamnesis/MainWindow.xaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Anamnesis/MainWindow.xaml b/Anamnesis/MainWindow.xaml index f659c5b7f..ce87e4e57 100644 --- a/Anamnesis/MainWindow.xaml +++ b/Anamnesis/MainWindow.xaml @@ -389,8 +389,8 @@ + Icon="Bicycle" + ToolTip="Animate" /> From 00f186d626ecbf164d0ab9a2679a0b34f610de20 Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Fri, 24 Dec 2021 06:29:03 -0700 Subject: [PATCH 07/14] Add gpose actor ref --- Anamnesis/Memory/ActorActionMemory.cs | 2 ++ Anamnesis/Views/ActorActionView.xaml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/Anamnesis/Memory/ActorActionMemory.cs b/Anamnesis/Memory/ActorActionMemory.cs index fb795b880..a1909b88e 100644 --- a/Anamnesis/Memory/ActorActionMemory.cs +++ b/Anamnesis/Memory/ActorActionMemory.cs @@ -12,6 +12,7 @@ public class ActorActionMemory : MemoryBase public enum ActionTypes : byte { + None = 0x0, Emote = 0x1, Action = 0x2, } @@ -20,5 +21,6 @@ public enum ActionTypes : byte [Bind(0x08)] public uint ActorObjectId { get; set; } [Bind(0x10)] public ActionTypes ActionType { get; set; } [Bind(0x14)] public ushort ActionId { get; set; } + [Bind(0x70)] public IntPtr GPoseActorPtr { get; set; } } } diff --git a/Anamnesis/Views/ActorActionView.xaml b/Anamnesis/Views/ActorActionView.xaml index b4f6c8763..de6f9cda9 100644 --- a/Anamnesis/Views/ActorActionView.xaml +++ b/Anamnesis/Views/ActorActionView.xaml @@ -20,7 +20,7 @@ Type - None + None Emote Action From 837e734617b35f314f68e6bd202048d16a99c83e Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Fri, 24 Dec 2021 07:02:53 -0700 Subject: [PATCH 08/14] Improve gpose behavior --- Anamnesis/Services/ActorActionsService.cs | 54 ++++++++++++++--------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/Anamnesis/Services/ActorActionsService.cs b/Anamnesis/Services/ActorActionsService.cs index e4c295f78..32cb55817 100644 --- a/Anamnesis/Services/ActorActionsService.cs +++ b/Anamnesis/Services/ActorActionsService.cs @@ -3,14 +3,14 @@ namespace Anamnesis.Services { - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Anamnesis.Core.Memory; - using Anamnesis.Memory; - - public class ActorActionsService : ServiceBase + using System; + using System.Collections.Generic; + using System.Linq; + using System.Threading.Tasks; + using Anamnesis.Core.Memory; + using Anamnesis.Memory; + + public class ActorActionsService : ServiceBase { private IEnumerable? actionTable = null; @@ -26,24 +26,19 @@ public bool SetAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes acti // Make sure we have the latest version this.RefreshTable(); - // First search for an existing entry - foreach (var entry in this.actionTable!) - { - if (entry.ActorPtr == actor.Address && entry.ActorObjectId == actor.ObjectId) - { - targetAction = entry; - break; - } - } + // First search for an existing entry in both overworld and gpose + targetAction = this.GetAction(actor); - if (targetAction == null) + // If no match and not in gpose we search for a blank entry (you can't add a new entry during gpose as we don't have a handle to the overworld actor anymore) + if (targetAction == null && !GposeService.Instance.IsGpose) { - // Next we search for an empty entry foreach (var entry in this.actionTable!) { if (entry.ActorPtr == IntPtr.Zero && entry.ActorObjectId == 0xE0000000) { targetAction = entry; + targetAction.ActorPtr = actor.Address; + targetAction.ActorObjectId = actor.ObjectId; break; } } @@ -54,14 +49,29 @@ public bool SetAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes acti return false; // Update table entry - targetAction.ActorPtr = actor.Address; - targetAction.ActorObjectId = actor.ObjectId; targetAction.ActionType = actionType; targetAction.ActionId = actionId; return true; } + public ActorActionMemory? GetAction(ActorBasicMemory actor, bool searchOverworld = true, bool searchGPose = true, bool refresh = false) + { + if (refresh || this.actionTable == null) + this.RefreshTable(); + + foreach (var entry in this.actionTable!) + { + if ((searchOverworld && (entry.ActorPtr == actor.Address && entry.ActorObjectId == actor.ObjectId)) + || (searchGPose && (entry.GPoseActorPtr == actor.Address))) + { + return entry; + } + } + + return null; + } + public IEnumerable GetTable(bool refresh = false) { if (refresh || this.actionTable == null) @@ -89,7 +99,7 @@ private IEnumerable ReadTable() IntPtr tableStart = MemoryService.ReadPtr(AddressService.ActorActionTable) + 0x5B0; List result = new(); - for(int i = 0; i < ActorActionMemory.EntriesInActionTable; ++i) + for (int i = 0; i < ActorActionMemory.EntriesInActionTable; ++i) { IntPtr address = tableStart + (i * ActorActionMemory.ActionMemoryLength); ActorActionMemory actorActionMemory = new(); From a562f78c78c909304eed7f2a4a712fb82e1ed952 Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Sat, 25 Dec 2021 02:30:49 -0700 Subject: [PATCH 09/14] Fix up some ability issues --- Anamnesis/Memory/ActorActionMemory.cs | 6 ++++-- Anamnesis/Services/ActorActionsService.cs | 8 +++++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Anamnesis/Memory/ActorActionMemory.cs b/Anamnesis/Memory/ActorActionMemory.cs index a1909b88e..76096a3de 100644 --- a/Anamnesis/Memory/ActorActionMemory.cs +++ b/Anamnesis/Memory/ActorActionMemory.cs @@ -10,7 +10,7 @@ public class ActorActionMemory : MemoryBase public static readonly int ActionMemoryLength = 0xA0; public static readonly int EntriesInActionTable = 0x40; - public enum ActionTypes : byte + public enum ActionTypes : uint { None = 0x0, Emote = 0x1, @@ -20,7 +20,9 @@ public enum ActionTypes : byte [Bind(0x00)] public IntPtr ActorPtr { get; set; } [Bind(0x08)] public uint ActorObjectId { get; set; } [Bind(0x10)] public ActionTypes ActionType { get; set; } - [Bind(0x14)] public ushort ActionId { get; set; } + [Bind(0x14)] public uint ActionId { get; set; } + [Bind(0x40)] public uint SubActionType { get; set; } + [Bind(0x44)] public uint SubActionId { get; set; } [Bind(0x70)] public IntPtr GPoseActorPtr { get; set; } } } diff --git a/Anamnesis/Services/ActorActionsService.cs b/Anamnesis/Services/ActorActionsService.cs index 32cb55817..e9ad68d83 100644 --- a/Anamnesis/Services/ActorActionsService.cs +++ b/Anamnesis/Services/ActorActionsService.cs @@ -19,7 +19,7 @@ public override async Task Start() await base.Start(); } - public bool SetAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes actionType, ushort actionId) + public bool SetAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes actionType, uint actionId) { ActorActionMemory? targetAction = null; @@ -52,6 +52,12 @@ public bool SetAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes acti targetAction.ActionType = actionType; targetAction.ActionId = actionId; + if(actionType == ActorActionMemory.ActionTypes.Action) + { + targetAction.SubActionType = 1; + targetAction.SubActionId = actionId; + } + return true; } From 4c33e1ba2999c7509f6eb4132d60e5b1c7263491 Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Sat, 25 Dec 2021 02:45:19 -0700 Subject: [PATCH 10/14] Note --- Anamnesis/Services/ActorActionsService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Anamnesis/Services/ActorActionsService.cs b/Anamnesis/Services/ActorActionsService.cs index e9ad68d83..c71a7fc09 100644 --- a/Anamnesis/Services/ActorActionsService.cs +++ b/Anamnesis/Services/ActorActionsService.cs @@ -52,6 +52,7 @@ public bool SetAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes acti targetAction.ActionType = actionType; targetAction.ActionId = actionId; + // This is pretty hacky, but when using an action it always seems to set this. Would be nice to understand what this does a little more if(actionType == ActorActionMemory.ActionTypes.Action) { targetAction.SubActionType = 1; From 34e8493c791bb44630e2311f89eea8d1ba8530ca Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Sat, 25 Dec 2021 23:51:51 -0700 Subject: [PATCH 11/14] Improved CMTool animation method --- Anamnesis/Core/Memory/AddressService.cs | 4 +- Anamnesis/MainWindow.xaml | 2 +- Anamnesis/Memory/ActorActionMemory.cs | 28 ---- Anamnesis/Memory/ActorBasicMemory.cs | 3 + Anamnesis/ServiceManager.cs | 2 +- Anamnesis/Services/ActorActionsService.cs | 121 ---------------- Anamnesis/Services/AnimationService.cs | 131 ++++++++++++++++++ Anamnesis/Views/ActorActionView.xaml.cs | 30 ---- ...ctionView.xaml => ActorAnimationView.xaml} | 18 +-- Anamnesis/Views/ActorAnimationView.xaml.cs | 39 ++++++ 10 files changed, 184 insertions(+), 194 deletions(-) delete mode 100644 Anamnesis/Memory/ActorActionMemory.cs delete mode 100644 Anamnesis/Services/ActorActionsService.cs create mode 100644 Anamnesis/Services/AnimationService.cs delete mode 100644 Anamnesis/Views/ActorActionView.xaml.cs rename Anamnesis/Views/{ActorActionView.xaml => ActorAnimationView.xaml} (57%) create mode 100644 Anamnesis/Views/ActorAnimationView.xaml.cs diff --git a/Anamnesis/Core/Memory/AddressService.cs b/Anamnesis/Core/Memory/AddressService.cs index 03a68e3af..e66f86b64 100644 --- a/Anamnesis/Core/Memory/AddressService.cs +++ b/Anamnesis/Core/Memory/AddressService.cs @@ -36,7 +36,7 @@ public class AddressService : ServiceBase public static IntPtr TimeAsm { get; private set; } public static IntPtr TimeReal { get; set; } public static IntPtr PlayerTargetSystem { get; set; } - public static IntPtr ActorActionTable { get; set; } + public static IntPtr AnimationPatch { get; set; } public static IntPtr Camera { @@ -123,6 +123,7 @@ public static async Task Scan() tasks.Add(GetAddressFromTextSignature("SkeletonFreezePosition", "41 0F 29 24 12", (p) => { SkeletonFreezePosition = p; })); // SkeletonAddress5 tasks.Add(GetAddressFromTextSignature("SkeletonFreezeScale2", "43 0F 29 44 18 20", (p) => { SkeletonFreezeScale2 = p; })); // SkeletonAddress6 tasks.Add(GetAddressFromTextSignature("SkeletonFreezePosition2", "43 0f 29 24 18", (p) => { SkeletonFreezePosition2 = p; })); // SkeletonAddress7 + tasks.Add(GetAddressFromTextSignature("AnimationPatch", "66 89 8B D0 00 00 00 48 8B 43 60 48 85 C0", (p) => { AnimationPatch = p; })); tasks.Add(GetAddressFromSignature("Territory", "8B 1D ?? ?? ?? ?? 0F 45 D8 39 1D", 2, (p) => { Territory = p; })); tasks.Add(GetAddressFromSignature("Weather", "49 8B 9D ?? ?? ?? ?? 48 8D 0D", 0, (p) => { Weather = p + 0x8; })); tasks.Add(GetAddressFromSignature("GPoseFilters", "4C 8B 05 ?? ?? ?? ?? 41 8B 80 ?? ?? ?? ?? C1 E8 02", 0, (p) => { GPoseFilters = p; })); @@ -132,7 +133,6 @@ public static async Task Scan() tasks.Add(GetAddressFromSignature("GPose", "48 39 0D ?? ?? ?? ?? 75 28", 0, (p) => { GPose = p + 0x20; })); tasks.Add(GetAddressFromSignature("Camera", "48 8D 35 ?? ?? ?? ?? 48 8B 09", 0, (p) => { cameraManager = p; })); // CameraAddress tasks.Add(GetAddressFromSignature("PlayerTargetSystem", "48 8B 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? FF 50 ?? 48 85 DB", 0, (p) => { PlayerTargetSystem = p; })); - tasks.Add(GetAddressFromSignature("ActorActionTable", "48 8B 0D ?? ?? ?? ?? 41 B1 0A", 0, (p) => { ActorActionTable = p; })); tasks.Add(GetAddressFromTextSignature( "TimeAsm", diff --git a/Anamnesis/MainWindow.xaml b/Anamnesis/MainWindow.xaml index ce87e4e57..9213f56a1 100644 --- a/Anamnesis/MainWindow.xaml +++ b/Anamnesis/MainWindow.xaml @@ -393,7 +393,7 @@ ToolTip="Animate" /> - + diff --git a/Anamnesis/Memory/ActorActionMemory.cs b/Anamnesis/Memory/ActorActionMemory.cs deleted file mode 100644 index 76096a3de..000000000 --- a/Anamnesis/Memory/ActorActionMemory.cs +++ /dev/null @@ -1,28 +0,0 @@ -// © Anamnesis. -// Licensed under the MIT license. - -namespace Anamnesis.Memory -{ - using System; - - public class ActorActionMemory : MemoryBase - { - public static readonly int ActionMemoryLength = 0xA0; - public static readonly int EntriesInActionTable = 0x40; - - public enum ActionTypes : uint - { - None = 0x0, - Emote = 0x1, - Action = 0x2, - } - - [Bind(0x00)] public IntPtr ActorPtr { get; set; } - [Bind(0x08)] public uint ActorObjectId { get; set; } - [Bind(0x10)] public ActionTypes ActionType { get; set; } - [Bind(0x14)] public uint ActionId { get; set; } - [Bind(0x40)] public uint SubActionType { get; set; } - [Bind(0x44)] public uint SubActionId { get; set; } - [Bind(0x70)] public IntPtr GPoseActorPtr { get; set; } - } -} diff --git a/Anamnesis/Memory/ActorBasicMemory.cs b/Anamnesis/Memory/ActorBasicMemory.cs index da4e7a61f..2ebe5c103 100644 --- a/Anamnesis/Memory/ActorBasicMemory.cs +++ b/Anamnesis/Memory/ActorBasicMemory.cs @@ -22,6 +22,9 @@ public class ActorBasicMemory : MemoryBase [Bind(0x08c, BindFlags.ActorRefresh)] public ActorTypes ObjectKind { get; set; } [Bind(0x090)] public byte DistanceFromPlayerX { get; set; } [Bind(0x092)] public byte DistanceFromPlayerY { get; set; } + [Bind(0x0F30)] public uint TargetAnimation { get; set; } + [Bind(0x0F4C)] public uint NextAnimation { get; set; } + public uint DesiredAnimation { get; set; } public string Id => $"n{this.NameHash}_d{this.DataId}_o{this.Address}"; public string IdNoAddress => $"n{this.NameHash}_d{this.DataId}"; diff --git a/Anamnesis/ServiceManager.cs b/Anamnesis/ServiceManager.cs index ba7e87fdf..bdbfa2348 100644 --- a/Anamnesis/ServiceManager.cs +++ b/Anamnesis/ServiceManager.cs @@ -67,7 +67,7 @@ public async Task InitializeServices() await Add(); await Add(); await Add(); - await Add(); + await Add(); await Add(); IsInitialized = true; diff --git a/Anamnesis/Services/ActorActionsService.cs b/Anamnesis/Services/ActorActionsService.cs deleted file mode 100644 index c71a7fc09..000000000 --- a/Anamnesis/Services/ActorActionsService.cs +++ /dev/null @@ -1,121 +0,0 @@ -// © Anamnesis. -// Licensed under the MIT license. - -namespace Anamnesis.Services -{ - using System; - using System.Collections.Generic; - using System.Linq; - using System.Threading.Tasks; - using Anamnesis.Core.Memory; - using Anamnesis.Memory; - - public class ActorActionsService : ServiceBase - { - private IEnumerable? actionTable = null; - - public override async Task Start() - { - await base.Start(); - } - - public bool SetAction(ActorBasicMemory actor, ActorActionMemory.ActionTypes actionType, uint actionId) - { - ActorActionMemory? targetAction = null; - - // Make sure we have the latest version - this.RefreshTable(); - - // First search for an existing entry in both overworld and gpose - targetAction = this.GetAction(actor); - - // If no match and not in gpose we search for a blank entry (you can't add a new entry during gpose as we don't have a handle to the overworld actor anymore) - if (targetAction == null && !GposeService.Instance.IsGpose) - { - foreach (var entry in this.actionTable!) - { - if (entry.ActorPtr == IntPtr.Zero && entry.ActorObjectId == 0xE0000000) - { - targetAction = entry; - targetAction.ActorPtr = actor.Address; - targetAction.ActorObjectId = actor.ObjectId; - break; - } - } - } - - // If there is no space maybe we should write a random one? - if (targetAction == null) - return false; - - // Update table entry - targetAction.ActionType = actionType; - targetAction.ActionId = actionId; - - // This is pretty hacky, but when using an action it always seems to set this. Would be nice to understand what this does a little more - if(actionType == ActorActionMemory.ActionTypes.Action) - { - targetAction.SubActionType = 1; - targetAction.SubActionId = actionId; - } - - return true; - } - - public ActorActionMemory? GetAction(ActorBasicMemory actor, bool searchOverworld = true, bool searchGPose = true, bool refresh = false) - { - if (refresh || this.actionTable == null) - this.RefreshTable(); - - foreach (var entry in this.actionTable!) - { - if ((searchOverworld && (entry.ActorPtr == actor.Address && entry.ActorObjectId == actor.ObjectId)) - || (searchGPose && (entry.GPoseActorPtr == actor.Address))) - { - return entry; - } - } - - return null; - } - - public IEnumerable GetTable(bool refresh = false) - { - if (refresh || this.actionTable == null) - this.RefreshTable(); - - return this.actionTable!; - } - - public void RefreshTable() - { - if (this.actionTable == null) - { - this.actionTable = this.ReadTable(); - return; - } - - foreach (var action in this.actionTable) - { - action.Tick(); - } - } - - private IEnumerable ReadTable() - { - IntPtr tableStart = MemoryService.ReadPtr(AddressService.ActorActionTable) + 0x5B0; - - List result = new(); - for (int i = 0; i < ActorActionMemory.EntriesInActionTable; ++i) - { - IntPtr address = tableStart + (i * ActorActionMemory.ActionMemoryLength); - ActorActionMemory actorActionMemory = new(); - actorActionMemory.SetAddress(address); - - result.Add(actorActionMemory); - } - - return result; - } - } -} diff --git a/Anamnesis/Services/AnimationService.cs b/Anamnesis/Services/AnimationService.cs new file mode 100644 index 000000000..37a8fd5cb --- /dev/null +++ b/Anamnesis/Services/AnimationService.cs @@ -0,0 +1,131 @@ +// © Anamnesis. +// Licensed under the MIT license. + +namespace Anamnesis.Services +{ + using System.Collections.Generic; + using System.Threading.Tasks; + using Anamnesis.Core.Memory; + using Anamnesis.Memory; + + public class AnimationService : ServiceBase + { + private readonly List animatingActors = new(); + private NopHookViewModel? nopHook; + + private bool isEnabled = false; + + public bool Enabled + { + get => this.isEnabled; + set + { + if(this.isEnabled != value) + { + this.SetOverride(value); + } + } + } + + public void AnimateActor(ActorBasicMemory actor, uint desiredAnimation) + { + if (!this.animatingActors.Contains(actor)) + this.animatingActors.Add(actor); + + actor.DesiredAnimation = desiredAnimation; + } + + public override Task Start() + { + this.nopHook = new NopHookViewModel(AddressService.AnimationPatch, 0x7); + Task.Run(this.CheckThread); + return base.Start(); + } + + public override async Task Shutdown() + { + this.Enabled = false; + + await base.Shutdown(); + } + + private async Task CheckThread() + { + while (this.IsAlive) + { + if(this.isEnabled) + { + if (!GposeService.Instance.IsGpose) + this.Enabled = false; // Should only run in gpose + + foreach(var actor in this.animatingActors) + { + this.TickActor(actor); + } + } + + await Task.Delay(100); + } + } + + private void TickActor(ActorBasicMemory actor) + { + // This allows you to stop animations + if(actor.DesiredAnimation == 0 && actor.TargetAnimation != 0) + { + actor.TargetAnimation = actor.DesiredAnimation; + return; + } + + // Determine if we need to reset the state + if(actor.TargetAnimation != actor.DesiredAnimation && actor.TargetAnimation != 0 && actor.TargetAnimation != 3) + { + actor.TargetAnimation = 0; + return; + } + + // Once reset, we can go into idle + if(actor.TargetAnimation == 0 && actor.NextAnimation == 0) + { + actor.TargetAnimation = 3; + return; + } + + // We wait until we are idling + if (actor.TargetAnimation == 3 && actor.NextAnimation != 3) + return; + + // Set the target animation + if(actor.TargetAnimation != actor.DesiredAnimation) + { + actor.TargetAnimation = actor.DesiredAnimation; + return; + } + + // Once the target animation is queued, queue idle pose so our anim starts + if(actor.NextAnimation == actor.DesiredAnimation) + { + actor.TargetAnimation = 3; + return; + } + } + + private void SetOverride(bool enabled) + { + if (this.isEnabled == enabled) + return; + + this.isEnabled = enabled; + + if(enabled) + { + this.nopHook?.SetEnabled(true); + } + else + { + this.nopHook?.SetEnabled(false); + this.animatingActors.Clear(); + } + } + } +} diff --git a/Anamnesis/Views/ActorActionView.xaml.cs b/Anamnesis/Views/ActorActionView.xaml.cs deleted file mode 100644 index 29121f10f..000000000 --- a/Anamnesis/Views/ActorActionView.xaml.cs +++ /dev/null @@ -1,30 +0,0 @@ -// © Anamnesis. -// Licensed under the MIT license. - -namespace Anamnesis.Views -{ - using System.Windows.Controls; - using Anamnesis.Memory; - using Anamnesis.Services; - - public partial class ActorActionView : UserControl - { - public ActorActionView() - { - this.DataContext = this; - this.InitializeComponent(); - } - - public int ActionType { get; set; } = (int)ActorActionMemory.ActionTypes.Emote; - public ushort ActionId { get; set; } = 232; - - private void ApplyAction_Click(object sender, System.Windows.RoutedEventArgs e) - { - var selectedActor = TargetService.Instance.SelectedActor; - if (selectedActor != null) - { - ActorActionsService.Instance.SetAction(selectedActor, (ActorActionMemory.ActionTypes)this.ActionType, this.ActionId); - } - } - } -} diff --git a/Anamnesis/Views/ActorActionView.xaml b/Anamnesis/Views/ActorAnimationView.xaml similarity index 57% rename from Anamnesis/Views/ActorActionView.xaml rename to Anamnesis/Views/ActorAnimationView.xaml index de6f9cda9..37ed833db 100644 --- a/Anamnesis/Views/ActorActionView.xaml +++ b/Anamnesis/Views/ActorAnimationView.xaml @@ -1,4 +1,4 @@ - - Type - - None - Emote - Action - + ID + - ID - - - + + + + diff --git a/Anamnesis/Views/ActorAnimationView.xaml.cs b/Anamnesis/Views/ActorAnimationView.xaml.cs new file mode 100644 index 000000000..1420bae00 --- /dev/null +++ b/Anamnesis/Views/ActorAnimationView.xaml.cs @@ -0,0 +1,39 @@ +// © Anamnesis. +// Licensed under the MIT license. + +namespace Anamnesis.Views +{ + using System.Windows.Controls; + using Anamnesis.Memory; + using Anamnesis.Services; + + public partial class ActorAnimationView : UserControl + { + public ActorAnimationView() + { + this.DataContext = this; + this.InitializeComponent(); + } + + public ushort AnimationId { get; set; } = 8376; + + private void ApplyAction_Click(object sender, System.Windows.RoutedEventArgs e) + { + var selectedActor = TargetService.Instance.SelectedActor; + if (selectedActor != null) + { + AnimationService.Instance.AnimateActor(selectedActor, this.AnimationId); + } + } + + private void DisableAction_Click(object sender, System.Windows.RoutedEventArgs e) + { + AnimationService.Instance.Enabled = false; + } + + private void EnableAction_Click(object sender, System.Windows.RoutedEventArgs e) + { + AnimationService.Instance.Enabled = true; + } + } +} \ No newline at end of file From f7dd461219b239f510d7087f5e2ba81fc704c2c1 Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Sun, 26 Dec 2021 01:57:32 -0700 Subject: [PATCH 12/14] Many tweaks and fixes --- Anamnesis/Memory/ActorBasicMemory.cs | 3 - Anamnesis/Memory/ActorMemory.cs | 2 + Anamnesis/Services/AnimationService.cs | 159 ++++++++++++++------- Anamnesis/Views/ActorAnimationView.xaml | 36 ++++- Anamnesis/Views/ActorAnimationView.xaml.cs | 35 ++++- 5 files changed, 166 insertions(+), 69 deletions(-) diff --git a/Anamnesis/Memory/ActorBasicMemory.cs b/Anamnesis/Memory/ActorBasicMemory.cs index 2ebe5c103..da4e7a61f 100644 --- a/Anamnesis/Memory/ActorBasicMemory.cs +++ b/Anamnesis/Memory/ActorBasicMemory.cs @@ -22,9 +22,6 @@ public class ActorBasicMemory : MemoryBase [Bind(0x08c, BindFlags.ActorRefresh)] public ActorTypes ObjectKind { get; set; } [Bind(0x090)] public byte DistanceFromPlayerX { get; set; } [Bind(0x092)] public byte DistanceFromPlayerY { get; set; } - [Bind(0x0F30)] public uint TargetAnimation { get; set; } - [Bind(0x0F4C)] public uint NextAnimation { get; set; } - public uint DesiredAnimation { get; set; } public string Id => $"n{this.NameHash}_d{this.DataId}_o{this.Address}"; public string IdNoAddress => $"n{this.NameHash}_d{this.DataId}"; diff --git a/Anamnesis/Memory/ActorMemory.cs b/Anamnesis/Memory/ActorMemory.cs index 45905e228..20bc25c2c 100644 --- a/Anamnesis/Memory/ActorMemory.cs +++ b/Anamnesis/Memory/ActorMemory.cs @@ -33,6 +33,8 @@ public enum RenderModes : int [Bind(0x0CE0)] public WeaponMemory? OffHand { get; set; } [Bind(0x0DB0)] public ActorEquipmentMemory? Equipment { get; set; } [Bind(0x0DD8)] public ActorCustomizeMemory? Customize { get; set; } + [Bind(0x0F30)] public uint TargetAnimation { get; set; } + [Bind(0x0F4C)] public uint NextAnimation { get; set; } [Bind(0x18B8)] public float Transparency { get; set; } public bool AutomaticRefreshEnabled { get; set; } = true; diff --git a/Anamnesis/Services/AnimationService.cs b/Anamnesis/Services/AnimationService.cs index 37a8fd5cb..30b984722 100644 --- a/Anamnesis/Services/AnimationService.cs +++ b/Anamnesis/Services/AnimationService.cs @@ -3,14 +3,17 @@ namespace Anamnesis.Services { - using System.Collections.Generic; - using System.Threading.Tasks; - using Anamnesis.Core.Memory; - using Anamnesis.Memory; - - public class AnimationService : ServiceBase + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + using Anamnesis.Core.Memory; + using Anamnesis.Memory; + using PropertyChanged; + + [AddINotifyPropertyChangedInterface] + public class AnimationService : ServiceBase { - private readonly List animatingActors = new(); + private readonly List animatingActors = new(); private NopHookViewModel? nopHook; private bool isEnabled = false; @@ -20,21 +23,13 @@ public bool Enabled get => this.isEnabled; set { - if(this.isEnabled != value) + if (this.isEnabled != value) { - this.SetOverride(value); + this.SetEnabled(value); } } } - public void AnimateActor(ActorBasicMemory actor, uint desiredAnimation) - { - if (!this.animatingActors.Contains(actor)) - this.animatingActors.Add(actor); - - actor.DesiredAnimation = desiredAnimation; - } - public override Task Start() { this.nopHook = new NopHookViewModel(AddressService.AnimationPatch, 0x7); @@ -42,6 +37,30 @@ public override Task Start() return base.Start(); } + public void AnimateActor(ActorMemory actor, uint desiredAnimation, int repeatAfter = 0) + { + this.ClearAnimation(actor); + + var animationEntry = new ActorAnimation() + { + Actor = actor, + AnimationId = desiredAnimation, + RepeatAfter = repeatAfter, + }; + + this.animatingActors.Add(animationEntry); + } + + public void ClearAnimation(ActorBasicMemory actor) + { + this.animatingActors.RemoveAll(p => p.Actor == actor); + } + + public void ClearAll() + { + this.animatingActors.Clear(); + } + public override async Task Shutdown() { this.Enabled = false; @@ -53,12 +72,12 @@ private async Task CheckThread() { while (this.IsAlive) { - if(this.isEnabled) + if (this.isEnabled) { if (!GposeService.Instance.IsGpose) this.Enabled = false; // Should only run in gpose - foreach(var actor in this.animatingActors) + foreach (var actor in this.animatingActors) { this.TickActor(actor); } @@ -68,64 +87,96 @@ private async Task CheckThread() } } - private void TickActor(ActorBasicMemory actor) + private void TickActor(ActorAnimation animation) { - // This allows you to stop animations - if(actor.DesiredAnimation == 0 && actor.TargetAnimation != 0) + if (animation.Actor != null) { - actor.TargetAnimation = actor.DesiredAnimation; - return; - } + if (!animation.Actor.IsAnimating) + { + if(animation.State != ActorAnimation.ExecutionState.Begin) + animation.LastPlayed = DateTime.Now; - // Determine if we need to reset the state - if(actor.TargetAnimation != actor.DesiredAnimation && actor.TargetAnimation != 0 && actor.TargetAnimation != 3) - { - actor.TargetAnimation = 0; - return; - } + return; + } - // Once reset, we can go into idle - if(actor.TargetAnimation == 0 && actor.NextAnimation == 0) - { - actor.TargetAnimation = 3; - return; - } + if (animation.State == ActorAnimation.ExecutionState.Executed && animation.RepeatAfter == 0) + return; - // We wait until we are idling - if (actor.TargetAnimation == 3 && actor.NextAnimation != 3) - return; + if (animation.State == ActorAnimation.ExecutionState.Executed && animation.RepeatAfter != 0) + { + if (DateTime.Now > animation.LastPlayed.Add(TimeSpan.FromSeconds(animation.RepeatAfter))) + { + animation.State = ActorAnimation.ExecutionState.Begin; + } + } - // Set the target animation - if(actor.TargetAnimation != actor.DesiredAnimation) - { - actor.TargetAnimation = actor.DesiredAnimation; - return; - } + if (animation.State == ActorAnimation.ExecutionState.Executed) + return; - // Once the target animation is queued, queue idle pose so our anim starts - if(actor.NextAnimation == actor.DesiredAnimation) - { - actor.TargetAnimation = 3; - return; + animation.State = ActorAnimation.ExecutionState.Executing; + + // The flow below is a little confusing, but basically we need to ensure that the animation is reset and then we can play our custom animation. + // First we need to set TargetAnimation to 0 and wait for NextAnimation to tick over to 0 as well. + // Once that's done, we set TargetAnimation to the actual animation we want, and again wait for the engine to tick that into NextAnimation. + // Finally we set TargetAnimation to 0 again which will cause the engine to fire the now queued animation in NextAnimation. + // This takes a couple of frames to work through, which is why this requires a tick thread to drive all that through. + if (animation.Actor.TargetAnimation != animation.AnimationId && animation.Actor.TargetAnimation != 0) + { + animation.Actor.TargetAnimation = 0; + return; + } + + if (animation.Actor.TargetAnimation == 0 && animation.Actor.NextAnimation == 0) + { + animation.Actor.TargetAnimation = animation.AnimationId; + return; + } + + if (animation.Actor.TargetAnimation == animation.AnimationId && animation.Actor.NextAnimation == animation.AnimationId) + { + animation.Actor.TargetAnimation = 0; + animation.LastPlayed = DateTime.Now; + animation.State = ActorAnimation.ExecutionState.Executed; + return; + } } } - private void SetOverride(bool enabled) + private void SetEnabled(bool enabled) { if (this.isEnabled == enabled) return; + if (enabled && !GposeService.Instance.IsGpose) + return; + this.isEnabled = enabled; - if(enabled) + if (enabled) { this.nopHook?.SetEnabled(true); } else { + this.ClearAll(); this.nopHook?.SetEnabled(false); - this.animatingActors.Clear(); } } + + private class ActorAnimation + { + public enum ExecutionState + { + Begin, + Executing, + Executed, + } + + public ActorMemory? Actor { get; init; } + public uint AnimationId { get; init; } + public int RepeatAfter { get; init; } + public DateTime LastPlayed { get; set; } = new(); + public ExecutionState State { get; set; } = ExecutionState.Begin; + } } } diff --git a/Anamnesis/Views/ActorAnimationView.xaml b/Anamnesis/Views/ActorAnimationView.xaml index 37ed833db..db0cc1ad0 100644 --- a/Anamnesis/Views/ActorAnimationView.xaml +++ b/Anamnesis/Views/ActorAnimationView.xaml @@ -6,7 +6,7 @@ xmlns:local="clr-namespace:Anamnesis.Views" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800"> - + @@ -14,16 +14,38 @@ - + - ID - + + + + + + + + + + + + + + + + + + ID + + + Repeat After + + + - + + + - - diff --git a/Anamnesis/Views/ActorAnimationView.xaml.cs b/Anamnesis/Views/ActorAnimationView.xaml.cs index 1420bae00..cb5dcbffd 100644 --- a/Anamnesis/Views/ActorAnimationView.xaml.cs +++ b/Anamnesis/Views/ActorAnimationView.xaml.cs @@ -4,7 +4,6 @@ namespace Anamnesis.Views { using System.Windows.Controls; - using Anamnesis.Memory; using Anamnesis.Services; public partial class ActorAnimationView : UserControl @@ -12,28 +11,54 @@ public partial class ActorAnimationView : UserControl public ActorAnimationView() { this.DataContext = this; + this.AnimationService = AnimationService.Instance; + this.GPoseService = GposeService.Instance; this.InitializeComponent(); } - public ushort AnimationId { get; set; } = 8376; + public uint AnimationId { get; set; } = 8376; + public int RepeatTimer { get; set; } = 5; + + public AnimationService AnimationService { get; set; } + public GposeService GPoseService { get; set; } private void ApplyAction_Click(object sender, System.Windows.RoutedEventArgs e) + { + this.AnimationService = AnimationService.Instance; + + var selectedActor = TargetService.Instance.SelectedActor; + if (selectedActor != null) + { + this.AnimationService.AnimateActor(selectedActor, this.AnimationId, this.RepeatTimer); + } + } + + private void IdleAction_Click(object sender, System.Windows.RoutedEventArgs e) + { + var selectedActor = TargetService.Instance.SelectedActor; + if (selectedActor != null) + { + this.AnimationService.AnimateActor(selectedActor, 3, 0); + } + } + + private void DrawAction_Click(object sender, System.Windows.RoutedEventArgs e) { var selectedActor = TargetService.Instance.SelectedActor; if (selectedActor != null) { - AnimationService.Instance.AnimateActor(selectedActor, this.AnimationId); + this.AnimationService.AnimateActor(selectedActor, 190, 0); } } private void DisableAction_Click(object sender, System.Windows.RoutedEventArgs e) { - AnimationService.Instance.Enabled = false; + this.AnimationService.Enabled = false; } private void EnableAction_Click(object sender, System.Windows.RoutedEventArgs e) { - AnimationService.Instance.Enabled = true; + this.AnimationService.Enabled = true; } } } \ No newline at end of file From d6074a2e25e8a9e51c75c8c602dcd7b8d3a67568 Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Mon, 27 Dec 2021 16:31:36 -0700 Subject: [PATCH 13/14] Change defaults --- Anamnesis/Views/ActorAnimationView.xaml.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Anamnesis/Views/ActorAnimationView.xaml.cs b/Anamnesis/Views/ActorAnimationView.xaml.cs index cb5dcbffd..2496d3b35 100644 --- a/Anamnesis/Views/ActorAnimationView.xaml.cs +++ b/Anamnesis/Views/ActorAnimationView.xaml.cs @@ -16,8 +16,8 @@ public ActorAnimationView() this.InitializeComponent(); } - public uint AnimationId { get; set; } = 8376; - public int RepeatTimer { get; set; } = 5; + public uint AnimationId { get; set; } = 8047; + public int RepeatTimer { get; set; } = 0; public AnimationService AnimationService { get; set; } public GposeService GPoseService { get; set; } From 63b5e8b78587097787e4edc8f3d4d0dbd0ab623b Mon Sep 17 00:00:00 2001 From: Asgard <95163444+AsgardXIV@users.noreply.github.com> Date: Tue, 28 Dec 2021 19:02:54 -0700 Subject: [PATCH 14/14] Slow Motion Feature --- Anamnesis/Core/Memory/AddressService.cs | 2 ++ Anamnesis/Memory/ActorMemory.cs | 1 + Anamnesis/Services/AnimationService.cs | 19 +++++++++++++------ Anamnesis/Views/ActorAnimationView.xaml | 10 +++++++--- Anamnesis/Views/ActorAnimationView.xaml.cs | 7 ++++--- 5 files changed, 27 insertions(+), 12 deletions(-) diff --git a/Anamnesis/Core/Memory/AddressService.cs b/Anamnesis/Core/Memory/AddressService.cs index e66f86b64..d437444f9 100644 --- a/Anamnesis/Core/Memory/AddressService.cs +++ b/Anamnesis/Core/Memory/AddressService.cs @@ -37,6 +37,7 @@ public class AddressService : ServiceBase public static IntPtr TimeReal { get; set; } public static IntPtr PlayerTargetSystem { get; set; } public static IntPtr AnimationPatch { get; set; } + public static IntPtr SlowMotionPatch { get; set; } public static IntPtr Camera { @@ -124,6 +125,7 @@ public static async Task Scan() tasks.Add(GetAddressFromTextSignature("SkeletonFreezeScale2", "43 0F 29 44 18 20", (p) => { SkeletonFreezeScale2 = p; })); // SkeletonAddress6 tasks.Add(GetAddressFromTextSignature("SkeletonFreezePosition2", "43 0f 29 24 18", (p) => { SkeletonFreezePosition2 = p; })); // SkeletonAddress7 tasks.Add(GetAddressFromTextSignature("AnimationPatch", "66 89 8B D0 00 00 00 48 8B 43 60 48 85 C0", (p) => { AnimationPatch = p; })); + tasks.Add(GetAddressFromTextSignature("SlowMotionPatch", "F3 0F 11 94 ?? ?? ?? ?? ?? 80 89 ?? ?? ?? ?? 01", (p) => { SlowMotionPatch = p; })); tasks.Add(GetAddressFromSignature("Territory", "8B 1D ?? ?? ?? ?? 0F 45 D8 39 1D", 2, (p) => { Territory = p; })); tasks.Add(GetAddressFromSignature("Weather", "49 8B 9D ?? ?? ?? ?? 48 8D 0D", 0, (p) => { Weather = p + 0x8; })); tasks.Add(GetAddressFromSignature("GPoseFilters", "4C 8B 05 ?? ?? ?? ?? 41 8B 80 ?? ?? ?? ?? C1 E8 02", 0, (p) => { GPoseFilters = p; })); diff --git a/Anamnesis/Memory/ActorMemory.cs b/Anamnesis/Memory/ActorMemory.cs index 20bc25c2c..1dfd385bc 100644 --- a/Anamnesis/Memory/ActorMemory.cs +++ b/Anamnesis/Memory/ActorMemory.cs @@ -35,6 +35,7 @@ public enum RenderModes : int [Bind(0x0DD8)] public ActorCustomizeMemory? Customize { get; set; } [Bind(0x0F30)] public uint TargetAnimation { get; set; } [Bind(0x0F4C)] public uint NextAnimation { get; set; } + [Bind(0x0FA7)] public byte AnimationMode { get; set; } [Bind(0x18B8)] public float Transparency { get; set; } public bool AutomaticRefreshEnabled { get; set; } = true; diff --git a/Anamnesis/Services/AnimationService.cs b/Anamnesis/Services/AnimationService.cs index 30b984722..ad324e44f 100644 --- a/Anamnesis/Services/AnimationService.cs +++ b/Anamnesis/Services/AnimationService.cs @@ -14,7 +14,8 @@ namespace Anamnesis.Services public class AnimationService : ServiceBase { private readonly List animatingActors = new(); - private NopHookViewModel? nopHook; + private NopHookViewModel? animationNopHook; + private NopHookViewModel? slowMotionNopHook; private bool isEnabled = false; @@ -32,12 +33,13 @@ public bool Enabled public override Task Start() { - this.nopHook = new NopHookViewModel(AddressService.AnimationPatch, 0x7); + this.animationNopHook = new NopHookViewModel(AddressService.AnimationPatch, 0x7); + this.slowMotionNopHook = new NopHookViewModel(AddressService.SlowMotionPatch, 0x9); Task.Run(this.CheckThread); return base.Start(); } - public void AnimateActor(ActorMemory actor, uint desiredAnimation, int repeatAfter = 0) + public void AnimateActor(ActorMemory actor, uint desiredAnimation, bool slowMotion = false, int repeatAfter = 0) { this.ClearAnimation(actor); @@ -45,6 +47,7 @@ public void AnimateActor(ActorMemory actor, uint desiredAnimation, int repeatAft { Actor = actor, AnimationId = desiredAnimation, + SlowMotion = slowMotion, RepeatAfter = repeatAfter, }; @@ -122,13 +125,14 @@ private void TickActor(ActorAnimation animation) // This takes a couple of frames to work through, which is why this requires a tick thread to drive all that through. if (animation.Actor.TargetAnimation != animation.AnimationId && animation.Actor.TargetAnimation != 0) { - animation.Actor.TargetAnimation = 0; + animation.Actor.TargetAnimation = animation.AnimationId; return; } if (animation.Actor.TargetAnimation == 0 && animation.Actor.NextAnimation == 0) { animation.Actor.TargetAnimation = animation.AnimationId; + animation.Actor.AnimationMode = (byte)(animation.SlowMotion ? 0x3E : 0x3F); return; } @@ -154,12 +158,14 @@ private void SetEnabled(bool enabled) if (enabled) { - this.nopHook?.SetEnabled(true); + this.animationNopHook?.SetEnabled(true); + this.slowMotionNopHook?.SetEnabled(true); } else { this.ClearAll(); - this.nopHook?.SetEnabled(false); + this.animationNopHook?.SetEnabled(false); + this.slowMotionNopHook?.SetEnabled(false); } } @@ -174,6 +180,7 @@ public enum ExecutionState public ActorMemory? Actor { get; init; } public uint AnimationId { get; init; } + public bool SlowMotion { get; init; } public int RepeatAfter { get; init; } public DateTime LastPlayed { get; set; } = new(); public ExecutionState State { get; set; } = ExecutionState.Begin; diff --git a/Anamnesis/Views/ActorAnimationView.xaml b/Anamnesis/Views/ActorAnimationView.xaml index db0cc1ad0..8b2523a6b 100644 --- a/Anamnesis/Views/ActorAnimationView.xaml +++ b/Anamnesis/Views/ActorAnimationView.xaml @@ -31,6 +31,7 @@ + @@ -41,10 +42,13 @@ Repeat After - + Slow Motion + + + - - + + diff --git a/Anamnesis/Views/ActorAnimationView.xaml.cs b/Anamnesis/Views/ActorAnimationView.xaml.cs index 2496d3b35..0c1ae74f0 100644 --- a/Anamnesis/Views/ActorAnimationView.xaml.cs +++ b/Anamnesis/Views/ActorAnimationView.xaml.cs @@ -18,6 +18,7 @@ public ActorAnimationView() public uint AnimationId { get; set; } = 8047; public int RepeatTimer { get; set; } = 0; + public bool SlowMotion { get; set; } = false; public AnimationService AnimationService { get; set; } public GposeService GPoseService { get; set; } @@ -29,7 +30,7 @@ private void ApplyAction_Click(object sender, System.Windows.RoutedEventArgs e) var selectedActor = TargetService.Instance.SelectedActor; if (selectedActor != null) { - this.AnimationService.AnimateActor(selectedActor, this.AnimationId, this.RepeatTimer); + this.AnimationService.AnimateActor(selectedActor, this.AnimationId, this.SlowMotion, this.RepeatTimer); } } @@ -38,7 +39,7 @@ private void IdleAction_Click(object sender, System.Windows.RoutedEventArgs e) var selectedActor = TargetService.Instance.SelectedActor; if (selectedActor != null) { - this.AnimationService.AnimateActor(selectedActor, 3, 0); + this.AnimationService.AnimateActor(selectedActor, 3); } } @@ -47,7 +48,7 @@ private void DrawAction_Click(object sender, System.Windows.RoutedEventArgs e) var selectedActor = TargetService.Instance.SelectedActor; if (selectedActor != null) { - this.AnimationService.AnimateActor(selectedActor, 190, 0); + this.AnimationService.AnimateActor(selectedActor, 190); } }