1
1
using Brio . Core ;
2
2
using Brio . Game . GPose ;
3
3
using Brio . Utils ;
4
- using FFXIVClientStructs . FFXIV . Client . LayoutEngine ;
5
4
using Lumina . Excel . GeneratedSheets ;
6
5
using System . Collections . Generic ;
7
6
using System . Collections . ObjectModel ;
8
7
using System . IO ;
9
8
using System . Linq ;
10
9
using System . Numerics ;
11
10
using System . Reflection ;
12
- using System . Runtime . InteropServices ;
13
11
using System . Text . Json ;
14
12
15
13
namespace Brio . Game . World ;
16
14
public class FestivalService : ServiceBase < FestivalService >
17
15
{
18
- // TODO: Submit the layout changes back to ClientStructs
16
+ public const int MaxFestivals = 4 ;
19
17
20
18
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 ;
22
38
23
39
private List < FestivalEntry > _festivalEntries = new ( ) ;
24
40
25
- private Queue < ushort > _queuedTransitions = new ( ) ;
26
- private ushort ? _originalFestival ;
41
+ private LayoutManagerInterop _festivalInterop = new ( ) ;
42
+ private GameMainInterop _gameMainInterop = new ( ) ;
27
43
28
- private unsafe delegate * unmanaged< LayoutManager * , FestivalArgs , void > _updateFestival ;
29
44
30
- public FestivalEntry CurrentFestival
45
+ private Queue < uint [ ] ? > _pendingChanges = new ( ) ;
46
+ private uint [ ] ? _originalState ;
47
+
48
+ public override void Start ( )
31
49
{
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 )
33
65
{
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
+ }
36
73
}
74
+
75
+ return false ;
37
76
}
38
77
39
- public unsafe ushort CurrentFestivalId
78
+ public bool RemoveFestival ( uint festival )
40
79
{
41
- get
80
+ var active = _festivalInterop . GetActiveFestivals ( ) ;
81
+ var copy = active . ToArray ( ) ;
82
+
83
+ for ( int i = 0 ; i < MaxFestivals ; ++ i )
42
84
{
43
- var layoutWorld = LayoutWorld . Instance ( ) ;
44
- if ( layoutWorld != null )
85
+ if ( active [ i ] == festival )
45
86
{
46
- var layoutManager = layoutWorld ->ActiveLayout ;
87
+ SnapshotFestivals ( ) ;
88
+ copy [ i ] = 0 ;
89
+ _pendingChanges . Enqueue ( copy ) ;
90
+ return true ;
91
+ }
92
+ }
47
93
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 ( ) ) ;
52
108
}
53
109
54
- return 0 ;
110
+ _originalState = null ;
55
111
}
56
112
}
57
113
58
- public unsafe FestivalService ( )
114
+ public unsafe override void Tick ( )
59
115
{
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
+ }
62
122
}
63
123
64
- public override void Start ( )
124
+ private void SnapshotFestivals ( )
65
125
{
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 ( ) ;
70
128
}
71
129
72
- public unsafe override void Tick ( )
130
+ private unsafe void InternalApply ( uint [ ] festivals , bool applyNow = true )
73
131
{
74
- if ( _queuedTransitions . Count == 0 )
75
- return ;
76
-
77
- var layoutWorld = LayoutWorld . Instance ( ) ;
78
- if ( layoutWorld != null )
132
+ if ( applyNow )
79
133
{
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 ] ) ;
91
139
}
92
140
}
93
141
@@ -137,47 +185,8 @@ private void UpdateFestivalList()
137
185
}
138
186
}
139
187
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
- }
179
188
180
- private bool CheckFestivalRestrictions ( ushort festivalId )
189
+ private bool CheckFestivalRestrictions ( uint festivalId )
181
190
{
182
191
var festival = _festivalEntries . SingleOrDefault ( i => i . Id == festivalId ) ;
183
192
if ( festival == null ) return false ;
@@ -208,38 +217,26 @@ private void Instance_OnGPoseStateChange(GPoseState gposeState)
208
217
{
209
218
if ( gposeState == GPoseState . Exiting )
210
219
{
211
- ResetFestivalOverride ( ) ;
220
+ ResetFestivals ( ) ;
212
221
}
213
222
}
214
223
215
224
private void ClientState_TerritoryChanged ( object ? sender , ushort e )
216
225
{
217
- _queuedTransitions . Clear ( ) ;
218
- _originalFestival = null ;
226
+ _pendingChanges . Clear ( ) ;
227
+ _originalState = null ;
219
228
}
220
229
221
230
public override void Stop ( )
222
231
{
232
+ ResetFestivals ( true ) ;
223
233
GPoseService . Instance . OnGPoseStateChange -= Instance_OnGPoseStateChange ;
224
234
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 ;
238
235
}
239
236
240
237
private class FestivalFileEntry
241
238
{
242
- public ushort Id { get ; set ; }
239
+ public uint Id { get ; set ; }
243
240
public string Name { get ; set ; } = null ! ;
244
241
public bool Unsafe { get ; set ; }
245
242
public FestivalAreaExclusion ? AreaExclusion { get ; set ; }
@@ -262,7 +259,7 @@ public class FestivalAreaExclusionBoundary
262
259
263
260
public class FestivalEntry
264
261
{
265
- public ushort Id { get ; set ; }
262
+ public uint Id { get ; set ; }
266
263
public string Name { get ; set ; } = "Unknown" ;
267
264
public bool Unknown { get ; set ; }
268
265
public bool Unsafe { get ; set ; }
0 commit comments