2026-06-14 04:17:41 -04:00
using Cielonos.MainGame.Characters.AI ;
using Opsive.BehaviorDesigner.Runtime.Tasks ;
using Opsive.Shared.Utility ;
using UnityEngine ;
namespace Cielonos.MainGame.Characters.AI
{
[Category("Cielonos/Rhythm")]
[Description("Returns Success if an upcoming beat containing a tag that starts with 'EnemyAttack' is within the lead time. Otherwise returns Failure.")]
public class RhythmAttackDetector : AutomataConditionalBase
{
[Tooltip("The lead time before the beat in seconds.")]
public float leadTime = 0.1f ;
[Tooltip("The prefix of the tag to match.")]
public string tagPrefix = "EnemyAttack" ;
private MusicBeatSystem _beatSystem ;
2026-06-30 01:48:58 -04:00
private ILocalRhythmTracker _localTracker ;
2026-06-14 04:17:41 -04:00
private BeatMarker _lastTriggeredBeat ;
public override void OnAwake ( )
{
base . OnAwake ( ) ;
_beatSystem = CombatManager . GetCombatSystem < MusicBeatSystem > ( ) ;
2026-06-30 01:48:58 -04:00
_localTracker = gameObject . GetComponent < ILocalRhythmTracker > ( ) ;
2026-06-14 04:17:41 -04:00
}
public override void OnStart ( )
{
_lastTriggeredBeat = null ;
}
public override TaskStatus OnUpdate ( )
{
2026-06-30 01:48:58 -04:00
MusicBeatData activeData = null ;
float currentSongTime = 0f ;
bool isActive = false ;
if ( _localTracker ! = null & & _localTracker . CurrentBeatData ! = null )
{
activeData = _localTracker . CurrentBeatData ;
currentSongTime = _localTracker . CurrentSongTime ;
isActive = _localTracker . IsPlaying ;
}
else if ( _beatSystem ! = null & & _beatSystem . IsActive & & _beatSystem . CurrentBeatData ! = null )
{
activeData = _beatSystem . CurrentBeatData ;
currentSongTime = _beatSystem . CurrentSongTime ;
isActive = _beatSystem . IsPlaying ;
}
if ( ! isActive | | activeData = = null )
2026-06-14 04:17:41 -04:00
{
return TaskStatus . Failure ;
}
2026-06-30 01:48:58 -04:00
BeatMarker targetBeat = FindNextMatchingBeat ( activeData , currentSongTime ) ;
2026-06-14 04:17:41 -04:00
if ( targetBeat = = null )
{
return TaskStatus . Failure ;
}
// If we have already triggered for this specific beat, we shouldn't succeed again
if ( _lastTriggeredBeat = = targetBeat )
{
return TaskStatus . Failure ;
}
2026-06-30 01:48:58 -04:00
float timeUntilBeat = targetBeat . time - currentSongTime ;
2026-06-14 04:17:41 -04:00
// Check if we are within the lead time window
if ( timeUntilBeat < = leadTime )
{
TriggerDetection ( targetBeat , timeUntilBeat ) ;
return TaskStatus . Success ;
}
return TaskStatus . Failure ;
}
2026-06-30 01:48:58 -04:00
private BeatMarker FindNextMatchingBeat ( MusicBeatData activeData , float currentTime )
2026-06-14 04:17:41 -04:00
{
2026-06-30 01:48:58 -04:00
var markers = activeData . beatMarkers ;
2026-06-14 04:17:41 -04:00
if ( markers = = null ) return null ;
for ( int i = 0 ; i < markers . Count ; i + + )
{
BeatMarker marker = markers [ i ] ;
// Check for upcoming beats
if ( marker . time > currentTime )
{
if ( marker . tags ! = null )
{
for ( int j = 0 ; j < marker . tags . Count ; j + + )
{
if ( marker . tags [ j ] . StartsWith ( tagPrefix ) )
{
return marker ;
}
}
}
}
}
return null ;
}
private void TriggerDetection ( BeatMarker beat , float timeUntilBeat )
{
_lastTriggeredBeat = beat ;
// Log matching tags for identification
string matchedTag = "" ;
if ( beat . tags ! = null )
{
foreach ( string t in beat . tags )
{
if ( t . StartsWith ( tagPrefix ) )
{
matchedTag = t ;
break ;
}
}
}
Debug . Log ( $"[RhythmAttackDetector] Conditional MET for {self.gameObject.name} at {beat.time:F3}s " +
$"(in {timeUntilBeat:F3}s) with matched tag '{matchedTag}'." ) ;
}
}
}