11using System ;
22using System . Collections . Generic ;
3+ using System . Linq ;
34using System . Numerics ;
45using Dalamud . Bindings . ImGui ;
6+ using Dalamud . Game . ClientState . Objects . Types ;
57using Dalamud . Interface . Colors ;
68using ECommons ;
79using ECommons . Configuration ;
10+ using ECommons . DalamudServices ;
11+ using ECommons . GameFunctions ;
812using ECommons . Hooks ;
913using ECommons . Hooks . ActionEffectTypes ;
1014using ECommons . ImGuiMethods ;
15+ using ECommons . Logging ;
16+ using ECommons . MathHelpers ;
1117using Splatoon ;
1218using Splatoon . SplatoonScripting ;
1319
@@ -45,12 +51,6 @@ public enum Role
4551 Tether
4652 }
4753
48- public enum TetherOrigin
49- {
50- NorthWest ,
51- SouthEast
52- }
53-
5454 private readonly uint _actionAfterWave3 = 46134 ;
5555 private readonly uint _actionEnd = 46139 ;
5656 private readonly uint _actionMeteorPlace = 46133 ;
@@ -70,14 +70,18 @@ public enum TetherOrigin
7070 private readonly Vector2 _posSW = new ( 93f , 107f ) ;
7171 private readonly float _southPullDist = 18f ;
7272
73+ private HashSet < ( uint source , uint target ) > _tetherMaps = new ( ) ;
74+ private int _tetherCount = 0 ;
75+ private uint _correctSources ;
76+
7377 private readonly string _vfxLockon = "vfx/lockon/eff/lockon8_t0w.avfx" ;
7478
7579 private Element ? _eNav ;
7680 private Element ? _eTether ;
7781
7882 private State _state = State . Idle ;
7983
80- public override Metadata Metadata => new ( 1 , "Garume" ) ;
84+ public override Metadata Metadata => new ( 2 , "Garume" ) ;
8185 public override HashSet < uint > ? ValidTerritories { get ; } = [ 1325 ] ;
8286
8387 private Config C => Controller . GetConfig < Config > ( ) ;
@@ -89,7 +93,8 @@ public override void OnSetup()
8993 radius = 2f ,
9094 thicc = 10f ,
9195 overlayVOffset = 3f ,
92- overlayFScale = 2.6f
96+ overlayFScale = 2.6f ,
97+ tether = true
9398 } ) ;
9499
95100 Controller . RegisterElement ( "Tether" , new Element ( 2 )
@@ -149,12 +154,50 @@ public override void OnVFXSpawn(uint target, string vfxPath)
149154 if ( ! vfxPath . Equals ( _vfxLockon , StringComparison . OrdinalIgnoreCase ) &&
150155 ! vfxPath . StartsWith ( _vfxLockon , StringComparison . OrdinalIgnoreCase ) )
151156 return ;
157+
152158
153159 if ( _state == State . Wave1Prepare ) _state = State . Wave1Active ;
154160 else if ( _state == State . Wave2Prepare ) _state = State . Wave2Active ;
155161 else if ( _state == State . Wave3Prepare ) _state = State . Wave3Active ;
156162 }
157163
164+ public override void OnTetherCreate ( uint source , uint target , uint data2 , uint data3 , uint data5 )
165+ {
166+ if ( data3 != 356 ) return ;
167+
168+ if ( _state != State . Idle && _state != State . Done )
169+ {
170+ _tetherCount ++ ;
171+ _tetherMaps . Add ( ( source , target ) ) ;
172+ if ( _tetherCount == 2 )
173+ {
174+ var desiredDirections = C . UseSecondMeteorDirection ? new [ ]
175+ {
176+ C . MyTetherOrigin ,
177+ C . MySecondTetherOrigin
178+ } : new [ ] { C . MyTetherOrigin } ;
179+ var normalizedDirections = desiredDirections . Select ( Dir ) . ToArray ( ) ;
180+ foreach ( var ( s , t ) in _tetherMaps )
181+ {
182+ var targetObject = t . GetObject ( ) ;
183+ var targetDirection = Dir ( targetObject ! . Position . ToVector2 ( ) ) ;
184+
185+ if ( normalizedDirections . Any ( d => Vector2 . Dot ( d , targetDirection ) > 0.95f ) )
186+ {
187+ _correctSources = s ;
188+ }
189+ }
190+ }
191+ }
192+ }
193+
194+ public override void OnTetherRemoval ( uint source , uint data2 , uint data3 , uint data5 )
195+ {
196+ if ( data2 != 356 ) return ;
197+
198+ _tetherMaps . RemoveWhere ( x => x . source == source ) ;
199+ }
200+
158201 public override void OnActionEffectEvent ( ActionEffectSet set )
159202 {
160203 if ( set . Action == null ) return ;
@@ -163,6 +206,7 @@ public override void OnActionEffectEvent(ActionEffectSet set)
163206
164207 if ( actionId == _actionMeteorPlace )
165208 {
209+ _tetherCount = 0 ;
166210 if ( _state == State . Wave1Active ) _state = State . Wave2Prepare ;
167211 else if ( _state == State . Wave2Active ) _state = State . Wave3Prepare ;
168212 else if ( _state == State . Wave3Active ) _state = State . Wave4StackOnly ;
@@ -189,60 +233,50 @@ public override void OnUpdate()
189233 if ( _state is State . Idle or State . Done ) return ;
190234
191235 var grad = GradientColor . Get ( C . GradientA , C . GradientB , 333 ) . ToUint ( ) ;
192-
193- if ( _eNav != null )
236+ var ( wave , phase ) = GetWavePhase ( ) ;
237+
238+ if ( C . MyRole == Role . Tether && ( wave == 2 || wave == 3 || wave == 4 ) )
239+ {
240+ ApplyCommonStyle ( _eTether , grad ) ;
241+ var t = GetTetherInstruction ( ) ;
242+ _eTether . SetOffPosition ( new Vector3 ( t . sourcePos . X , 0f , t . sourcePos . Y ) ) ;
243+ _eTether . SetRefPosition ( new Vector3 ( t . targetPos . X , 0f , t . targetPos . Y ) ) ;
244+ }
245+ else
194246 {
195247 ApplyCommonStyle ( _eNav , grad ) ;
196248
197- var nav = GetNavInstruction ( ) ;
249+ var nav = GetNavInstruction ( wave , phase ) ;
198250 SetElement ( _eNav , nav . pos , nav . text ) ;
199251 }
200-
201- if ( _eTether != null )
202- if ( ShouldShowTether ( ) )
203- {
204- ApplyCommonStyle ( _eTether , grad ) ;
205- var t = GetTetherInstruction ( ) ;
206- SetElement ( _eTether , t . pos , t . text ) ;
207- }
208- }
209-
210- private bool ShouldShowTether ( )
211- {
212- if ( C . MyRole != Role . Tether ) return false ;
213- return _state == State . Wave2Active || _state == State . Wave3Active ;
214252 }
215253
216- private ( Vector2 pos , string text ) GetTetherInstruction ( )
254+ private unsafe ( Vector2 sourcePos , Vector2 targetPos , string text ) GetTetherInstruction ( )
217255 {
218- var originPos = C . MyTetherOrigin == TetherOrigin . NorthWest
219- ? PosDir ( Dir ( Corner . NorthWest ) , _distMeteor )
220- : PosDir ( Dir ( Corner . SouthEast ) , _distMeteor ) ;
221- var pullPos = C . MyTetherOrigin == TetherOrigin . NorthWest
222- ? _center with { Y = _center . Y - _northPullDist }
223- : _center with { Y = _center . Y + _southPullDist } ;
224-
225- var txt = C . MyTetherOrigin == TetherOrigin . NorthWest
226- ? "Tether: Take NW -> Pull N"
227- : "Tether: Take SE -> Pull S" ;
228-
229- var useOrigin = true ;
230- if ( _state == State . Wave2Active || _state == State . Wave3Active ) useOrigin = true ;
231-
232- return useOrigin ? ( originPos , txt ) : ( pullPos , txt ) ;
256+ var sourceObj = _correctSources . GetObject ( ) as IBattleNpc ;
257+ if ( sourceObj == null )
258+ return ( Vector2 . Zero , Vector2 . Zero , "" ) ;
259+ var targetObj = sourceObj . Struct ( ) ->Vfx . Tethers . ToArray ( ) . ToArray ( ) . First ( ) . TargetId . ObjectId . GetObject ( ) ;
260+
261+ var text = targetObj != null && targetObj . EntityId == Controller . BasePlayer . EntityId ? "Correct !!!" : "Pick this" ;
262+ if ( targetObj == null )
263+ return ( Vector2 . Zero , Vector2 . Zero , text ) ;
264+
265+ var sourcePos = sourceObj . Position . ToVector2 ( ) ;
266+ var targetPos = targetObj . Position . ToVector2 ( ) ;
267+ return ( sourcePos , targetPos , text ) ;
233268 }
234269
235- private ( Vector2 pos , string text ) GetNavInstruction ( )
270+ private ( Vector2 pos , string text ) GetNavInstruction ( int wave , Phase phase )
236271 {
237- var ( wave , phase ) = GetWavePhase ( ) ;
238-
239272 if ( phase == Phase . Final )
240- return ( PosDir ( Dir ( MeteorSpot . SouthWest ) , _distFinal ) , "Final: SW " ) ;
273+ return ( PosDir ( Dir ( MeteorSpot . SouthWest ) , _distFinal ) , "Final" ) ;
241274
275+
276+ var p = CornerToPos ( C . Stack4 ) ;
242277 if ( phase == Phase . StackOnly )
243278 {
244- var p = CornerToPos ( C . Stack4 ) ;
245- return ( p , $ "Stack4 { Short ( C . Stack4 ) } ") ;
279+ return ( p , $ "Stack4") ;
246280 }
247281
248282 var stackCorner = wave switch
@@ -252,25 +286,35 @@ private bool ShouldShowTether()
252286 _ => C . Stack3
253287 } ;
254288
255- var isMyMeteorWave = C . MyRole == Role . Meteor && C . MyMeteorWave ==
289+ var isMyMeteorWave = C . MyMeteorWave ==
256290 ( wave == 1 ? MeteorWave . Wave1 : wave == 2 ? MeteorWave . Wave2 : MeteorWave . Wave3 ) ;
257-
291+ var isPrepareMeteorWave = C . MyMeteorWave ==
292+ ( wave == 1 ? MeteorWave . Wave2 : MeteorWave . Wave3 ) ;
258293 if ( phase == Phase . Prepare )
259294 {
260295 if ( isMyMeteorWave )
261- return ( _center , $ "Wave{ wave } : Near Boss") ;
262- var p = CornerToPos ( stackCorner ) ;
263- return ( p , $ "Stack{ wave } { Short ( stackCorner ) } ") ;
296+ return ( _center , $ "Wave{ wave } : { C . NearBoss . Get ( ) } ") ;
297+ return ( p , $ "Stack") ;
264298 }
265-
266- if ( isMyMeteorWave )
299+ else
267300 {
268- var ( mp , mt ) = MeteorToNav ( C . MyMeteorSpot , wave ) ;
269- return ( mp , mt ) ;
301+ if ( isMyMeteorWave )
302+ {
303+ var ( mp , mt ) = MeteorToNav ( C . MyMeteorSpot , wave ) ;
304+ return ( mp , mt ) ;
305+ }
306+ else if ( isPrepareMeteorWave )
307+ {
308+ return ( p , $ "Wave{ wave } : { C . PrepareNearBoss . Get ( ) } ") ;
309+ }
310+ else
311+ {
312+ return ( p , $ "Stack") ;
313+ }
270314 }
271315
272316 var sp = CornerToPos ( stackCorner ) ;
273- return ( sp , $ "Stack{ wave } { Short ( stackCorner ) } ") ;
317+ return ( sp , $ "Stack") ;
274318 }
275319
276320 private ( int wave , Phase phase ) GetWavePhase ( )
@@ -322,16 +366,6 @@ private void SetElement(Element e, Vector2 xz, string text)
322366 e . Enabled = true ;
323367 }
324368
325- private string Short ( Corner c )
326- {
327- return c switch
328- {
329- Corner . NorthWest => "NW" ,
330- Corner . NorthEast => "NE" ,
331- _ => "SE"
332- } ;
333- }
334-
335369 private string Short ( MeteorSpot s )
336370 {
337371 return s switch
@@ -353,8 +387,10 @@ public override void OnSettingsDraw()
353387
354388 if ( C . MyRole == Role . Tether )
355389 {
356- ImGui . TextColored ( ImGuiColors . DalamudRed , "対応していません。/ Not supported." ) ;
357- ImGuiEx . EnumCombo ( "Tether Origin" , ref C . MyTetherOrigin ) ;
390+ ImGuiEx . EnumCombo ( "Meteor Direction" , ref C . MyTetherOrigin ) ;
391+ ImGuiEx . HelpMarker ( "Please select which direction’s meteor tether to track.\n どの方角のメテオについたテザーを見るか選択してください" ) ;
392+ ImGui . Checkbox ( "Use Second Direction" , ref C . UseSecondMeteorDirection ) ;
393+ ImGuiEx . EnumCombo ( "Second Meteor Direction" , ref C . MySecondTetherOrigin ) ;
358394 }
359395 else
360396 {
@@ -373,8 +409,13 @@ public override void OnSettingsDraw()
373409 ImGuiEx . Text ( "Gradient (2 colors)" ) ;
374410 ImGui . ColorEdit4 ( "Color A" , ref C . GradientA , ImGuiColorEditFlags . NoInputs ) ;
375411 ImGui . ColorEdit4 ( "Color B" , ref C . GradientB , ImGuiColorEditFlags . NoInputs ) ;
376-
377412 ImGui . Separator ( ) ;
413+
414+ var prepareNearBoss = C . PrepareNearBoss . Get ( ) ;
415+ C . PrepareNearBoss . ImGuiEdit ( ref prepareNearBoss ) ;
416+ var nearBoss = C . NearBoss . Get ( ) ;
417+ C . NearBoss . ImGuiEdit ( ref nearBoss ) ;
418+
378419 if ( ImGui . CollapsingHeader ( "Guide (JP)" ) )
379420 {
380421 ImGui . BulletText ( "頭割りの位置は 1〜4回目すべて選択してください。" ) ;
@@ -388,9 +429,9 @@ public override void OnSettingsDraw()
388429 {
389430 ImGui . BulletText ( " Please select the stack position for all four stacks (Stack 1–4)." ) ;
390431 ImGui . BulletText ( " Example: If you take the first stack at the northeast, choose NorthEast." ) ;
391- ImGui . BulletText ( " If you are baiting/placing a meteor, set MyRole to Meteor, then choose which wave (1–3) and which direction you will place it." ) ;
432+ ImGui . BulletText ( " If you are baiting/placing a meteor, set MyRole to Meteor, then choose which wave (1–3) and which direction you will place it." ) ;
392433 ImGui . BulletText ( " Example: If you want to place the second meteor to the southwest, select Wave2 and then choose one of SouthWest / SouthWestAlt1 / SouthWestAlt2." ) ;
393- ImGui . BulletText ( " For the southwest options, the placement is farther out in this order: SouthWest < SouthWestAlt1 < SouthWestAlt2." ) ;
434+ ImGui . BulletText ( " For the southwest options, the placement is farther out in this order: SouthWest < SouthWestAlt1 < SouthWestAlt2." ) ;
394435 }
395436
396437 if ( ImGui . CollapsingHeader ( "Debug" ) )
@@ -421,17 +462,28 @@ public class Config : IEzConfig
421462 public Vector4 GradientA = ImGuiColors . DalamudYellow ;
422463 public Vector4 GradientB = ImGuiColors . DalamudRed ;
423464 public MeteorSpot MyMeteorSpot = MeteorSpot . NorthWest ;
424-
425465 public MeteorWave MyMeteorWave = MeteorWave . Wave1 ;
426-
427466 public Role MyRole = Role . Meteor ;
428-
429- public TetherOrigin MyTetherOrigin = TetherOrigin . NorthWest ;
467+ public MeteorSpot MyTetherOrigin = MeteorSpot . NorthEast ;
468+ public MeteorSpot MySecondTetherOrigin = MeteorSpot . SouthEast ;
469+ public bool UseSecondMeteorDirection ;
430470
431471 public Corner Stack1 = Corner . NorthEast ;
432472 public Corner Stack2 = Corner . NorthWest ;
433473 public Corner Stack3 = Corner . SouthEast ;
434474 public Corner Stack4 = Corner . NorthEast ;
475+
476+ public InternationalString PrepareNearBoss = new ( )
477+ {
478+ En = "Next, near boss" ,
479+ Jp = "次はボスの足元へ"
480+ } ;
481+
482+ public InternationalString NearBoss = new ( )
483+ {
484+ En = "Near boss" ,
485+ Jp = "足元へ"
486+ } ;
435487 }
436488
437489 private enum Phase
0 commit comments