Skip to content

Commit 5277128

Browse files
committed
Festival rework
1 parent 07f5be4 commit 5277128

File tree

5 files changed

+255
-121
lines changed

5 files changed

+255
-121
lines changed

Brio/Game/World/FestivalInterop.cs

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
using Dalamud.Utility.Signatures;
2+
using FFXIVClientStructs.FFXIV.Client.Game;
3+
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
4+
using System;
5+
using System.Runtime.InteropServices;
6+
7+
namespace Brio.Game.World;
8+
9+
// TODO: Move into ClientStructs
10+
// Tracking: https://github.com/aers/FFXIVClientStructs/pull/310 & https://github.com/aers/FFXIVClientStructs/pull/311
11+
12+
[StructLayout(LayoutKind.Explicit, Size = 0x98)]
13+
public unsafe struct LayoutManager
14+
{
15+
[FieldOffset(0x38)] public uint FestivalStatus; // SetActiveFestivals will not allow a change when not 5 or 0
16+
[FieldOffset(0x40)] public fixed uint ActiveFestivals[4];
17+
}
18+
19+
public unsafe class LayoutManagerInterop
20+
{
21+
private delegate void SetActiveFestivalsDelegate(LayoutManager* instance, uint* festivalArray);
22+
23+
[Signature("E8 ?? ?? ?? ?? 8B C5 EB 6A", ScanType = ScanType.Text)]
24+
private SetActiveFestivalsDelegate _setActiveFestivals = null!;
25+
26+
public LayoutManagerInterop()
27+
{
28+
SignatureHelper.Initialise(this);
29+
}
30+
31+
public unsafe void SetActiveFestivals(uint* festivalArray)
32+
{
33+
var world = LayoutWorld.Instance();
34+
if(world != null)
35+
{
36+
var manager = (LayoutManager*) world->ActiveLayout;
37+
if(manager != null)
38+
{
39+
_setActiveFestivals(manager, festivalArray);
40+
}
41+
}
42+
}
43+
44+
public uint[] GetActiveFestivals()
45+
{
46+
var result = new uint[4];
47+
48+
var world = LayoutWorld.Instance();
49+
if(world != null)
50+
{
51+
var manager = (LayoutManager*)world->ActiveLayout;
52+
if(manager != null)
53+
{
54+
for(int i = 0; i < 4; ++i)
55+
{
56+
result[i] = manager->ActiveFestivals[i];
57+
}
58+
}
59+
}
60+
61+
return result;
62+
}
63+
64+
public bool IsBusy
65+
{
66+
get
67+
{
68+
var world = LayoutWorld.Instance();
69+
if(world != null)
70+
{
71+
var manager = (LayoutManager*)world->ActiveLayout;
72+
if(manager != null)
73+
{
74+
return manager->FestivalStatus != 0 && manager->FestivalStatus != 5;
75+
}
76+
}
77+
78+
return true;
79+
}
80+
}
81+
}
82+
83+
public unsafe class GameMainInterop
84+
{
85+
private delegate void SetActiveFestivalsDelegate(GameMain* instance, uint festival1, uint festival2, uint festival3, uint festival4);
86+
87+
[Signature("E8 ?? ?? ?? ?? 80 63 50 FE", ScanType = ScanType.Text)]
88+
private SetActiveFestivalsDelegate _setActiveFestivals = null!;
89+
public void SetActiveFestivals(uint festival1, uint festival2, uint festival3, uint festival4) => _setActiveFestivals(GameMain.Instance(), festival1, festival2, festival3, festival4);
90+
91+
[Signature("E8 ?? ?? ?? ?? E9 08 29 00 00", ScanType = ScanType.Text)]
92+
private SetActiveFestivalsDelegate _queueActiveFestivals = null!;
93+
public void QueueActiveFestivals(uint festival1, uint festival2, uint festival3, uint festival4) => _queueActiveFestivals(GameMain.Instance(), festival1, festival2, festival3, festival4);
94+
95+
96+
public GameMainInterop()
97+
{
98+
SignatureHelper.Initialise(this);
99+
}
100+
}

Brio/Game/World/FestivalService.cs

+101-104
Original file line numberDiff line numberDiff line change
@@ -1,93 +1,141 @@
11
using Brio.Core;
22
using Brio.Game.GPose;
33
using Brio.Utils;
4-
using FFXIVClientStructs.FFXIV.Client.LayoutEngine;
54
using Lumina.Excel.GeneratedSheets;
65
using System.Collections.Generic;
76
using System.Collections.ObjectModel;
87
using System.IO;
98
using System.Linq;
109
using System.Numerics;
1110
using System.Reflection;
12-
using System.Runtime.InteropServices;
1311
using System.Text.Json;
1412

1513
namespace Brio.Game.World;
1614
public class FestivalService : ServiceBase<FestivalService>
1715
{
18-
// TODO: Submit the layout changes back to ClientStructs
16+
public const int MaxFestivals = 4;
1917

2018
public ReadOnlyCollection<FestivalEntry> FestivalEntries => new(_festivalEntries);
21-
public bool IsOverriden => _originalFestival != null;
19+
20+
public ReadOnlyCollection<FestivalEntry> ActiveFestivals
21+
{
22+
get
23+
{
24+
var result = new List<FestivalEntry>();
25+
var active = _festivalInterop.GetActiveFestivals();
26+
for(int idx = 0; idx < MaxFestivals; ++idx)
27+
{
28+
var entry = _festivalEntries.First(i => i.Id == active[idx]);
29+
if(entry.Id != 0)
30+
result.Add(entry);
31+
}
32+
return new(result);
33+
}
34+
}
35+
36+
public bool HasMoreSlots => _festivalInterop.GetActiveFestivals().Count(i => i == 0) > 0;
37+
public bool IsOverridden => _originalState != null;
2238

2339
private List<FestivalEntry> _festivalEntries = new();
2440

25-
private Queue<ushort> _queuedTransitions = new();
26-
private ushort? _originalFestival;
41+
private LayoutManagerInterop _festivalInterop = new();
42+
private GameMainInterop _gameMainInterop = new();
2743

28-
private unsafe delegate* unmanaged<LayoutManager*, FestivalArgs, void> _updateFestival;
2944

30-
public FestivalEntry CurrentFestival
45+
private Queue<uint[]?> _pendingChanges = new();
46+
private uint[]? _originalState;
47+
48+
public override void Start()
3149
{
32-
get
50+
UpdateFestivalList();
51+
GPoseService.Instance.OnGPoseStateChange += Instance_OnGPoseStateChange;
52+
Dalamud.ClientState.TerritoryChanged += ClientState_TerritoryChanged;
53+
base.Start();
54+
}
55+
56+
public bool AddFestival(uint festival)
57+
{
58+
if(!CheckFestivalRestrictions(festival))
59+
return false;
60+
61+
var active = _festivalInterop.GetActiveFestivals();
62+
var copy = active.ToArray();
63+
64+
for(int i = 0; i < MaxFestivals; ++i)
3365
{
34-
var currentId = CurrentFestivalId;
35-
return _festivalEntries.First(i => i.Id == currentId);
66+
if(active[i] == 0)
67+
{
68+
SnapshotFestivals();
69+
copy[i] = festival;
70+
_pendingChanges.Enqueue(copy);
71+
return true;
72+
}
3673
}
74+
75+
return false;
3776
}
3877

39-
public unsafe ushort CurrentFestivalId
78+
public bool RemoveFestival(uint festival)
4079
{
41-
get
80+
var active = _festivalInterop.GetActiveFestivals();
81+
var copy = active.ToArray();
82+
83+
for(int i = 0; i < MaxFestivals; ++i)
4284
{
43-
var layoutWorld = LayoutWorld.Instance();
44-
if(layoutWorld != null)
85+
if(active[i] == festival)
4586
{
46-
var layoutManager = layoutWorld->ActiveLayout;
87+
SnapshotFestivals();
88+
copy[i] = 0;
89+
_pendingChanges.Enqueue(copy);
90+
return true;
91+
}
92+
}
4793

48-
if(layoutManager != null)
49-
{
50-
return *(ushort*)(((nint)layoutManager) + 0x40);
51-
}
94+
return false;
95+
}
96+
97+
public unsafe void ResetFestivals(bool tryThisFrame = false)
98+
{
99+
if(_originalState != null)
100+
{
101+
if(tryThisFrame)
102+
{
103+
InternalApply(_originalState, false);
104+
}
105+
else
106+
{
107+
_pendingChanges.Enqueue(_originalState.ToArray());
52108
}
53109

54-
return 0;
110+
_originalState = null;
55111
}
56112
}
57113

58-
public unsafe FestivalService()
114+
public unsafe override void Tick()
59115
{
60-
var updateFestivalAddress = Dalamud.SigScanner.ScanText("E8 ?? ?? ?? ?? 8B C5 EB 6A");
61-
_updateFestival = (delegate* unmanaged<LayoutManager*, FestivalArgs, void>)updateFestivalAddress;
116+
if(_pendingChanges.Count > 0 && !_festivalInterop.IsBusy)
117+
{
118+
var pending = _pendingChanges.Dequeue();
119+
if(pending != null)
120+
InternalApply(pending);
121+
}
62122
}
63123

64-
public override void Start()
124+
private void SnapshotFestivals()
65125
{
66-
UpdateFestivalList();
67-
GPoseService.Instance.OnGPoseStateChange += Instance_OnGPoseStateChange;
68-
Dalamud.ClientState.TerritoryChanged += ClientState_TerritoryChanged;
69-
base.Start();
126+
if(_originalState == null)
127+
_originalState = _festivalInterop.GetActiveFestivals().ToArray();
70128
}
71129

72-
public unsafe override void Tick()
130+
private unsafe void InternalApply(uint[] festivals, bool applyNow = true)
73131
{
74-
if(_queuedTransitions.Count == 0)
75-
return;
76-
77-
var layoutWorld = LayoutWorld.Instance();
78-
if(layoutWorld != null )
132+
if(applyNow)
79133
{
80-
var layoutManager = layoutWorld->ActiveLayout;
81-
82-
if( layoutManager != null )
83-
{
84-
byte status = *(byte*)(((nint)layoutManager) + 0x38);
85-
if(status != 0 && status != 5)
86-
return;
87-
88-
var next = _queuedTransitions.Dequeue();
89-
SetFestivalImmediately(next);
90-
}
134+
_gameMainInterop.SetActiveFestivals(festivals[0], festivals[1], festivals[2], festivals[3]);
135+
}
136+
else
137+
{
138+
_gameMainInterop.QueueActiveFestivals(festivals[0], festivals[1], festivals[2], festivals[3]);
91139
}
92140
}
93141

@@ -137,47 +185,8 @@ private void UpdateFestivalList()
137185
}
138186
}
139187

140-
public void SetFestivalOverride(ushort festivalId)
141-
{
142-
if(!CheckFestivalRestrictions(festivalId))
143-
return;
144-
145-
if(_originalFestival == null)
146-
_originalFestival = CurrentFestivalId;
147-
148-
_queuedTransitions.Enqueue(festivalId);
149-
}
150-
151-
public void ResetFestivalOverride()
152-
{
153-
if(_originalFestival != null)
154-
{
155-
SetFestivalOverride((ushort) _originalFestival);
156-
_originalFestival = null;
157-
}
158-
}
159-
160-
private unsafe void SetFestivalImmediately(ushort festivalId)
161-
{
162-
var layoutWorld = LayoutWorld.Instance();
163-
if(layoutWorld != null)
164-
{
165-
var layoutManager = layoutWorld->ActiveLayout;
166-
167-
if(layoutManager != null)
168-
{
169-
var layout = new FestivalArgs
170-
{
171-
FestivalId = festivalId
172-
};
173-
174-
175-
_updateFestival(layoutManager, layout);
176-
}
177-
}
178-
}
179188

180-
private bool CheckFestivalRestrictions(ushort festivalId)
189+
private bool CheckFestivalRestrictions(uint festivalId)
181190
{
182191
var festival = _festivalEntries.SingleOrDefault(i => i.Id == festivalId);
183192
if(festival == null) return false;
@@ -208,38 +217,26 @@ private void Instance_OnGPoseStateChange(GPoseState gposeState)
208217
{
209218
if(gposeState == GPoseState.Exiting)
210219
{
211-
ResetFestivalOverride();
220+
ResetFestivals();
212221
}
213222
}
214223

215224
private void ClientState_TerritoryChanged(object? sender, ushort e)
216225
{
217-
_queuedTransitions.Clear();
218-
_originalFestival = null;
226+
_pendingChanges.Clear();
227+
_originalState = null;
219228
}
220229

221230
public override void Stop()
222231
{
232+
ResetFestivals(true);
223233
GPoseService.Instance.OnGPoseStateChange -= Instance_OnGPoseStateChange;
224234
Dalamud.ClientState.TerritoryChanged -= ClientState_TerritoryChanged;
225-
226-
if(_originalFestival != null && CurrentFestivalId != _originalFestival)
227-
SetFestivalImmediately((ushort) _originalFestival);
228-
229-
}
230-
231-
[StructLayout(LayoutKind.Explicit, Size = 0x10)]
232-
private struct FestivalArgs
233-
{
234-
[FieldOffset(0x0)] public uint FestivalId;
235-
[FieldOffset(0x4)] public uint a1;
236-
[FieldOffset(0x8)] public uint a2;
237-
[FieldOffset(0xC)] public uint a3;
238235
}
239236

240237
private class FestivalFileEntry
241238
{
242-
public ushort Id { get; set; }
239+
public uint Id { get; set; }
243240
public string Name { get; set; } = null!;
244241
public bool Unsafe { get; set; }
245242
public FestivalAreaExclusion? AreaExclusion { get; set; }
@@ -262,7 +259,7 @@ public class FestivalAreaExclusionBoundary
262259

263260
public class FestivalEntry
264261
{
265-
public ushort Id { get; set; }
262+
public uint Id { get; set; }
266263
public string Name { get; set; } = "Unknown";
267264
public bool Unknown { get; set; }
268265
public bool Unsafe { get; set; }

0 commit comments

Comments
 (0)