Skip to content

Commit bc51f88

Browse files
committed
Penumbra Collection Support
1 parent 4db7300 commit bc51f88

16 files changed

+514
-85
lines changed

Brio/Brio.cs

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using Brio.Config;
22
using Brio.Game.Actor;
33
using Brio.Game.Chat;
4+
using Brio.Game.Core;
45
using Brio.Game.GPose;
56
using Brio.Game.Render;
7+
using Brio.IPC;
68
using Brio.UI;
79

810
namespace Brio;
@@ -16,22 +18,27 @@ public static class Brio
1618
public static GPoseService GPoseService { get; private set; } = null!;
1719
public static ActorSpawnService ActorSpawnService { get; private set; } = null!;
1820
public static ActorRedrawService ActorRedrawService { get; private set; } = null!;
19-
21+
public static PenumbraIPC PenumbraIPC { get; private set; } = null!;
2022
public static UIContainer UI { get; private set; } = null!;
2123
public static RenderHooks RenderHooks { get; set; } = null!;
24+
public static FrameworkUtils FrameworkUtils { get; set; } = null!;
25+
2226

23-
private static CommandHandler CommandHandler { get; set; } = null!;
27+
private static CommandHandler _commandHandler { get; set; } = null!;
2428

2529
public static void Initialize()
2630
{
2731
Configuration = Dalamud.PluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
2832

29-
CommandHandler = new();
33+
_commandHandler = new();
3034

3135
GPoseService = new GPoseService();
3236
ActorSpawnService = new ActorSpawnService();
3337
ActorRedrawService = new ActorRedrawService();
34-
RenderHooks= new RenderHooks();
38+
PenumbraIPC = new PenumbraIPC();
39+
RenderHooks = new RenderHooks();
40+
FrameworkUtils = new FrameworkUtils();
41+
3542

3643
UI = new UIContainer();
3744

@@ -59,10 +66,12 @@ public static void Destroy()
5966

6067
UI.Dispose();
6168

69+
FrameworkUtils.Dispose();
6270
RenderHooks.Dispose();
6371
GPoseService.Dispose();
72+
PenumbraIPC.Dispose();
6473
ActorSpawnService.Dispose();
6574
ActorRedrawService.Dispose();
66-
CommandHandler.Dispose();
75+
_commandHandler.Dispose();
6776
}
6877
}

Brio/Brio.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
<ItemGroup>
1919
<PackageReference Include="DalamudPackager" Version="2.1.10" />
20+
<PackageReference Include="Penumbra.Api" Version="1.0.6" />
2021
<Reference Include="FFXIVClientStructs">
2122
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
2223
<Private>false</Private>

Brio/Game/Actor/ActorRedrawService.cs

+50-26
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,71 @@
1-
using Dalamud.Game.ClientState.Objects.Enums;
1+
using Brio.Utils;
2+
using Dalamud.Game.ClientState.Objects.Types;
3+
using FFXIVClientStructs.FFXIV.Common.Math;
24
using System;
3-
using DalamudGameObject = Dalamud.Game.ClientState.Objects.Types.GameObject;
4-
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
55

66
namespace Brio.Game.Actor;
77

88
public class ActorRedrawService : IDisposable
99
{
10-
public unsafe void StandardRedraw(DalamudGameObject gameObject)
11-
{
12-
GameObject* raw = (GameObject*)gameObject.Address;
13-
raw->DisableDraw();
14-
raw->EnableDraw();
15-
}
10+
public bool CanRedraw { get; private set; } = true;
1611

17-
public unsafe void ModernNPCHackRedraw(DalamudGameObject gameObject)
12+
public unsafe void Redraw(GameObject gameObject, RedrawType redrawType, bool preservePosition = true)
1813
{
19-
var wasEnabled = Brio.RenderHooks.ApplyNPCOverride;
14+
CanRedraw = false;
2015

21-
Brio.RenderHooks.ApplyNPCOverride = true;
16+
var raw = gameObject.AsNative();
2217

23-
GameObject* raw = (GameObject*)gameObject.Address;
24-
raw->DisableDraw();
25-
raw->EnableDraw();
18+
var originalPositon = raw->DrawObject->Object.Position;
19+
var originalRotation = raw->DrawObject->Object.Rotation;
2620

27-
Brio.RenderHooks.ApplyNPCOverride = wasEnabled;
21+
var npcOverrideEnabled = Brio.RenderHooks.ApplyNPCOverride;
22+
if (redrawType == RedrawType.ForceNPCAppearance)
23+
Brio.RenderHooks.ApplyNPCOverride = true;
2824

29-
}
25+
switch(redrawType)
26+
{
27+
case RedrawType.ForceNPCAppearance:
28+
case RedrawType.Standard:
29+
raw->DisableDraw();
30+
raw->EnableDraw();
31+
break;
3032

31-
public unsafe void LegacyNPCHackRedraw(DalamudGameObject gameObject)
32-
{
33-
GameObject* raw = (GameObject*)gameObject.Address;
34-
if (raw->ObjectKind == (byte)ObjectKind.Player)
33+
case RedrawType.Penumbra:
34+
Brio.PenumbraIPC.RawPenumbraRefresh(raw->ObjectIndex);
35+
break;
36+
}
37+
38+
if (redrawType == RedrawType.ForceNPCAppearance)
39+
Brio.RenderHooks.ApplyNPCOverride = npcOverrideEnabled;
40+
41+
if (preservePosition)
3542
{
36-
raw->DisableDraw();
37-
raw->ObjectKind = (byte)ObjectKind.BattleNpc;
38-
raw->EnableDraw();
39-
raw->ObjectKind = (byte)ObjectKind.Player;
43+
Brio.FrameworkUtils.RunUntilSatisfied(() => raw->RenderFlags == 0,
44+
(_) =>
45+
{
46+
raw->DrawObject->Object.Rotation = originalRotation;
47+
raw->DrawObject->Object.Position = originalPositon;
48+
CanRedraw = true;
49+
},
50+
50);
4051
}
52+
else
53+
{
54+
CanRedraw = true;
55+
}
56+
4157
}
4258

59+
4360
public void Dispose()
4461
{
45-
62+
CanRedraw = true;
4663
}
4764
}
65+
66+
public enum RedrawType
67+
{
68+
Standard,
69+
ForceNPCAppearance,
70+
Penumbra
71+
}

Brio/Game/Actor/ActorSpawnService.cs

+11-8
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ namespace Brio.Game.Actor;
99
public class ActorSpawnService : IDisposable
1010
{
1111
private ClientObjectManager _clientObjectManager;
12-
private List<ushort> CreatedIndexes = new List<ushort>();
12+
private List<ushort> _createdIndexes = new();
1313

1414
public bool CanSpawn => Brio.GPoseService.IsInGPose;
1515

@@ -23,7 +23,7 @@ public ActorSpawnService()
2323

2424
private void ClientState_TerritoryChanged(object? sender, ushort e)
2525
{
26-
CreatedIndexes.Clear();
26+
_createdIndexes.Clear();
2727
}
2828

2929
private void GPoseService_OnGPoseStateChange(bool isInGpose)
@@ -40,7 +40,7 @@ private void GPoseService_OnGPoseStateChange(bool isInGpose)
4040
if (localPlayer == null)
4141
return null;
4242

43-
Character* originalPlayer = (Character*)localPlayer.Address;
43+
Character* originalPlayer = (Character*)localPlayer.AsNative();
4444
if(originalPlayer == null) return null;
4545

4646
uint idCheck = _clientObjectManager.CreateBattleCharacter();
@@ -50,25 +50,28 @@ private void GPoseService_OnGPoseStateChange(bool isInGpose)
5050
Character* newPlayer = (Character*) _clientObjectManager.GetObjectByIndex(newId);
5151
if (newPlayer == null) return null;
5252

53-
newPlayer->CopyFromCharacter(originalPlayer, 0);
53+
newPlayer->CopyFromCharacter(originalPlayer, 0); // We copy the Player as the created actor is just blank
54+
5455
*((sbyte*)newPlayer + 0x95) &= ~2; // Disable selection just incase this somehow leaks out of GPose
5556
newPlayer->GameObject.Position= originalPlayer->GameObject.Position;
56-
newPlayer->GameObject.SetName($"Brio {newId}");
57+
newPlayer->GameObject.SetName(((int)newId).ToCharacterName());
58+
59+
newPlayer->CopyFromCharacter(newPlayer, 0); // Some tools get confused (Like Penumbra) unless we copy onto ourselves after name change
5760

5861
newPlayer->GameObject.EnableDraw();
5962

60-
CreatedIndexes.Add(newId);
63+
_createdIndexes.Add(newId);
6164

6265
return newId;
6366
}
6467

6568
public unsafe void DestroyAllCreated()
6669
{
67-
foreach(var idx in CreatedIndexes)
70+
foreach(var idx in _createdIndexes)
6871
{
6972
_clientObjectManager.DeleteObjectByIndex(idx, 0);
7073
}
71-
CreatedIndexes.Clear();
74+
_createdIndexes.Clear();
7275
}
7376

7477
public unsafe void DestroyAll()

Brio/Game/Core/FrameworkUtils.cs

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
using Dalamud.Game;
2+
using Dalamud.Logging;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Runtime.CompilerServices;
6+
7+
namespace Brio.Game.Core;
8+
9+
public class FrameworkUtils : IDisposable
10+
{
11+
private List<DeferredTask> _deferredTasks = new();
12+
13+
public FrameworkUtils()
14+
{
15+
Dalamud.Framework.Update += Framework_Update;
16+
}
17+
18+
private void Framework_Update(Framework framework)
19+
{
20+
TickTasks();
21+
}
22+
23+
public void RunDeferred(
24+
Action<bool> action,
25+
int delay = 0,
26+
[CallerFilePath] string callerFile = "",
27+
[CallerLineNumber] int callerLine = 0,
28+
[CallerMemberName] string callerMember = ""
29+
)
30+
{
31+
var newTask = new DeferredTask()
32+
{
33+
ConditionAction = (task) => task.TickCount >= task.FrameTarget,
34+
CompleteAction = action,
35+
FrameTarget = delay,
36+
DebugPath = $"{callerFile}:{callerLine} - {callerMember}"
37+
};
38+
39+
_deferredTasks.Add(newTask);
40+
}
41+
42+
public void RunUntilSatisfied(
43+
Func<bool> condition,
44+
Action<bool> onSatisfied,
45+
int attempts = 0,
46+
[CallerFilePath] string callerFile = "",
47+
[CallerLineNumber] int callerLine = 0,
48+
[CallerMemberName] string callerMember = ""
49+
)
50+
{
51+
var newTask = new DeferredTask()
52+
{
53+
ConditionAction = (_) => condition.Invoke(),
54+
CompleteAction = onSatisfied.Invoke,
55+
FrameTarget = attempts,
56+
DebugPath = $"{callerFile}:{callerLine} - {callerMember}"
57+
};
58+
59+
_deferredTasks.Add(newTask);
60+
}
61+
62+
public void Dispose()
63+
{
64+
Dalamud.Framework.Update -= Framework_Update;
65+
_deferredTasks.Clear();
66+
}
67+
68+
private void TickTasks()
69+
{
70+
for(int i = 0; i < _deferredTasks.Count; i++)
71+
{
72+
var task = _deferredTasks[i];
73+
task.TickCount++;
74+
75+
var conditionSatisfied = CheckTask(task);
76+
77+
if(conditionSatisfied == true)
78+
{
79+
_deferredTasks.RemoveAt(i--);
80+
CompleteTask(task, true);
81+
}
82+
else if(conditionSatisfied == null || task.FrameTarget <= task.TickCount)
83+
{
84+
if (task.FrameTarget <= task.TickCount)
85+
PluginLog.Warning($"Task timed out. {task}");
86+
87+
_deferredTasks.RemoveAt(i--);
88+
CompleteTask(task, false);
89+
}
90+
}
91+
}
92+
93+
private bool? CheckTask(DeferredTask task)
94+
{
95+
try
96+
{
97+
return task.ConditionAction(task);
98+
}
99+
catch (Exception ex)
100+
{
101+
PluginLog.Warning(ex, $"Exception running condition action. {task}");
102+
return null;
103+
}
104+
}
105+
106+
private void CompleteTask(DeferredTask task, bool success)
107+
{
108+
try
109+
{
110+
task.CompleteAction.Invoke(success);
111+
} catch(Exception ex)
112+
{
113+
PluginLog.Warning(ex, $"Exception running completion action. {task}");
114+
}
115+
}
116+
}
117+
118+
class DeferredTask
119+
{
120+
public Func<DeferredTask, bool> ConditionAction { get; init; } = null!;
121+
public Action<bool> CompleteAction { get; init; } = null!;
122+
public string DebugPath { get; init; } = null!;
123+
public int FrameTarget { get; init; }
124+
public int TickCount { get; set; } = -1;
125+
126+
public override string ToString() => $"{DebugPath}. T: {FrameTarget} C: {TickCount}";
127+
}

Brio/Game/GPose/GPoseService.cs

+4-2
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ namespace Brio.Game.GPose;
66

77
public class GPoseService : IDisposable
88
{
9-
public bool IsInGPose => Dalamud.PluginInterface.UiBuilder.GposeActive;
9+
public bool IsInGPose => Dalamud.PluginInterface.UiBuilder.GposeActive || FakeGPose;
10+
public bool FakeGPose { get; set; } = false;
1011

1112
private bool _lastGPoseState;
1213

1314
public delegate void OnGPoseStateChangeDelegate(bool isInGpose);
1415
public event OnGPoseStateChangeDelegate? OnGPoseStateChange;
1516

17+
public const int GPoseActorCount = 39;
1618
private const int GPoseFirstActor = 201;
17-
private const int GPoseActorCount = 39;
19+
1820

1921
public GPoseService()
2022
{

0 commit comments

Comments
 (0)