GameLogic/crowd/CrowdEvaluator.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
public sealed class CrowdEvaluator : Component
{
public static CrowdEvaluator Instance { get; private set; }
[Property] public CrowdConfig Config { get; set; }
[Property] public CrowdAudioController CrowdAudio { get; set; }
// Candidats potentiels au SparedFighter (Combattant -> Temps depuis le dernier coup)
private readonly Dictionary<Fighter, RealTimeSince> _sparedCandidates = new();
public static event Action<CrowdEventPayload, CrowdActionDefinition> OnEvaluatedCrowdAction;
protected override void OnAwake()
{
Instance = this;
CrowdAudio ??= Scene.GetAllComponents<CrowdAudioController>().FirstOrDefault();
}
protected override void OnStart()
{
CombatEvents.OnPlayerWeaponThrowHit += HandleWeaponThrowHit;
CombatEvents.OnPlayerKillEnemy += HandlePlayerKill;
CombatEvents.OnPlayerAirKnockdown += HandleAirKnockdown;
CombatEvents.OnPlayerReboundStrike += HandleReboundStrike;
CombatEvents.OnFighterDamaged += HandleFighterDamaged;
CombatEvents.OnFighterRecoveredFromStaggerOrKnockdown += HandleFighterRecovered;
CombatEvents.OnFighterKilled += HandleAnyFighterKilled;
}
protected override void OnDestroy()
{
CombatEvents.OnPlayerWeaponThrowHit -= HandleWeaponThrowHit;
CombatEvents.OnPlayerKillEnemy -= HandlePlayerKill;
CombatEvents.OnPlayerAirKnockdown -= HandleAirKnockdown;
CombatEvents.OnPlayerReboundStrike -= HandleReboundStrike;
CombatEvents.OnFighterDamaged -= HandleFighterDamaged;
CombatEvents.OnFighterRecoveredFromStaggerOrKnockdown -= HandleFighterRecovered;
CombatEvents.OnFighterKilled -= HandleAnyFighterKilled;
}
private void HandleWeaponThrowHit( Weapon weapon, Fighter victim, DamageContext ctx, bool willImpale )
{
if ( willImpale )
RegisterAction( ctx.Attacker, CrowdAction.Impale, victim );
else
RegisterAction( ctx.Attacker, CrowdAction.PrecisionThrow, victim );
}
private void HandlePlayerKill( Fighter victim, DamageContext ctx, bool victimWasUnarmed )
{
_sparedCandidates.Remove( victim );
if ( victimWasUnarmed )
{
RegisterAction( ctx.Attacker, CrowdAction.UnfairFightKill, victim );
}
else if ( ctx.Zone == HitZone.Head && ctx.Type == DamageType.Slash )
{
RegisterAction( ctx.Attacker, CrowdAction.Decapitation, victim );
}
else if ( ctx.ForceKnockdown && ctx.Type == DamageType.Pierce )
{
RegisterAction( ctx.Attacker, CrowdAction.Impale, victim );
}
else
{
RegisterAction( ctx.Attacker, CrowdAction.Finisher, victim );
}
}
private void HandleAirKnockdown( Fighter player, Fighter victim, DamageContext ctx )
{
RegisterAction( player, CrowdAction.AirKnockdown, victim );
}
private void HandleReboundStrike( Fighter player, Fighter victim, DamageContext ctx )
{
RegisterAction( player, CrowdAction.ReboundStrike, victim );
}
private void HandleFighterDamaged( Fighter victim, DamageContext ctx )
{
bool attackerIsPlayer = ctx.Attacker != null && ctx.Attacker.Components.Get<PlayerController>() != null;
if ( !attackerIsPlayer || victim == ctx.Attacker ) return;
// Condition Spared : agresser un ennemi qui se retrouve low HP (<= 25%) et Staggered
if ( victim.CurrentHealth <= 25f && victim.Status == FighterStatus.Staggered )
{
_sparedCandidates[victim] = 0;
}
else if ( _sparedCandidates.ContainsKey( victim ) )
{
_sparedCandidates.Remove( victim );
}
}
private void HandleFighterRecovered( Fighter fighter )
{
if ( _sparedCandidates.TryGetValue( fighter, out var timeSinceLastHit ) )
{
_sparedCandidates.Remove( fighter );
if ( timeSinceLastHit >= 2.0f && !fighter.IsDead )
{
var player = Scene.GetAllComponents<PlayerController>().FirstOrDefault()?.Components.Get<Fighter>();
if ( player != null )
{
RegisterAction( player, CrowdAction.SparedFighter, fighter );
}
}
}
}
private void HandleAnyFighterKilled( Fighter victim, DamageContext ctx )
{
bool isPlayerAttacker = ctx.Attacker?.Components.Get<PlayerController>() != null;
if ( !isPlayerAttacker && ctx.Attacker != null )
{
ctx.Attacker.Components.Get<FighterAura>()?.ApplyAuraDelta( 10f );
CrowdAudio?.PlayBotHighlightReaction();
}
}
public void RegisterAction( Fighter actor, CrowdAction action, Fighter target = null )
{
if ( actor == null ) return;
var def = Config?.GetDefinition( action ) ?? GetDefaultDefinition( action );
float delta = def.AuraDelta;
actor.Components.Get<FighterAura>()?.ApplyAuraDelta( delta );
if ( def.CrowdSound != null )
CrowdAudio?.PlayReaction( def.CrowdSound, delta >= 0 );
OnEvaluatedCrowdAction?.Invoke( new CrowdEventPayload( actor, action, target ), def );
}
private CrowdActionDefinition GetDefaultDefinition( CrowdAction action )
{
var meta = CrowdEventDisplay.GetMetadata( action );
float defaultDelta = action switch
{
CrowdAction.Decapitation => +15f,
CrowdAction.Impale => +15f,
CrowdAction.Finisher => +12f,
CrowdAction.PrecisionThrow => +8f,
CrowdAction.ReboundStrike => +12f,
CrowdAction.AirKnockdown => +14f,
CrowdAction.SparedFighter => +25f,
CrowdAction.UnfairFightKill => -8f,
CrowdAction.UnsatisfyingKill => -15f,
_ => 0f
};
return new CrowdActionDefinition
{
Action = action,
AuraDelta = defaultDelta,
Title = meta.Title,
ModifierText = meta.ModifierText,
DisplayColor = meta.DisplayColor,
ShowInFeed = meta.ShowInHudToast
};
}
}