2022-07-06 17:58:14 +10:00
using System.Diagnostics.CodeAnalysis ;
using Content.Shared.ActionBlocker ;
2022-12-15 12:33:27 -06:00
using Content.Shared.Administration.Logs ;
2022-07-06 17:58:14 +10:00
using Content.Shared.Alert ;
2022-12-15 21:34:43 +00:00
using Content.Shared.Bed.Sleep ;
2022-07-06 17:58:14 +10:00
using Content.Shared.Damage ;
2022-12-15 12:33:27 -06:00
using Content.Shared.Database ;
2022-12-16 17:33:34 +00:00
using Content.Shared.Disease.Events ;
2022-07-06 17:58:14 +10:00
using Content.Shared.DragDrop ;
using Content.Shared.Emoting ;
using Content.Shared.FixedPoint ;
using Content.Shared.Interaction.Events ;
using Content.Shared.Inventory.Events ;
using Content.Shared.Item ;
using Content.Shared.MobState.Components ;
using Content.Shared.Movement.Events ;
using Content.Shared.Pulling.Events ;
using Content.Shared.Speech ;
using Content.Shared.Standing ;
using Content.Shared.StatusEffect ;
2022-10-16 18:26:28 +13:00
using Content.Shared.Strip.Components ;
2022-07-06 17:58:14 +10:00
using Content.Shared.Throwing ;
2022-10-04 14:24:19 +11:00
using Robust.Shared.Physics.Systems ;
2022-07-06 17:58:14 +10:00
using Robust.Shared.Serialization ;
namespace Content.Shared.MobState.EntitySystems
{
public abstract partial class SharedMobStateSystem : EntitySystem
{
[Dependency] protected readonly AlertsSystem Alerts = default ! ;
2022-10-04 14:24:19 +11:00
[Dependency] private readonly ActionBlockerSystem _blocker = default ! ;
[Dependency] private readonly SharedAppearanceSystem _appearance = default ! ;
[Dependency] private readonly SharedPhysicsSystem _physics = default ! ;
2022-07-06 17:58:14 +10:00
[Dependency] protected readonly StatusEffectsSystem Status = default ! ;
2022-10-04 14:24:19 +11:00
[Dependency] private readonly StandingStateSystem _standing = default ! ;
2022-12-15 12:33:27 -06:00
[Dependency] private readonly ISharedAdminLogManager _adminLogger = default ! ;
2022-07-06 17:58:14 +10:00
public override void Initialize ( )
{
base . Initialize ( ) ;
SubscribeLocalEvent < MobStateComponent , ComponentShutdown > ( OnMobShutdown ) ;
SubscribeLocalEvent < MobStateComponent , ComponentStartup > ( OnMobStartup ) ;
2022-10-16 18:26:28 +13:00
SubscribeLocalEvent < MobStateComponent , BeforeGettingStrippedEvent > ( OnGettingStripped ) ;
2022-07-06 17:58:14 +10:00
SubscribeLocalEvent < MobStateComponent , ChangeDirectionAttemptEvent > ( OnChangeDirectionAttempt ) ;
SubscribeLocalEvent < MobStateComponent , UseAttemptEvent > ( OnUseAttempt ) ;
SubscribeLocalEvent < MobStateComponent , InteractionAttemptEvent > ( OnInteractAttempt ) ;
SubscribeLocalEvent < MobStateComponent , ThrowAttemptEvent > ( OnThrowAttempt ) ;
SubscribeLocalEvent < MobStateComponent , SpeakAttemptEvent > ( OnSpeakAttempt ) ;
SubscribeLocalEvent < MobStateComponent , IsEquippingAttemptEvent > ( OnEquipAttempt ) ;
SubscribeLocalEvent < MobStateComponent , EmoteAttemptEvent > ( OnEmoteAttempt ) ;
SubscribeLocalEvent < MobStateComponent , IsUnequippingAttemptEvent > ( OnUnequipAttempt ) ;
SubscribeLocalEvent < MobStateComponent , DropAttemptEvent > ( OnDropAttempt ) ;
SubscribeLocalEvent < MobStateComponent , PickupAttemptEvent > ( OnPickupAttempt ) ;
SubscribeLocalEvent < MobStateComponent , StartPullAttemptEvent > ( OnStartPullAttempt ) ;
SubscribeLocalEvent < MobStateComponent , DamageChangedEvent > ( UpdateState ) ;
SubscribeLocalEvent < MobStateComponent , UpdateCanMoveEvent > ( OnMoveAttempt ) ;
SubscribeLocalEvent < MobStateComponent , StandAttemptEvent > ( OnStandAttempt ) ;
2022-12-15 21:34:43 +00:00
SubscribeLocalEvent < MobStateComponent , TryingToSleepEvent > ( OnSleepAttempt ) ;
2022-12-16 17:33:34 +00:00
SubscribeLocalEvent < MobStateComponent , AttemptSneezeCoughEvent > ( OnSneezeAttempt ) ;
2022-07-06 17:58:14 +10:00
SubscribeLocalEvent < MobStateChangedEvent > ( OnStateChanged ) ;
// Note that there's no check for Down attempts because if a mob's in crit or dead, they can be downed...
}
2022-12-15 21:34:43 +00:00
private void OnSleepAttempt ( EntityUid uid , MobStateComponent component , ref TryingToSleepEvent args )
{
if ( IsDead ( uid , component ) )
args . Cancelled = true ;
2022-12-16 17:33:34 +00:00
}
private void OnSneezeAttempt ( EntityUid uid , MobStateComponent component , ref AttemptSneezeCoughEvent args )
{
if ( IsDead ( uid , component ) )
args . Cancelled = true ;
2022-12-15 21:34:43 +00:00
}
2022-10-16 18:26:28 +13:00
private void OnGettingStripped ( EntityUid uid , MobStateComponent component , BeforeGettingStrippedEvent args )
{
// Incapacitated or dead targets get stripped two or three times as fast. Makes stripping corpses less tedious.
if ( IsDead ( uid , component ) )
args . Multiplier / = 3 ;
else if ( IsCritical ( uid , component ) )
args . Multiplier / = 2 ;
}
2022-07-06 17:58:14 +10:00
private void OnMobStartup ( EntityUid uid , MobStateComponent component , ComponentStartup args )
{
if ( component . CurrentState ! = null & & component . CurrentThreshold ! = null )
{
// Initialize with given states
SetMobState ( component , null , ( component . CurrentState . Value , component . CurrentThreshold . Value ) ) ;
}
else
{
// Initialize with some amount of damage, defaulting to 0.
UpdateState ( component , CompOrNull < DamageableComponent > ( uid ) ? . TotalDamage ? ? FixedPoint2 . Zero ) ;
}
}
private void OnMobShutdown ( EntityUid uid , MobStateComponent component , ComponentShutdown args )
{
Alerts . ClearAlert ( uid , AlertType . HumanHealth ) ;
}
public bool IsAlive ( EntityUid uid , MobStateComponent ? component = null )
{
if ( ! Resolve ( uid , ref component , false ) ) return false ;
return component . CurrentState = = DamageState . Alive ;
}
public bool IsCritical ( EntityUid uid , MobStateComponent ? component = null )
{
if ( ! Resolve ( uid , ref component , false ) ) return false ;
return component . CurrentState = = DamageState . Critical ;
}
public bool IsDead ( EntityUid uid , MobStateComponent ? component = null )
{
if ( ! Resolve ( uid , ref component , false ) ) return false ;
return component . CurrentState = = DamageState . Dead ;
}
public bool IsIncapacitated ( EntityUid uid , MobStateComponent ? component = null )
{
if ( ! Resolve ( uid , ref component , false ) ) return false ;
return component . CurrentState is DamageState . Critical or DamageState . Dead ;
}
#region ActionBlocker
private void OnStateChanged ( MobStateChangedEvent ev )
{
_blocker . UpdateCanMove ( ev . Entity ) ;
}
private void CheckAct ( EntityUid uid , MobStateComponent component , CancellableEntityEventArgs args )
{
switch ( component . CurrentState )
{
case DamageState . Dead :
case DamageState . Critical :
args . Cancel ( ) ;
break ;
}
}
private void OnChangeDirectionAttempt ( EntityUid uid , MobStateComponent component , ChangeDirectionAttemptEvent args )
{
CheckAct ( uid , component , args ) ;
}
private void OnUseAttempt ( EntityUid uid , MobStateComponent component , UseAttemptEvent args )
{
CheckAct ( uid , component , args ) ;
}
private void OnInteractAttempt ( EntityUid uid , MobStateComponent component , InteractionAttemptEvent args )
{
CheckAct ( uid , component , args ) ;
}
private void OnThrowAttempt ( EntityUid uid , MobStateComponent component , ThrowAttemptEvent args )
{
CheckAct ( uid , component , args ) ;
}
private void OnSpeakAttempt ( EntityUid uid , MobStateComponent component , SpeakAttemptEvent args )
{
CheckAct ( uid , component , args ) ;
}
private void OnEquipAttempt ( EntityUid uid , MobStateComponent component , IsEquippingAttemptEvent args )
{
// is this a self-equip, or are they being stripped?
if ( args . Equipee = = uid )
CheckAct ( uid , component , args ) ;
}
private void OnEmoteAttempt ( EntityUid uid , MobStateComponent component , EmoteAttemptEvent args )
{
CheckAct ( uid , component , args ) ;
}
private void OnUnequipAttempt ( EntityUid uid , MobStateComponent component , IsUnequippingAttemptEvent args )
{
// is this a self-equip, or are they being stripped?
if ( args . Unequipee = = uid )
CheckAct ( uid , component , args ) ;
}
private void OnDropAttempt ( EntityUid uid , MobStateComponent component , DropAttemptEvent args )
{
CheckAct ( uid , component , args ) ;
}
private void OnPickupAttempt ( EntityUid uid , MobStateComponent component , PickupAttemptEvent args )
{
CheckAct ( uid , component , args ) ;
}
#endregion
private void OnStartPullAttempt ( EntityUid uid , MobStateComponent component , StartPullAttemptEvent args )
{
if ( IsIncapacitated ( uid , component ) )
args . Cancel ( ) ;
}
public void UpdateState ( EntityUid _ , MobStateComponent component , DamageChangedEvent args )
{
2022-10-08 12:15:27 +02:00
UpdateState ( component , args . Damageable . TotalDamage , args . Origin ) ;
2022-07-06 17:58:14 +10:00
}
private void OnMoveAttempt ( EntityUid uid , MobStateComponent component , UpdateCanMoveEvent args )
{
switch ( component . CurrentState )
{
case DamageState . Critical :
case DamageState . Dead :
args . Cancel ( ) ;
return ;
default :
return ;
}
}
private void OnStandAttempt ( EntityUid uid , MobStateComponent component , StandAttemptEvent args )
{
if ( IsIncapacitated ( uid , component ) )
args . Cancel ( ) ;
}
public virtual void RemoveState ( MobStateComponent component )
{
var old = component . CurrentState ;
component . CurrentState = null ;
component . CurrentThreshold = null ;
SetMobState ( component , old , null ) ;
}
public virtual void EnterState ( MobStateComponent ? component , DamageState ? state )
{
// TODO: Thanks buckle
if ( component = = null ) return ;
switch ( state )
{
case DamageState . Alive :
EnterNormState ( component . Owner ) ;
break ;
case DamageState . Critical :
EnterCritState ( component . Owner ) ;
break ;
case DamageState . Dead :
EnterDeadState ( component . Owner ) ;
break ;
case null :
break ;
default :
throw new NotImplementedException ( ) ;
}
}
protected virtual void UpdateState ( MobStateComponent component , DamageState ? state , FixedPoint2 threshold )
{
switch ( state )
{
case DamageState . Alive :
UpdateNormState ( component . Owner , threshold ) ;
break ;
case DamageState . Critical :
UpdateCritState ( component . Owner , threshold ) ;
break ;
case DamageState . Dead :
UpdateDeadState ( component . Owner , threshold ) ;
break ;
case null :
break ;
default :
throw new NotImplementedException ( ) ;
}
}
protected virtual void ExitState ( MobStateComponent component , DamageState ? state )
{
switch ( state )
{
case DamageState . Alive :
ExitNormState ( component . Owner ) ;
break ;
case DamageState . Critical :
ExitCritState ( component . Owner ) ;
break ;
case DamageState . Dead :
ExitDeadState ( component . Owner ) ;
break ;
case null :
break ;
default :
throw new NotImplementedException ( ) ;
}
}
/// <summary>
/// Updates the mob state..
/// </summary>
2022-10-08 12:15:27 +02:00
public void UpdateState ( MobStateComponent component , FixedPoint2 damage , EntityUid ? origin = null )
2022-07-06 17:58:14 +10:00
{
if ( ! TryGetState ( component , damage , out var newState , out var threshold ) )
{
return ;
}
2022-10-08 12:15:27 +02:00
SetMobState ( component , component . CurrentState , ( newState . Value , threshold ) , origin ) ;
2022-07-06 17:58:14 +10:00
}
/// <summary>
/// Sets the mob state and marks the component as dirty.
/// </summary>
2022-10-08 12:15:27 +02:00
private void SetMobState ( MobStateComponent component , DamageState ? old , ( DamageState state , FixedPoint2 threshold ) ? current , EntityUid ? origin = null )
2022-07-06 17:58:14 +10:00
{
2022-11-08 13:53:49 -05:00
//if it got deleted instantly in a nuke or something
if ( ! Exists ( component . Owner ) | | Deleted ( component . Owner ) )
return ;
2022-07-06 17:58:14 +10:00
if ( ! current . HasValue )
{
ExitState ( component , old ) ;
return ;
}
var ( state , threshold ) = current . Value ;
component . CurrentThreshold = threshold ;
if ( state = = old )
{
UpdateState ( component , state , threshold ) ;
return ;
}
ExitState ( component , old ) ;
component . CurrentState = state ;
2022-12-15 12:33:27 -06:00
_adminLogger . Add ( LogType . Damaged , state = = DamageState . Alive ? LogImpact . Low : LogImpact . Medium , $"{ToPrettyString(component.Owner):user} state changed from {old} to {state}" ) ;
2022-07-06 17:58:14 +10:00
EnterState ( component , state ) ;
UpdateState ( component , state , threshold ) ;
2022-10-08 12:15:27 +02:00
var message = new MobStateChangedEvent ( component , old , state , origin ) ;
2022-07-06 17:58:14 +10:00
RaiseLocalEvent ( component . Owner , message , true ) ;
Dirty ( component ) ;
}
public ( DamageState state , FixedPoint2 threshold ) ? GetState ( MobStateComponent component , FixedPoint2 damage )
{
foreach ( var ( threshold , state ) in component . _highestToLowestStates )
{
if ( damage > = threshold )
{
return ( state , threshold ) ;
}
}
return null ;
}
public bool TryGetState (
MobStateComponent component ,
FixedPoint2 damage ,
[NotNullWhen(true)] out DamageState ? state ,
out FixedPoint2 threshold )
{
var highestState = GetState ( component , damage ) ;
if ( highestState = = null )
{
state = default ;
threshold = default ;
return false ;
}
( state , threshold ) = highestState . Value ;
return true ;
}
private ( DamageState state , FixedPoint2 threshold ) ? GetEarliestState ( MobStateComponent component , FixedPoint2 minimumDamage , Predicate < DamageState > predicate )
{
foreach ( var ( threshold , state ) in component . _lowestToHighestStates )
{
if ( threshold < minimumDamage | |
! predicate ( state ) )
{
continue ;
}
return ( state , threshold ) ;
}
return null ;
}
private ( DamageState state , FixedPoint2 threshold ) ? GetPreviousState ( MobStateComponent component , FixedPoint2 maximumDamage , Predicate < DamageState > predicate )
{
foreach ( var ( threshold , state ) in component . _highestToLowestStates )
{
if ( threshold > maximumDamage | |
! predicate ( state ) )
{
continue ;
}
return ( state , threshold ) ;
}
return null ;
}
public ( DamageState state , FixedPoint2 threshold ) ? GetEarliestCriticalState ( MobStateComponent component , FixedPoint2 minimumDamage )
{
return GetEarliestState ( component , minimumDamage , s = > s = = DamageState . Critical ) ;
}
public ( DamageState state , FixedPoint2 threshold ) ? GetEarliestIncapacitatedState ( MobStateComponent component , FixedPoint2 minimumDamage )
{
return GetEarliestState ( component , minimumDamage , s = > s is DamageState . Critical or DamageState . Dead ) ;
}
public ( DamageState state , FixedPoint2 threshold ) ? GetEarliestDeadState ( MobStateComponent component , FixedPoint2 minimumDamage )
{
return GetEarliestState ( component , minimumDamage , s = > s = = DamageState . Dead ) ;
}
public ( DamageState state , FixedPoint2 threshold ) ? GetPreviousCriticalState ( MobStateComponent component , FixedPoint2 minimumDamage )
{
return GetPreviousState ( component , minimumDamage , s = > s = = DamageState . Critical ) ;
}
private bool TryGetState (
( DamageState state , FixedPoint2 threshold ) ? tuple ,
[NotNullWhen(true)] out DamageState ? state ,
out FixedPoint2 threshold )
{
if ( tuple = = null )
{
state = default ;
threshold = default ;
return false ;
}
( state , threshold ) = tuple . Value ;
return true ;
}
public bool TryGetEarliestCriticalState (
MobStateComponent component ,
FixedPoint2 minimumDamage ,
[NotNullWhen(true)] out DamageState ? state ,
out FixedPoint2 threshold )
{
var earliestState = GetEarliestCriticalState ( component , minimumDamage ) ;
return TryGetState ( earliestState , out state , out threshold ) ;
}
public bool TryGetEarliestIncapacitatedState (
MobStateComponent component ,
FixedPoint2 minimumDamage ,
[NotNullWhen(true)] out DamageState ? state ,
out FixedPoint2 threshold )
{
var earliestState = GetEarliestIncapacitatedState ( component , minimumDamage ) ;
return TryGetState ( earliestState , out state , out threshold ) ;
}
public bool TryGetEarliestDeadState (
MobStateComponent component ,
FixedPoint2 minimumDamage ,
[NotNullWhen(true)] out DamageState ? state ,
out FixedPoint2 threshold )
{
var earliestState = GetEarliestDeadState ( component , minimumDamage ) ;
return TryGetState ( earliestState , out state , out threshold ) ;
}
public bool TryGetPreviousCriticalState (
MobStateComponent component ,
FixedPoint2 maximumDamage ,
[NotNullWhen(true)] out DamageState ? state ,
out FixedPoint2 threshold )
{
var earliestState = GetPreviousCriticalState ( component , maximumDamage ) ;
return TryGetState ( earliestState , out state , out threshold ) ;
}
[Serializable, NetSerializable]
protected sealed class MobStateComponentState : ComponentState
{
public readonly FixedPoint2 ? CurrentThreshold ;
public MobStateComponentState ( FixedPoint2 ? currentThreshold )
{
CurrentThreshold = currentThreshold ;
}
}
}
}