diff --git a/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs index 767f354f4b..fc54c88a78 100644 --- a/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs +++ b/Content.Client/GameObjects/Components/Body/BodyManagerComponent.cs @@ -14,7 +14,7 @@ namespace Content.Client.GameObjects.Components.Body { [RegisterComponent] [ComponentReference(typeof(IDamageableComponent))] - [ComponentReference(typeof(IBodyManagerComponent))] + [ComponentReference(typeof(ISharedBodyManagerComponent))] public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable { [Dependency] private readonly IEntityManager _entityManager = default!; diff --git a/Content.Client/GameObjects/Components/Mobs/State/CriticalState.cs b/Content.Client/GameObjects/Components/Mobs/State/CriticalState.cs new file mode 100644 index 0000000000..39b750d525 --- /dev/null +++ b/Content.Client/GameObjects/Components/Mobs/State/CriticalState.cs @@ -0,0 +1,30 @@ +using Content.Client.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Client.GameObjects.Components.Mobs.State +{ + public class CriticalState : SharedCriticalState + { + public override void EnterState(IEntity entity) + { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Critical); + } + + EntitySystem.Get().Down(entity); + } + + public override void ExitState(IEntity entity) + { + EntitySystem.Get().Standing(entity); + } + + public override void UpdateState(IEntity entity) { } + } +} diff --git a/Content.Client/GameObjects/Components/Mobs/State/DeadState.cs b/Content.Client/GameObjects/Components/Mobs/State/DeadState.cs new file mode 100644 index 0000000000..9ab07be1ac --- /dev/null +++ b/Content.Client/GameObjects/Components/Mobs/State/DeadState.cs @@ -0,0 +1,41 @@ +using Content.Client.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; +using Robust.Client.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Client.GameObjects.Components.Mobs.State +{ + public class DeadState : SharedDeadState + { + public override void EnterState(IEntity entity) + { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Dead); + } + + EntitySystem.Get().Down(entity); + + if (entity.TryGetComponent(out CollidableComponent collidable)) + { + collidable.CanCollide = false; + } + } + + public override void ExitState(IEntity entity) + { + EntitySystem.Get().Standing(entity); + + if (entity.TryGetComponent(out CollidableComponent collidable)) + { + collidable.CanCollide = true; + } + } + + public override void UpdateState(IEntity entity) { } + } +} diff --git a/Content.Client/GameObjects/Components/Mobs/State/MobStateManagerComponent.cs b/Content.Client/GameObjects/Components/Mobs/State/MobStateManagerComponent.cs new file mode 100644 index 0000000000..a5b16f9e40 --- /dev/null +++ b/Content.Client/GameObjects/Components/Mobs/State/MobStateManagerComponent.cs @@ -0,0 +1,62 @@ +#nullable enable +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs.State; +using Robust.Shared.GameObjects; + +namespace Content.Client.GameObjects.Components.Mobs.State +{ + [RegisterComponent] + [ComponentReference(typeof(SharedMobStateManagerComponent))] + public class MobStateManagerComponent : SharedMobStateManagerComponent + { + private readonly Dictionary _behavior = new Dictionary + { + {DamageState.Alive, new NormalState()}, + {DamageState.Critical, new CriticalState()}, + {DamageState.Dead, new DeadState()} + }; + + private DamageState _currentDamageState; + + protected override IReadOnlyDictionary Behavior => _behavior; + + public override DamageState CurrentDamageState + { + get => _currentDamageState; + protected set + { + if (_currentDamageState == value) + { + return; + } + + if (_currentDamageState != DamageState.Invalid) + { + CurrentMobState.ExitState(Owner); + } + + _currentDamageState = value; + CurrentMobState = Behavior[CurrentDamageState]; + CurrentMobState.EnterState(Owner); + + Dirty(); + } + } + + public override void HandleComponentState(ComponentState? curState, ComponentState? nextState) + { + base.HandleComponentState(curState, nextState); + + if (!(curState is MobStateManagerComponentState state)) + { + return; + } + + _currentDamageState = state.DamageState; + CurrentMobState.ExitState(Owner); + CurrentMobState = Behavior[CurrentDamageState]; + CurrentMobState.EnterState(Owner); + } + } +} diff --git a/Content.Client/GameObjects/Components/Mobs/State/NormalState.cs b/Content.Client/GameObjects/Components/Mobs/State/NormalState.cs new file mode 100644 index 0000000000..af65dcc082 --- /dev/null +++ b/Content.Client/GameObjects/Components/Mobs/State/NormalState.cs @@ -0,0 +1,25 @@ +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; +using Robust.Client.GameObjects; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Client.GameObjects.Components.Mobs.State +{ + public class NormalState : SharedNormalState + { + public override void EnterState(IEntity entity) + { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Alive); + } + + UpdateState(entity); + } + + public override void ExitState(IEntity entity) { } + + public override void UpdateState(IEntity entity) { } + } +} diff --git a/Content.Client/GameObjects/EntitySystems/StandingStateSystem.cs b/Content.Client/GameObjects/EntitySystems/StandingStateSystem.cs new file mode 100644 index 0000000000..212d386c01 --- /dev/null +++ b/Content.Client/GameObjects/EntitySystems/StandingStateSystem.cs @@ -0,0 +1,50 @@ +using Content.Shared.Audio; +using Content.Shared.GameObjects.Components.Rotation; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Client.GameObjects; +using Robust.Client.GameObjects.EntitySystems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Client.GameObjects.EntitySystems +{ + public class StandingStateSystem : SharedStandingStateSystem + { + protected override bool OnDown(IEntity entity, bool playSound = true, bool dropItems = true, bool force = false) + { + if (!entity.TryGetComponent(out AppearanceComponent appearance)) + { + return false; + } + + var newState = RotationState.Horizontal; + appearance.TryGetData(RotationVisuals.RotationState, out var oldState); + + if (newState != oldState) + { + appearance.SetData(RotationVisuals.RotationState, newState); + } + + if (playSound) + { + var file = AudioHelpers.GetRandomFileFromSoundCollection("bodyfall"); + Get().Play(file, entity, AudioHelpers.WithVariation(0.25f)); + } + + return true; + } + + protected override bool OnStand(IEntity entity) + { + if (!entity.TryGetComponent(out AppearanceComponent appearance)) return false; + + appearance.TryGetData(RotationVisuals.RotationState, out var oldState); + var newState = RotationState.Vertical; + + if (newState == oldState) return false; + + appearance.SetData(RotationVisuals.RotationState, newState); + + return true; + } + } +} diff --git a/Content.Client/IgnoredComponents.cs b/Content.Client/IgnoredComponents.cs index 9aceb3988d..e49789d519 100644 --- a/Content.Client/IgnoredComponents.cs +++ b/Content.Client/IgnoredComponents.cs @@ -156,7 +156,6 @@ "Barotrauma", "GasSprayer", "GasVapor", - "MobStateManager", "Metabolism", "AiFactionTag", "PressureProtection", diff --git a/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs b/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs index 2554c4cf53..b6664b00c7 100644 --- a/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs +++ b/Content.Server/AI/WorldState/States/Mobs/NearbyBodiesState.cs @@ -21,7 +21,7 @@ namespace Content.Server.AI.WorldState.States.Mobs return result; } - foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(IBodyManagerComponent), controller.VisionRadius)) + foreach (var entity in Visibility.GetEntitiesInRange(Owner.Transform.GridPosition, typeof(ISharedBodyManagerComponent), controller.VisionRadius)) { if (entity == Owner) continue; result.Add(entity); diff --git a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs index 3c65da2fd1..e5154a9d93 100644 --- a/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs +++ b/Content.Server/GameObjects/Components/Body/BodyManagerComponent.cs @@ -20,6 +20,7 @@ using Content.Shared.GameObjects.Components.Damage; using Content.Shared.GameObjects.Components.Movement; using Robust.Server.Interfaces.Player; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.GameObjects; using Robust.Shared.Interfaces.Reflection; using Robust.Shared.IoC; @@ -38,7 +39,7 @@ namespace Content.Server.GameObjects.Components.Body /// [RegisterComponent] [ComponentReference(typeof(IDamageableComponent))] - [ComponentReference(typeof(IBodyManagerComponent))] + [ComponentReference(typeof(ISharedBodyManagerComponent))] public class BodyManagerComponent : SharedBodyManagerComponent, IBodyPartContainer, IRelayMoveInput { [Dependency] private readonly IPrototypeManager _prototypeManager = default!; @@ -289,14 +290,14 @@ namespace Content.Server.GameObjects.Components.Body if (speedSum <= 0.001f || _activeLegs.Count <= 0) { // Case: no way of moving. Fall down. - StandingStateHelper.Down(Owner); + EntitySystem.Get().Down(Owner); playerMover.BaseWalkSpeed = 0.8f; playerMover.BaseSprintSpeed = 2.0f; } else { // Case: have at least one leg. Set move speed. - StandingStateHelper.Standing(Owner); + EntitySystem.Get().Standing(Owner); // Extra legs stack diminishingly. // Final speed = speed sum/(leg count-log4(leg count)) diff --git a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs index 0b15560869..b23b629d24 100644 --- a/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs +++ b/Content.Server/GameObjects/Components/Buckle/BuckleComponent.cs @@ -3,9 +3,10 @@ using System; using System.Diagnostics.CodeAnalysis; using Content.Server.GameObjects.Components.GUI; using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.Components.Mobs.State; using Content.Server.GameObjects.Components.Strap; +using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; -using Content.Server.Mobs; using Content.Server.Utility; using Content.Shared.GameObjects.Components.Buckle; using Content.Shared.GameObjects.Components.Mobs; @@ -136,11 +137,11 @@ namespace Content.Server.GameObjects.Components.Buckle ownTransform.WorldRotation = strapTransform.WorldRotation; break; case StrapPosition.Stand: - StandingStateHelper.Standing(Owner); + EntitySystem.Get().Standing(Owner); ownTransform.WorldRotation = strapTransform.WorldRotation; break; case StrapPosition.Down: - StandingStateHelper.Down(Owner, force: true); + EntitySystem.Get().Down(Owner, force: true); ownTransform.WorldRotation = Angle.South; break; } @@ -364,11 +365,11 @@ namespace Content.Server.GameObjects.Components.Buckle if (Owner.TryGetComponent(out StunnableComponent? stunnable) && stunnable.KnockedDown) { - StandingStateHelper.Down(Owner); + EntitySystem.Get().Down(Owner); } else { - StandingStateHelper.Standing(Owner); + EntitySystem.Get().Standing(Owner); } if (Owner.TryGetComponent(out MobStateManagerComponent? stateManager)) diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs index 838cf933d2..ed19c29447 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalHolderComponent.cs @@ -63,7 +63,7 @@ namespace Content.Server.GameObjects.Components.Disposal } return entity.HasComponent() || - entity.HasComponent(); + entity.HasComponent(); } public bool TryInsert(IEntity entity) diff --git a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs index d67721987a..ad43b62def 100644 --- a/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs +++ b/Content.Server/GameObjects/Components/Disposal/DisposalUnitComponent.cs @@ -143,7 +143,7 @@ namespace Content.Server.GameObjects.Components.Disposal } if (!entity.HasComponent() && - !entity.HasComponent()) + !entity.HasComponent()) { return false; } diff --git a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs index 1a0aeb7b21..064bc6fde4 100644 --- a/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs +++ b/Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs @@ -100,7 +100,7 @@ namespace Content.Server.GameObjects.Components.Doors // Disabled because it makes it suck hard to walk through double doors. - if (entity.HasComponent()) + if (entity.HasComponent()) { if (!entity.TryGetComponent(out var mover)) return; diff --git a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs index 931b95288a..3af194ba8d 100644 --- a/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs +++ b/Content.Server/GameObjects/Components/Instruments/InstrumentComponent.cs @@ -2,6 +2,7 @@ using System; using System.Linq; using Content.Server.GameObjects.Components.Mobs; +using Content.Server.GameObjects.EntitySystems; using Content.Server.Interfaces; using Content.Server.Mobs; using Content.Server.Utility; @@ -15,6 +16,7 @@ using Robust.Server.Interfaces.Player; using Robust.Server.Player; using Robust.Shared.Enums; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Network; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; @@ -323,7 +325,7 @@ namespace Content.Server.GameObjects.Components.Instruments } else { - StandingStateHelper.DropAllItemsInHands(mob, false); + EntitySystem.Get().DropAllItemsInHands(mob, false); } InstrumentPlayer = null; diff --git a/Content.Server/GameObjects/Components/Medical/HealingComponent.cs b/Content.Server/GameObjects/Components/Medical/HealingComponent.cs index f89e4a5508..0b751e24c4 100644 --- a/Content.Server/GameObjects/Components/Medical/HealingComponent.cs +++ b/Content.Server/GameObjects/Components/Medical/HealingComponent.cs @@ -32,7 +32,7 @@ namespace Content.Server.GameObjects.Components.Medical return; } - if (!eventArgs.Target.TryGetComponent(out IBodyManagerComponent body)) + if (!eventArgs.Target.TryGetComponent(out ISharedBodyManagerComponent body)) { return; } diff --git a/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs b/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs deleted file mode 100644 index beb3bfa2d3..0000000000 --- a/Content.Server/GameObjects/Components/Mobs/MobStateManager.cs +++ /dev/null @@ -1,513 +0,0 @@ -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.Server.GameObjects; -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) - { - if (entity.TryGetComponent(out AppearanceComponent appearance)) - { - appearance.SetData(DamageStateVisuals.State, DamageState.Alive); - } - - 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: - { - if (ruinable.DeadThreshold == null) - { - break; - } - - var modifier = (int) (ruinable.TotalDamage / (ruinable.DeadThreshold / 7f)); - - status.ChangeStatusEffectIcon(StatusEffect.Health, - "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); - - break; - } - case BodyManagerComponent body: - { - if (body.CriticalThreshold == null) - { - return; - } - - var modifier = (int) (body.TotalDamage / (body.CriticalThreshold / 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 AppearanceComponent appearance)) - { - appearance.SetData(DamageStateVisuals.State, DamageState.Critical); - } - - 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 AppearanceComponent appearance)) - { - appearance.SetData(DamageStateVisuals.State, DamageState.Dead); - } - - 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; - } - } -} diff --git a/Content.Server/GameObjects/Components/Mobs/State/CriticalState.cs b/Content.Server/GameObjects/Components/Mobs/State/CriticalState.cs new file mode 100644 index 0000000000..4592a90f51 --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/State/CriticalState.cs @@ -0,0 +1,51 @@ +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.GameObjects.Components.Mobs.State +{ + public class CriticalState : SharedCriticalState + { + public override void EnterState(IEntity entity) + { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Critical); + } + + 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(); + } + + EntitySystem.Get().Down(entity); + } + + public override void ExitState(IEntity entity) + { + EntitySystem.Get().Standing(entity); + + if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) + { + overlay.ClearOverlays(); + } + } + + public override void UpdateState(IEntity entity) { } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/State/DeadState.cs b/Content.Server/GameObjects/Components/Mobs/State/DeadState.cs new file mode 100644 index 0000000000..174fc68ef4 --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/State/DeadState.cs @@ -0,0 +1,62 @@ +using Content.Server.GameObjects.EntitySystems; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects.Components; +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.GameObjects.Components.Mobs.State +{ + public class DeadState : SharedDeadState + { + public override void EnterState(IEntity entity) + { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Dead); + } + + 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(); + } + + EntitySystem.Get().Down(entity); + + if (entity.TryGetComponent(out CollidableComponent collidable)) + { + collidable.CanCollide = false; + } + } + + public override void ExitState(IEntity entity) + { + EntitySystem.Get().Standing(entity); + + if (entity.TryGetComponent(out CollidableComponent collidable)) + { + collidable.CanCollide = true; + } + + if (entity.TryGetComponent(out ServerOverlayEffectsComponent overlay)) + { + overlay.ClearOverlays(); + } + } + + public override void UpdateState(IEntity entity) { } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/State/MobStateManager.cs b/Content.Server/GameObjects/Components/Mobs/State/MobStateManager.cs new file mode 100644 index 0000000000..c0e90dd9fa --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/State/MobStateManager.cs @@ -0,0 +1,70 @@ +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; +using Robust.Shared.GameObjects; + +namespace Content.Server.GameObjects.Components.Mobs.State +{ + [RegisterComponent] + [ComponentReference(typeof(SharedMobStateManagerComponent))] + public class MobStateManagerComponent : SharedMobStateManagerComponent + { + private readonly Dictionary _behavior = new Dictionary + { + {DamageState.Alive, new NormalState()}, + {DamageState.Critical, new CriticalState()}, + {DamageState.Dead, new DeadState()} + }; + + private DamageState _currentDamageState; + + protected override IReadOnlyDictionary Behavior => _behavior; + + public override IMobState CurrentMobState { get; protected set; } + + public override DamageState CurrentDamageState + { + get => _currentDamageState; + protected set + { + if (_currentDamageState == value) + { + return; + } + + if (_currentDamageState != DamageState.Invalid) + { + CurrentMobState.ExitState(Owner); + } + + _currentDamageState = value; + CurrentMobState = Behavior[CurrentDamageState]; + CurrentMobState.EnterState(Owner); + + Dirty(); + } + } + + 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(); + } + } + + public override ComponentState GetComponentState() + { + return new MobStateManagerComponentState(CurrentDamageState); + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/State/NormalState.cs b/Content.Server/GameObjects/Components/Mobs/State/NormalState.cs new file mode 100644 index 0000000000..507139e8bc --- /dev/null +++ b/Content.Server/GameObjects/Components/Mobs/State/NormalState.cs @@ -0,0 +1,79 @@ +using Content.Server.GameObjects.Components.Body; +using Content.Server.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.Components.Mobs; +using Content.Shared.GameObjects.Components.Mobs.State; +using Robust.Server.GameObjects; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.GameObjects.Components.Mobs.State +{ + public class NormalState : SharedNormalState + { + public override void EnterState(IEntity entity) + { + if (entity.TryGetComponent(out AppearanceComponent appearance)) + { + appearance.SetData(DamageStateVisuals.State, DamageState.Alive); + } + + UpdateState(entity); + } + + public override void ExitState(IEntity entity) { } + + public override 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: + { + if (ruinable.DeadThreshold == null) + { + break; + } + + var modifier = (int) (ruinable.TotalDamage / (ruinable.DeadThreshold / 7f)); + + status.ChangeStatusEffectIcon(StatusEffect.Health, + "/Textures/Interface/StatusEffects/Human/human" + modifier + ".png"); + + break; + } + case BodyManagerComponent body: + { + if (body.CriticalThreshold == null) + { + return; + } + + var modifier = (int) (body.TotalDamage / (body.CriticalThreshold / 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; + } + } + } + } +} diff --git a/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs b/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs index 82d42a098e..2e19baa7a1 100644 --- a/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs +++ b/Content.Server/GameObjects/Components/Mobs/StunnableComponent.cs @@ -1,7 +1,8 @@ -using Content.Server.Mobs; +using Content.Server.GameObjects.EntitySystems; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.GameObjects.Components.Movement; using Robust.Shared.GameObjects; +using Robust.Shared.GameObjects.Systems; using Robust.Shared.Interfaces.Timing; using Robust.Shared.IoC; using Robust.Shared.Timers; @@ -16,7 +17,7 @@ namespace Content.Server.GameObjects.Components.Mobs protected override void OnKnockdown() { - StandingStateHelper.Down(Owner); + EntitySystem.Get().Down(Owner); } public void CancelAll() @@ -33,7 +34,7 @@ namespace Content.Server.GameObjects.Components.Mobs if (KnockedDown) { - StandingStateHelper.Standing(Owner); + EntitySystem.Get().Standing(Owner); } KnockdownTimer = 0f; @@ -58,7 +59,7 @@ namespace Content.Server.GameObjects.Components.Mobs if (KnockdownTimer <= 0f) { - StandingStateHelper.Standing(Owner); + EntitySystem.Get().Standing(Owner); KnockdownTimer = 0f; Dirty(); diff --git a/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs b/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs index 47285443dc..828ec9bb36 100644 --- a/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs +++ b/Content.Server/GameObjects/Components/Recycling/RecyclerComponent.cs @@ -69,7 +69,7 @@ namespace Content.Server.GameObjects.Components.Recycling private bool CanGib(IEntity entity) { - return entity.HasComponent() && !_safe && Powered; + return entity.HasComponent() && !_safe && Powered; } private bool CanRecycle(IEntity entity, [MaybeNullWhen(false)] out ConstructionPrototype prototype) diff --git a/Content.Server/GameObjects/EntitySystems/StandingStateSystem.cs b/Content.Server/GameObjects/EntitySystems/StandingStateSystem.cs new file mode 100644 index 0000000000..49165efd87 --- /dev/null +++ b/Content.Server/GameObjects/EntitySystems/StandingStateSystem.cs @@ -0,0 +1,65 @@ +using Content.Server.Interfaces.GameObjects.Components.Items; +using Content.Shared.Audio; +using Content.Shared.GameObjects.Components.Rotation; +using Content.Shared.GameObjects.EntitySystems; +using JetBrains.Annotations; +using Robust.Server.GameObjects; +using Robust.Server.GameObjects.EntitySystems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Server.GameObjects.EntitySystems +{ + [UsedImplicitly] + public class StandingStateSystem : SharedStandingStateSystem + { + protected override bool OnDown(IEntity entity, bool playSound = true, bool dropItems = true, bool force = false) + { + if (!entity.TryGetComponent(out AppearanceComponent appearance)) + { + return false; + } + + var newState = RotationState.Horizontal; + appearance.TryGetData(RotationVisuals.RotationState, out var oldState); + + if (newState != oldState) + { + appearance.SetData(RotationVisuals.RotationState, newState); + } + + if (playSound) + { + var file = AudioHelpers.GetRandomFileFromSoundCollection("bodyfall"); + Get().PlayFromEntity(file, entity, AudioHelpers.WithVariation(0.25f)); + } + + return true; + } + + protected override bool OnStand(IEntity entity) + { + if (!entity.TryGetComponent(out AppearanceComponent appearance)) return false; + + appearance.TryGetData(RotationVisuals.RotationState, out var oldState); + var newState = RotationState.Vertical; + + if (newState == oldState) return false; + + appearance.SetData(RotationVisuals.RotationState, newState); + + return true; + } + + public override void DropAllItemsInHands(IEntity entity, bool doMobChecks = true) + { + base.DropAllItemsInHands(entity, doMobChecks); + + if (!entity.TryGetComponent(out IHandsComponent hands)) return; + + foreach (var heldItem in hands.GetAllHeldItems()) + { + hands.Drop(heldItem.Owner, doMobChecks); + } + } + } +} diff --git a/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs b/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs index a40029da44..2f436c922a 100644 --- a/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs +++ b/Content.Server/GameObjects/EntitySystems/StationEvents/RadiationPulseSystem.cs @@ -34,7 +34,7 @@ namespace Content.Server.GameObjects.EntitySystems.StationEvents public override void Initialize() { base.Initialize(); - _speciesQuery = new TypeEntityQuery(typeof(IBodyManagerComponent)); + _speciesQuery = new TypeEntityQuery(typeof(ISharedBodyManagerComponent)); } public override void Update(float frameTime) diff --git a/Content.Server/Mobs/StandingStateHelper.cs b/Content.Server/Mobs/StandingStateHelper.cs deleted file mode 100644 index 3169b80503..0000000000 --- a/Content.Server/Mobs/StandingStateHelper.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Content.Server.Interfaces.GameObjects.Components.Items; -using Content.Shared.Audio; -using Content.Shared.GameObjects.Components.Rotation; -using Content.Shared.GameObjects.EntitySystems; -using Robust.Server.GameObjects; -using Robust.Server.GameObjects.EntitySystems; -using Robust.Shared.Interfaces.GameObjects; -using Robust.Shared.IoC; - -namespace Content.Server.Mobs -{ - public static class StandingStateHelper - { - /// - /// Set's the mob standing state to down. - /// - /// The mob in question - /// Whether to play a sound when falling down or not - /// Whether to make the mob drop all the items on his hands - /// Whether or not to check if the entity can fall. - /// False if the mob was already downed or couldn't set the state - public static bool Down(IEntity entity, bool playSound = true, bool dropItems = true, bool force = false) - { - if (dropItems) - { - DropAllItemsInHands(entity, false); - } - - if (!force && !EffectBlockerSystem.CanFall(entity)) - { - return false; - } - - if (!entity.TryGetComponent(out AppearanceComponent appearance)) - { - return false; - } - - var newState = RotationState.Horizontal; - appearance.TryGetData(RotationVisuals.RotationState, out var oldState); - - if (newState != oldState) - { - appearance.SetData(RotationVisuals.RotationState, newState); - } - - if (playSound) - { - IoCManager.Resolve().GetEntitySystem() - .PlayFromEntity(AudioHelpers.GetRandomFileFromSoundCollection("bodyfall"), entity, AudioHelpers.WithVariation(0.25f)); - } - - return true; - } - - /// - /// Sets the mob's standing state to standing. - /// - /// The mob in question. - /// False if the mob was already standing or couldn't set the state - public static bool Standing(IEntity entity) - { - if (!entity.TryGetComponent(out AppearanceComponent appearance)) return false; - appearance.TryGetData(RotationVisuals.RotationState, out var oldState); - var newState = RotationState.Vertical; - if (newState == oldState) - return false; - - appearance.SetData(RotationVisuals.RotationState, newState); - - return true; - } - - public static void DropAllItemsInHands(IEntity entity, bool doMobChecks = true) - { - if (!entity.TryGetComponent(out IHandsComponent hands)) return; - - foreach (var heldItem in hands.GetAllHeldItems()) - { - hands.Drop(heldItem.Owner, doMobChecks); - } - } - } -} diff --git a/Content.Shared/GameObjects/Components/Body/IBodyManagerComponent.cs b/Content.Shared/GameObjects/Components/Body/ISharedBodyManagerComponent.cs similarity index 52% rename from Content.Shared/GameObjects/Components/Body/IBodyManagerComponent.cs rename to Content.Shared/GameObjects/Components/Body/ISharedBodyManagerComponent.cs index bc3ee631f2..9a8c026460 100644 --- a/Content.Shared/GameObjects/Components/Body/IBodyManagerComponent.cs +++ b/Content.Shared/GameObjects/Components/Body/ISharedBodyManagerComponent.cs @@ -1,9 +1,8 @@ using Content.Shared.GameObjects.Components.Damage; -using Robust.Shared.Interfaces.GameObjects; namespace Content.Shared.GameObjects.Components.Body { - public interface IBodyManagerComponent : IDamageableComponent + public interface ISharedBodyManagerComponent : IDamageableComponent { } } diff --git a/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs b/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs index d78b93320f..2cb0022e88 100644 --- a/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs +++ b/Content.Shared/GameObjects/Components/Body/SharedBodyManagerComponent.cs @@ -5,7 +5,7 @@ using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.Components.Body { - public abstract class SharedBodyManagerComponent : DamageableComponent, IBodyManagerComponent + public abstract class SharedBodyManagerComponent : DamageableComponent, ISharedBodyManagerComponent { public override string Name => "BodyManager"; diff --git a/Content.Shared/GameObjects/Components/Damage/DamageState.cs b/Content.Shared/GameObjects/Components/Damage/DamageState.cs index 7ec6a260b7..ab49364b6f 100644 --- a/Content.Shared/GameObjects/Components/Damage/DamageState.cs +++ b/Content.Shared/GameObjects/Components/Damage/DamageState.cs @@ -16,6 +16,7 @@ namespace Content.Shared.GameObjects.Components.Damage [Serializable, NetSerializable] public enum DamageState { + Invalid = 0, Alive, Critical, Dead diff --git a/Content.Shared/GameObjects/Components/Damage/IDamageableComponent.cs b/Content.Shared/GameObjects/Components/Damage/IDamageableComponent.cs index f979d326c6..4290cf4ac2 100644 --- a/Content.Shared/GameObjects/Components/Damage/IDamageableComponent.cs +++ b/Content.Shared/GameObjects/Components/Damage/IDamageableComponent.cs @@ -143,8 +143,8 @@ namespace Content.Shared.GameObjects.Components.Damage _ => throw new ArgumentOutOfRangeException() }; - ChangeDamage(DamageType.Piercing, damage, false, null); - ChangeDamage(DamageType.Heat, damage, false, null); + ChangeDamage(DamageType.Piercing, damage, false); + ChangeDamage(DamageType.Heat, damage, false); } } diff --git a/Content.Shared/GameObjects/Components/Mobs/State/IMobState.cs b/Content.Shared/GameObjects/Components/Mobs/State/IMobState.cs new file mode 100644 index 0000000000..f22265dd92 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Mobs/State/IMobState.cs @@ -0,0 +1,29 @@ +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Mobs.State +{ + /// + /// 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); + } +} diff --git a/Content.Shared/GameObjects/Components/Mobs/State/SharedCriticalState.cs b/Content.Shared/GameObjects/Components/Mobs/State/SharedCriticalState.cs new file mode 100644 index 0000000000..bd50cc36ca --- /dev/null +++ b/Content.Shared/GameObjects/Components/Mobs/State/SharedCriticalState.cs @@ -0,0 +1,76 @@ +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Mobs.State +{ + /// + /// A state in which an entity is disabled from acting due to sufficient damage (considered unconscious). + /// + public abstract class SharedCriticalState : IMobState + { + public abstract void EnterState(IEntity entity); + + public abstract void ExitState(IEntity entity); + + public abstract void UpdateState(IEntity entity); + + public bool CanInteract() + { + return false; + } + + public bool CanMove() + { + return false; + } + + public bool CanUse() + { + return false; + } + + public bool CanThrow() + { + return false; + } + + public bool CanSpeak() + { + return false; + } + + public bool CanDrop() + { + return false; + } + + public bool CanPickup() + { + return false; + } + + public bool CanEmote() + { + return false; + } + + public bool CanAttack() + { + return false; + } + + public bool CanEquip() + { + return false; + } + + public bool CanUnequip() + { + return false; + } + + public bool CanChangeDirection() + { + return false; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Mobs/State/SharedDeadState.cs b/Content.Shared/GameObjects/Components/Mobs/State/SharedDeadState.cs new file mode 100644 index 0000000000..6dc85a07cf --- /dev/null +++ b/Content.Shared/GameObjects/Components/Mobs/State/SharedDeadState.cs @@ -0,0 +1,73 @@ +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Mobs.State +{ + public abstract class SharedDeadState : IMobState + { + public abstract void EnterState(IEntity entity); + + public abstract void ExitState(IEntity entity); + + public abstract void UpdateState(IEntity entity); + + public bool CanInteract() + { + return false; + } + + public bool CanMove() + { + return false; + } + + public bool CanUse() + { + return false; + } + + public bool CanThrow() + { + return false; + } + + public bool CanSpeak() + { + return false; + } + + public bool CanDrop() + { + return false; + } + + public bool CanPickup() + { + return false; + } + + public bool CanEmote() + { + return false; + } + + public bool CanAttack() + { + return false; + } + + public bool CanEquip() + { + return false; + } + + public bool CanUnequip() + { + return false; + } + + public bool CanChangeDirection() + { + return false; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Mobs/State/SharedMobStateManagerComponent.cs b/Content.Shared/GameObjects/Components/Mobs/State/SharedMobStateManagerComponent.cs new file mode 100644 index 0000000000..98a6899e8f --- /dev/null +++ b/Content.Shared/GameObjects/Components/Mobs/State/SharedMobStateManagerComponent.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using Content.Shared.GameObjects.Components.Damage; +using Content.Shared.GameObjects.EntitySystems; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; + +namespace Content.Shared.GameObjects.Components.Mobs.State +{ + /// + /// 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. + /// + public abstract class SharedMobStateManagerComponent : Component, IOnHealthChangedBehavior, IActionBlocker + { + public override string Name => "MobStateManager"; + + public override uint? NetID => ContentNetIDs.MOB_STATE_MANAGER; + + protected abstract IReadOnlyDictionary Behavior { get; } + + public virtual IMobState CurrentMobState { get; protected set; } + + public virtual DamageState CurrentDamageState { get; protected set; } + + public override void OnAdd() + { + base.OnAdd(); + + CurrentDamageState = DamageState.Alive; + } + + public override void Initialize() + { + base.Initialize(); + + CurrentMobState = Behavior[CurrentDamageState]; + CurrentMobState.EnterState(Owner); + CurrentMobState.UpdateState(Owner); + } + + 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); + } + } + + [Serializable, NetSerializable] + public class MobStateManagerComponentState : ComponentState + { + public readonly DamageState DamageState; + + public MobStateManagerComponentState(DamageState damageState) : base(ContentNetIDs.MOB_STATE_MANAGER) + { + DamageState = damageState; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Mobs/State/SharedNormalState.cs b/Content.Shared/GameObjects/Components/Mobs/State/SharedNormalState.cs new file mode 100644 index 0000000000..6b68620834 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Mobs/State/SharedNormalState.cs @@ -0,0 +1,76 @@ +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.Components.Mobs.State +{ + /// + /// The standard state an entity is in; no negative effects. + /// + public abstract class SharedNormalState : IMobState + { + public abstract void EnterState(IEntity entity); + + public abstract void ExitState(IEntity entity); + + public abstract void UpdateState(IEntity entity); + + public bool CanInteract() + { + return true; + } + + public bool CanMove() + { + return true; + } + + public bool CanUse() + { + return true; + } + + public bool CanThrow() + { + return true; + } + + public bool CanSpeak() + { + return true; + } + + public bool CanDrop() + { + return true; + } + + public bool CanPickup() + { + return true; + } + + public bool CanEmote() + { + return true; + } + + public bool CanAttack() + { + return true; + } + + public bool CanEquip() + { + return true; + } + + public bool CanUnequip() + { + return true; + } + + public bool CanChangeDirection() + { + return true; + } + } +} diff --git a/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs b/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs index a3b3794d3e..de864025fc 100644 --- a/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs +++ b/Content.Shared/GameObjects/Components/Movement/SharedPlayerInputMoverComponent.cs @@ -270,7 +270,7 @@ namespace Content.Shared.GameObjects.Components.Movement bool ICollideSpecial.PreventCollide(IPhysBody collidedWith) { // Don't collide with other mobs - return collidedWith.Entity.HasComponent(); + return collidedWith.Entity.HasComponent(); } [Serializable, NetSerializable] diff --git a/Content.Shared/GameObjects/Components/Rotation/RotationComponent.cs b/Content.Shared/GameObjects/Components/Rotation/SharedRotationComponent.cs similarity index 93% rename from Content.Shared/GameObjects/Components/Rotation/RotationComponent.cs rename to Content.Shared/GameObjects/Components/Rotation/SharedRotationComponent.cs index 150ed62a30..acdb33b7db 100644 --- a/Content.Shared/GameObjects/Components/Rotation/RotationComponent.cs +++ b/Content.Shared/GameObjects/Components/Rotation/SharedRotationComponent.cs @@ -1,5 +1,4 @@ using System; -using Robust.Shared.GameObjects; using Robust.Shared.Serialization; namespace Content.Shared.GameObjects.Components.Rotation diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index 3f127c68c4..6210936e41 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -72,6 +72,8 @@ public const uint HANDCUFFS = 1066; public const uint BATTERY_BARREL = 1067; public const uint SUSPICION_ROLE = 1068; + public const uint ROTATION = 1069; + public const uint MOB_STATE_MANAGER = 1070; // Net IDs for integration tests. public const uint PREDICTION_TEST = 10001; diff --git a/Content.Shared/GameObjects/EntitySystems/SharedStandingStateSystem.cs b/Content.Shared/GameObjects/EntitySystems/SharedStandingStateSystem.cs new file mode 100644 index 0000000000..9526b5414a --- /dev/null +++ b/Content.Shared/GameObjects/EntitySystems/SharedStandingStateSystem.cs @@ -0,0 +1,50 @@ +using Robust.Shared.GameObjects.Systems; +using Robust.Shared.Interfaces.GameObjects; + +namespace Content.Shared.GameObjects.EntitySystems +{ + public abstract class SharedStandingStateSystem : EntitySystem + { + protected abstract bool OnDown(IEntity entity, bool playSound = true, bool dropItems = true, + bool force = false); + + protected abstract bool OnStand(IEntity entity); + + /// + /// Set's the mob standing state to down. + /// + /// The mob in question + /// Whether to play a sound when falling down or not + /// Whether to make the mob drop all the items on his hands + /// Whether or not to check if the entity can fall. + /// False if the mob was already downed or couldn't set the state + public bool Down(IEntity entity, bool playSound = true, bool dropItems = true, bool force = false) + { + if (dropItems) + { + DropAllItemsInHands(entity, false); + } + + if (!force && !EffectBlockerSystem.CanFall(entity)) + { + return false; + } + + return OnDown(entity, playSound, dropItems, force); + } + + /// + /// Sets the mob's standing state to standing. + /// + /// The mob in question. + /// False if the mob was already standing or couldn't set the state + public bool Standing(IEntity entity) + { + return OnStand(entity); + } + + public virtual void DropAllItemsInHands(IEntity entity, bool doMobChecks = true) + { + } + } +}