33using System . Linq ;
44using System . Threading ;
55using System . Windows ;
6-
6+ using SleepHunter . Common ;
77using SleepHunter . Metadata ;
88using SleepHunter . Models ;
99using SleepHunter . Settings ;
@@ -14,8 +14,9 @@ public sealed class PlayerMacroState : MacroState
1414 {
1515 private static readonly TimeSpan PanelTimeout = TimeSpan . FromSeconds ( 1 ) ;
1616 private static readonly TimeSpan SwitchDelay = TimeSpan . FromMilliseconds ( 100 ) ;
17- private static readonly string [ ] CrasherSkillNames = new [ ] { "Crasher" , "Animal Feast" , "Execute" } ;
17+ private static readonly TimeSpan DialogDelay = TimeSpan . FromSeconds ( 2 ) ;
1818
19+ private readonly DeferredDispatcher deferredDispatcher = new ( ) ;
1920 private readonly ReaderWriterLockSlim spellQueueLock = new ( ) ;
2021 private readonly ReaderWriterLockSlim flowerQueueLock = new ( ) ;
2122
@@ -392,6 +393,9 @@ protected override void MacroLoop(object argument)
392393 {
393394 client . Update ( PlayerFieldFlags . GameClient ) ;
394395
396+ // Tick the dispatcher so any scheduled events go off
397+ deferredDispatcher . Tick ( ) ;
398+
395399 if ( client . GameClient . IsUserChatting )
396400 {
397401 SetPlayerStatus ( PlayerMacroStatus . ChatIsUp ) ;
@@ -527,6 +531,10 @@ private bool DoSkillMacro(out bool didAssail)
527531 skillList . Add ( skill ) ;
528532 }
529533
534+ // Update stats for current HP if any skill might need it for evaluating min/max thresholds
535+ if ( skillList . Any ( skill => skill . MinHealthPercent . HasValue || skill . MaxHealthPercent . HasValue ) )
536+ client . Update ( PlayerFieldFlags . Stats ) ;
537+
530538 foreach ( var skill in skillList . OrderBy ( ( s ) => { return s . OpensDialog ; } ) )
531539 {
532540 client . Update ( PlayerFieldFlags . GameClient ) ;
@@ -544,17 +552,13 @@ private bool DoSkillMacro(out bool didAssail)
544552 }
545553 }
546554
547- // Crasher skill (requires < 2% HP)
548- if ( CrasherSkillNames . Contains ( skill . Name , StringComparer . OrdinalIgnoreCase ) )
549- {
550- client . Update ( PlayerFieldFlags . Stats ) ;
551- if ( client . Stats . HealthPercent >= 2 )
552- continue ;
553-
554- // TODO: Add Mad Soul + Sacrifice support!
555+ // Min health percentage (ex: > 90%), skip if cannot use YET
556+ if ( skill . MinHealthPercent . HasValue && skill . MinHealthPercent . Value >= client . Stats . HealthPercent )
557+ continue ;
555558
556- // TODO: Add auto-hemloch + hemloch deum support!
557- }
559+ // Max health percentage (ex < 2%), skip if cannot use YET
560+ if ( skill . MaxHealthPercent . HasValue && client . Stats . HealthPercent > skill . MaxHealthPercent . Value )
561+ continue ;
558562
559563 if ( client . SwitchToPanelAndWait ( skill . Panel , TimeSpan . FromSeconds ( 1 ) , out var didRequireSwitch , useShiftKey ) )
560564 {
@@ -566,8 +570,9 @@ private bool DoSkillMacro(out bool didAssail)
566570 }
567571 }
568572
573+ // Close the dialog after a few seconds
569574 if ( expectDialog )
570- client . CancelDialog ( ) ;
575+ deferredDispatcher . DispatchAfter ( client . CancelDialog , DialogDelay ) ;
571576
572577 if ( useSpaceForAssail && isAssailQueued )
573578 {
@@ -604,9 +609,15 @@ private bool DoSpellMacro()
604609 return false ;
605610 }
606611
607- nextSpell . IsUndefined = ! SpellMetadataManager . Instance . ContainsSpell ( nextSpell . Name ) ;
612+ var spellMetadata = SpellMetadataManager . Instance . GetSpell ( nextSpell . Name ) ;
613+ nextSpell . IsUndefined = spellMetadata == null ;
614+
608615 CastSpell ( nextSpell ) ;
609616
617+ // Close the dialog after a few seconds
618+ if ( spellMetadata ? . OpensDialog ?? false )
619+ deferredDispatcher . DispatchAfter ( client . CancelDialog , DialogDelay ) ;
620+
610621 lastUsedSpellItem = nextSpell ;
611622 lastUsedSpellItem . IsActive = true ;
612623 return true ;
@@ -913,32 +924,28 @@ private SpellQueueItem GetNextSpell()
913924 private SpellQueueItem GetNextSpell_NoRotation ( bool skipOnCooldown = true )
914925 {
915926 if ( skipOnCooldown )
916- return spellQueue . FirstOrDefault ( spell => ! spell . IsOnCooldown ) ;
927+ return spellQueue . FirstOrDefault ( spell => ! spell . IsWaitingOnHealth && ! spell . IsOnCooldown ) ;
917928 else
918- return spellQueue . FirstOrDefault ( ) ;
929+ return spellQueue . FirstOrDefault ( spell => ! spell . IsWaitingOnHealth ) ;
919930 }
920931
921932 private SpellQueueItem GetNextSpell_SingularOrder ( bool skipOnCooldown = true )
922933 {
923934 if ( skipOnCooldown )
924- return spellQueue . FirstOrDefault ( spell => ! spell . IsOnCooldown && ! spell . IsDone ) ;
935+ return spellQueue . FirstOrDefault ( spell => ! spell . IsWaitingOnHealth && ! spell . IsOnCooldown && ! spell . IsDone ) ;
925936 else
926- return spellQueue . FirstOrDefault ( spell => ! spell . IsDone ) ;
937+ return spellQueue . FirstOrDefault ( spell => ! spell . IsWaitingOnHealth && ! spell . IsDone ) ;
927938 }
928939
929940 private SpellQueueItem GetNextSpell_RoundRobin ( bool skipOnCooldown = true )
930941 {
931- // All spells are done, nothing to cast
932- if ( spellQueue . All ( spell => spell . IsDone ) )
933- return null ;
934-
935- // All spells are on cooldown, and skipping so nothing to do
936- if ( spellQueue . All ( spell => spell . IsOnCooldown ) && skipOnCooldown )
942+ // All spells are unavailable, nothing to do
943+ if ( spellQueue . All ( spell => spell . IsWaitingOnHealth || spell . IsOnCooldown || spell . IsDone ) )
937944 return null ;
938945
939946 var currentSpell = spellQueue . ElementAt ( spellQueueIndex ) ;
940947
941- while ( currentSpell . IsDone || ( skipOnCooldown && currentSpell . IsOnCooldown ) )
948+ while ( currentSpell . IsWaitingOnHealth || currentSpell . IsDone || ( skipOnCooldown && currentSpell . IsOnCooldown ) )
942949 currentSpell = AdvanceToNextSpell ( ) ;
943950
944951 // Round robin rotation for next time
0 commit comments