using System.Collections.Generic; using Content.Server.GameObjects.Components.Body; using Content.Server.GameObjects.Components.Damage; using Content.Server.Mobs; using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.EntitySystems; using Robust.Shared.GameObjects; using Robust.Shared.GameObjects.Components; using Robust.Shared.Interfaces.GameObjects; namespace Content.Server.GameObjects.Components.Mobs { /// /// When attacked to an , this component will handle critical and death behaviors /// for mobs. /// Additionally, it handles sending effects to clients (such as blur effect for unconsciousness) and managing the /// health HUD. /// [RegisterComponent] internal class MobStateManagerComponent : Component, IOnHealthChangedBehavior, IActionBlocker { private readonly Dictionary _behavior = new Dictionary { {DamageState.Alive, new NormalState()}, {DamageState.Critical, new CriticalState()}, {DamageState.Dead, new DeadState()} }; public override string Name => "MobStateManager"; private DamageState _currentDamageState; public IMobState CurrentMobState { get; private set; } = new NormalState(); bool IActionBlocker.CanInteract() { return CurrentMobState.CanInteract(); } bool IActionBlocker.CanMove() { return CurrentMobState.CanMove(); } bool IActionBlocker.CanUse() { return CurrentMobState.CanUse(); } bool IActionBlocker.CanThrow() { return CurrentMobState.CanThrow(); } bool IActionBlocker.CanSpeak() { return CurrentMobState.CanSpeak(); } bool IActionBlocker.CanDrop() { return CurrentMobState.CanDrop(); } bool IActionBlocker.CanPickup() { return CurrentMobState.CanPickup(); } bool IActionBlocker.CanEmote() { return CurrentMobState.CanEmote(); } bool IActionBlocker.CanAttack() { return CurrentMobState.CanAttack(); } bool IActionBlocker.CanEquip() { return CurrentMobState.CanEquip(); } bool IActionBlocker.CanUnequip() { return CurrentMobState.CanUnequip(); } bool IActionBlocker.CanChangeDirection() { return CurrentMobState.CanChangeDirection(); } public void OnHealthChanged(HealthChangedEventArgs e) { if (e.Damageable.CurrentDamageState != _currentDamageState) { _currentDamageState = e.Damageable.CurrentDamageState; CurrentMobState.ExitState(Owner); CurrentMobState = _behavior[_currentDamageState]; CurrentMobState.EnterState(Owner); } CurrentMobState.UpdateState(Owner); } public override void Initialize() { base.Initialize(); _currentDamageState = DamageState.Alive; CurrentMobState = _behavior[_currentDamageState]; CurrentMobState.EnterState(Owner); CurrentMobState.UpdateState(Owner); } public override void OnRemove() { // TODO: Might want to add an OnRemove() to IMobState since those are where these components are being used base.OnRemove(); if (Owner.TryGetComponent(out ServerStatusEffectsComponent status)) { status.RemoveStatusEffect(StatusEffect.Health); } if (Owner.TryGetComponent(out ServerOverlayEffectsComponent overlay)) { overlay.ClearOverlays(); } } } /// /// Defines the blocking effects of an associated /// (i.e. Normal, Critical, Dead) and what effects to apply upon entering or /// exiting the state. /// public interface IMobState : IActionBlocker { /// /// Called when this state is entered. /// void EnterState(IEntity entity); /// /// Called when this state is left for a different state. /// void ExitState(IEntity entity); /// /// Called when this state is updated. /// void UpdateState(IEntity entity); } /// /// The standard state an entity is in; no negative effects. /// public struct NormalState : IMobState { public void EnterState(IEntity entity) { UpdateState(entity); } public void ExitState(IEntity entity) { } public void UpdateState(IEntity entity) { if (!entity.TryGetComponent(out ServerStatusEffectsComponent status)) { return; } if (!entity.TryGetComponent(out IDamageableComponent damageable)) { status.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Interface/StatusEffects/Human/human0.png"); return; } // TODO switch (damageable) { case RuinableComponent ruinable: { var modifier = (int) (ruinable.TotalDamage / (ruinable.MaxHp / 7f)); status.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); break; } case BodyManagerComponent body: { // TODO: Declare body max normal damage (currently 100) var modifier = (int) (body.TotalDamage / (100f / 7f)); status.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); break; } default: { status.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Interface/StatusEffects/Human/human0.png"); break; } } } bool IActionBlocker.CanInteract() { return true; } bool IActionBlocker.CanMove() { return true; } bool IActionBlocker.CanUse() { return true; } bool IActionBlocker.CanThrow() { return true; } bool IActionBlocker.CanSpeak() { return true; } bool IActionBlocker.CanDrop() { return true; } bool IActionBlocker.CanPickup() { return true; } bool IActionBlocker.CanEmote() { return true; } bool IActionBlocker.CanAttack() { return true; } bool IActionBlocker.CanEquip() { return true; } bool IActionBlocker.CanUnequip() { return true; } bool IActionBlocker.CanChangeDirection() { return true; } } /// /// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious). /// public struct CriticalState : IMobState { public void EnterState(IEntity entity) { if (entity.TryGetComponent(out ServerStatusEffectsComponent status)) { status.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Interface/StatusEffects/Human/humancrit-0.png"); //Todo: combine humancrit-0 and humancrit-1 into a gif and display it } if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) { overlay.AddOverlay(SharedOverlayID.GradientCircleMaskOverlay); } if (entity.TryGetComponent(out StunnableComponent stun)) { stun.CancelAll(); } StandingStateHelper.Down(entity); } public void ExitState(IEntity entity) { StandingStateHelper.Standing(entity); if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) { overlay.ClearOverlays(); } } public void UpdateState(IEntity entity) { } bool IActionBlocker.CanInteract() { return false; } bool IActionBlocker.CanMove() { return false; } bool IActionBlocker.CanUse() { return false; } bool IActionBlocker.CanThrow() { return false; } bool IActionBlocker.CanSpeak() { return false; } bool IActionBlocker.CanDrop() { return false; } bool IActionBlocker.CanPickup() { return false; } bool IActionBlocker.CanEmote() { return false; } bool IActionBlocker.CanAttack() { return false; } bool IActionBlocker.CanEquip() { return false; } bool IActionBlocker.CanUnequip() { return false; } bool IActionBlocker.CanChangeDirection() { return false; } } /// /// The state representing a dead entity; allows for ghosting. /// public struct DeadState : IMobState { public void EnterState(IEntity entity) { if (entity.TryGetComponent(out ServerStatusEffectsComponent status)) { status.ChangeStatusEffectIcon(StatusEffect.Health, "/Textures/Interface/StatusEffects/Human/humandead.png"); } if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlayComponent)) { overlayComponent.AddOverlay(SharedOverlayID.CircleMaskOverlay); } if (entity.TryGetComponent(out StunnableComponent stun)) { stun.CancelAll(); } StandingStateHelper.Down(entity); if (entity.TryGetComponent(out CollidableComponent collidable)) { collidable.CanCollide = false; } } public void ExitState(IEntity entity) { StandingStateHelper.Standing(entity); if (entity.TryGetComponent(out CollidableComponent collidable)) { collidable.CanCollide = true; } if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) { overlay.ClearOverlays(); } } public void UpdateState(IEntity entity) { } bool IActionBlocker.CanInteract() { return false; } bool IActionBlocker.CanMove() { return false; } bool IActionBlocker.CanUse() { return false; } bool IActionBlocker.CanThrow() { return false; } bool IActionBlocker.CanSpeak() { return false; } bool IActionBlocker.CanDrop() { return false; } bool IActionBlocker.CanPickup() { return false; } bool IActionBlocker.CanEmote() { return false; } bool IActionBlocker.CanAttack() { return false; } bool IActionBlocker.CanEquip() { return false; } bool IActionBlocker.CanUnequip() { return false; } bool IActionBlocker.CanChangeDirection() { return false; } } }