Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Animation Overriding #733

Merged
merged 19 commits into from
Dec 29, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Anamnesis/Core/Memory/AddressService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class AddressService : ServiceBase<AddressService>
public static IntPtr TimeAsm { get; private set; }
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
{
Expand Down Expand Up @@ -122,6 +124,8 @@ 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(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; }));
Expand Down
10 changes: 10 additions & 0 deletions Anamnesis/MainWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,16 @@

<posePages:PosePage DataContext="{Binding TargetService.SelectedActor}" />
</TabItem>
<TabItem IsEnabled="{Binding TargetService.SelectedActor, Converter={StaticResource NotNullToBoolConverter}}">
<TabItem.Header>
<fa:IconImage
Foreground="{DynamicResource MaterialDesignBodyLight}"
Icon="Bicycle"
ToolTip="Animate" />
</TabItem.Header>

<views:ActorAnimationView />
</TabItem>
</TabControl>
</Grid>

Expand Down
3 changes: 3 additions & 0 deletions Anamnesis/Memory/ActorMemory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ 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(0x0FA7)] public byte AnimationMode { get; set; }
[Bind(0x18B8)] public float Transparency { get; set; }

public bool AutomaticRefreshEnabled { get; set; } = true;
Expand Down
1 change: 1 addition & 0 deletions Anamnesis/ServiceManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public async Task InitializeServices()
await Add<TipService>();
await Add<TexToolsService>();
await Add<FavoritesService>();
await Add<AnimationService>();
////await Add<AnamnesisConnectService>();

IsInitialized = true;
Expand Down
189 changes: 189 additions & 0 deletions Anamnesis/Services/AnimationService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// © Anamnesis.
// Licensed under the MIT license.

namespace Anamnesis.Services
{
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Anamnesis.Core.Memory;
using Anamnesis.Memory;
using PropertyChanged;

[AddINotifyPropertyChangedInterface]
public class AnimationService : ServiceBase<AnimationService>
{
private readonly List<ActorAnimation> animatingActors = new();
private NopHookViewModel? animationNopHook;
private NopHookViewModel? slowMotionNopHook;

private bool isEnabled = false;

public bool Enabled
{
get => this.isEnabled;
set
{
if (this.isEnabled != value)
{
this.SetEnabled(value);
}
}
}

public override Task Start()
{
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, bool slowMotion = false, int repeatAfter = 0)
{
this.ClearAnimation(actor);

var animationEntry = new ActorAnimation()
{
Actor = actor,
AnimationId = desiredAnimation,
SlowMotion = slowMotion,
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;

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(ActorAnimation animation)
{
if (animation.Actor != null)
{
if (!animation.Actor.IsAnimating)
{
if(animation.State != ActorAnimation.ExecutionState.Begin)
animation.LastPlayed = DateTime.Now;

return;
}

if (animation.State == ActorAnimation.ExecutionState.Executed && animation.RepeatAfter == 0)
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;
}
}

if (animation.State == ActorAnimation.ExecutionState.Executed)
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 = 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;
}

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 SetEnabled(bool enabled)
{
if (this.isEnabled == enabled)
return;

if (enabled && !GposeService.Instance.IsGpose)
return;

this.isEnabled = enabled;

if (enabled)
{
this.animationNopHook?.SetEnabled(true);
this.slowMotionNopHook?.SetEnabled(true);
}
else
{
this.ClearAll();
this.animationNopHook?.SetEnabled(false);
this.slowMotionNopHook?.SetEnabled(false);
}
}

private class ActorAnimation
{
public enum ExecutionState
{
Begin,
Executing,
Executed,
}

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;
}
}
}
55 changes: 55 additions & 0 deletions Anamnesis/Views/ActorAnimationView.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<UserControl x:Class="Anamnesis.Views.ActorAnimationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Anamnesis.Views"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<Grid Margin="100" IsEnabled="{Binding GPoseService.IsGpose}">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>

<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="25"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<Button Grid.Row="0" Grid.Column="0" Click="EnableAction_Click" IsEnabled="{Binding AnimationService.Enabled, Converter={StaticResource NotConverter}}">Enable</Button>
<Button Grid.Row="0" Grid.Column="1" Click="DisableAction_Click" IsEnabled="{Binding AnimationService.Enabled}">Disable</Button>

<Grid Grid.Row="2" Grid.ColumnSpan="2" IsEnabled="{Binding AnimationService.Enabled}">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>

<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="20"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>

<TextBlock Grid.Column="0" Grid.Row="0" HorizontalAlignment="Right">ID</TextBlock>
<TextBox Grid.Column="1" Grid.Row="0" Style="{StaticResource MaterialDesignTextBox}" Text="{Binding AnimationId}" />

<TextBlock Grid.Column="0" Grid.Row="1" HorizontalAlignment="Right">Repeat After</TextBlock>
<TextBox Grid.Column="1" Grid.Row="1" Style="{StaticResource MaterialDesignTextBox}" Text="{Binding RepeatTimer}" />

<TextBlock Grid.Column="0" Grid.Row="2" HorizontalAlignment="Right">Slow Motion</TextBlock>
<CheckBox Grid.Column="1" Grid.Row="2" IsChecked="{Binding SlowMotion}" />

<Button Grid.Row="3" Grid.Column="1" Click="ApplyAction_Click">Apply</Button>

<Button Grid.Row="5" Grid.Column="1" Click="IdleAction_Click">Reset</Button>
<Button Grid.Row="5" Grid.Column="0" Click="DrawAction_Click">Draw Weapon</Button>
</Grid>

</Grid>
</UserControl>
65 changes: 65 additions & 0 deletions Anamnesis/Views/ActorAnimationView.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// © Anamnesis.
// Licensed under the MIT license.

namespace Anamnesis.Views
{
using System.Windows.Controls;
using Anamnesis.Services;

public partial class ActorAnimationView : UserControl
{
public ActorAnimationView()
{
this.DataContext = this;
this.AnimationService = AnimationService.Instance;
this.GPoseService = GposeService.Instance;
this.InitializeComponent();
}

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; }

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.SlowMotion, 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);
}
}

private void DrawAction_Click(object sender, System.Windows.RoutedEventArgs e)
{
var selectedActor = TargetService.Instance.SelectedActor;
if (selectedActor != null)
{
this.AnimationService.AnimateActor(selectedActor, 190);
}
}

private void DisableAction_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.AnimationService.Enabled = false;
}

private void EnableAction_Click(object sender, System.Windows.RoutedEventArgs e)
{
this.AnimationService.Enabled = true;
}
}
}