]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Add EmoteOnDamage comp/system for zombies (#14371)
author0x6273 <0x40@keemail.me>
Thu, 23 Mar 2023 14:52:46 +0000 (15:52 +0100)
committerGitHub <noreply@github.com>
Thu, 23 Mar 2023 14:52:46 +0000 (10:52 -0400)
Content.Server/Chat/EmoteOnDamageComponent.cs [new file with mode: 0644]
Content.Server/Chat/Systems/EmoteOnDamageSystem.cs [new file with mode: 0644]
Content.Server/Zombies/ActiveZombieComponent.cs [deleted file]
Content.Server/Zombies/ZombieSystem.cs
Content.Server/Zombies/ZombifyOnDeathSystem.cs

diff --git a/Content.Server/Chat/EmoteOnDamageComponent.cs b/Content.Server/Chat/EmoteOnDamageComponent.cs
new file mode 100644 (file)
index 0000000..f2c7dc5
--- /dev/null
@@ -0,0 +1,51 @@
+namespace Content.Server.Chat;
+
+using Content.Server.Chat.Systems;
+using Content.Shared.Chat.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype.Set;
+
+/// <summary>
+/// Causes an entity to automatically emote when taking damage.
+/// </summary>
+[RegisterComponent, Access(typeof(EmoteOnDamageSystem))]
+public sealed class EmoteOnDamageComponent : Component
+{
+    /// <summary>
+    /// Chance of preforming an emote when taking damage and not on cooldown.
+    /// </summary>
+    [DataField("emoteChance"), ViewVariables(VVAccess.ReadWrite)]
+    public float EmoteChance = 0.5f;
+
+    /// <summary>
+    /// A set of emotes that will be randomly picked from.
+    /// <see cref="EmotePrototype"/>
+    /// </summary>
+    [DataField("emotes", customTypeSerializer: typeof(PrototypeIdHashSetSerializer<EmotePrototype>)), ViewVariables(VVAccess.ReadWrite)]
+    public HashSet<string> Emotes = new();
+
+    /// <summary>
+    /// Also send the emote in chat.
+    /// <summary>
+    [DataField("withChat"), ViewVariables(VVAccess.ReadWrite)]
+    public bool WithChat = false;
+
+    /// <summary>
+    /// Hide the chat message from the chat window, only showing the popup.
+    /// This does nothing if WithChat is false.
+    /// <summary>
+    [DataField("hiddenFromChatWindow")]
+    public bool HiddenFromChatWindow = false;
+
+    /// <summary>
+    /// The simulation time of the last emote preformed due to taking damage.
+    /// </summary>
+    [DataField("lastEmoteTime", customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan LastEmoteTime = TimeSpan.Zero;
+
+    /// <summary>
+    /// The cooldown between emotes.
+    /// </summary>
+    [DataField("emoteCooldown"), ViewVariables(VVAccess.ReadWrite)]
+    public TimeSpan EmoteCooldown = TimeSpan.FromSeconds(2);
+}
diff --git a/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs b/Content.Server/Chat/Systems/EmoteOnDamageSystem.cs
new file mode 100644 (file)
index 0000000..ae14399
--- /dev/null
@@ -0,0 +1,82 @@
+namespace Content.Server.Chat.Systems;
+
+using Content.Shared.Chat.Prototypes;
+using Content.Shared.Damage;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+public sealed class EmoteOnDamageSystem : EntitySystem
+{
+    [Dependency] private readonly IGameTiming _gameTiming = default!;
+    [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+    [Dependency] private readonly IRobustRandom _random = default!;
+    [Dependency] private readonly ChatSystem _chatSystem = default!;
+
+    public override void Initialize()
+    {
+        base.Initialize();
+
+        SubscribeLocalEvent<EmoteOnDamageComponent, EntityUnpausedEvent>(OnUnpaused);
+        SubscribeLocalEvent<EmoteOnDamageComponent, DamageChangedEvent>(OnDamage);
+    }
+
+    private void OnUnpaused(EntityUid uid, EmoteOnDamageComponent emoteOnDamage, ref EntityUnpausedEvent args)
+    {
+        emoteOnDamage.LastEmoteTime += args.PausedTime;
+    }
+
+    private void OnDamage(EntityUid uid, EmoteOnDamageComponent emoteOnDamage, DamageChangedEvent args)
+    {
+        if (!args.DamageIncreased)
+            return;
+
+        if (emoteOnDamage.LastEmoteTime + emoteOnDamage.EmoteCooldown > _gameTiming.CurTime)
+            return;
+
+        if (emoteOnDamage.Emotes.Count == 0)
+            return;
+
+        if (!_random.Prob(emoteOnDamage.EmoteChance))
+            return;
+
+        var emote = _random.Pick(emoteOnDamage.Emotes);
+        if (emoteOnDamage.WithChat)
+        {
+            _chatSystem.TryEmoteWithChat(uid, emote, emoteOnDamage.HiddenFromChatWindow);
+        }
+        else
+        {
+            _chatSystem.TryEmoteWithoutChat(uid,emote);
+        }
+
+        emoteOnDamage.LastEmoteTime = _gameTiming.CurTime;
+    }
+
+    /// <summary>
+    /// Try to add an emote to the entity, which will be performed at an interval.
+    /// </summary>
+    public bool AddEmote(EntityUid uid, string emotePrototypeId, EmoteOnDamageComponent? emoteOnDamage = null)
+    {
+        if (!Resolve(uid, ref emoteOnDamage, logMissing: false))
+            return false;
+
+        DebugTools.Assert(_prototypeManager.HasIndex<EmotePrototype>(emotePrototypeId), "Prototype not found. Did you make a typo?");
+
+        return emoteOnDamage.Emotes.Add(emotePrototypeId);
+    }
+
+    /// <summary>
+    /// Stop preforming an emote.
+    /// </summary>
+    public bool RemoveEmote(EntityUid uid, string emotePrototypeId, EmoteOnDamageComponent? emoteOnDamage = null)
+    {
+        if (!Resolve(uid, ref emoteOnDamage, logMissing: false))
+            return false;
+
+        DebugTools.Assert(_prototypeManager.HasIndex<EmotePrototype>(emotePrototypeId), "Prototype not found. Did you make a typo?");
+
+        return emoteOnDamage.Emotes.Remove(emotePrototypeId);
+    }
+}
diff --git a/Content.Server/Zombies/ActiveZombieComponent.cs b/Content.Server/Zombies/ActiveZombieComponent.cs
deleted file mode 100644 (file)
index f658433..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-namespace Content.Server.Zombies;
-
-/// <summary>
-/// Indicates a zombie that is "alive", i.e not crit/dead.
-/// Causes it to emote when damaged.
-/// TODO: move this to generic EmoteWhenDamaged comp/system.
-/// </summary>
-[RegisterComponent]
-public sealed class ActiveZombieComponent : Component
-{
-    /// <summary>
-    /// What emote to preform.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    public string GroanEmoteId = "Scream";
-
-    /// <summary>
-    /// Minimum time between groans.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan DamageGroanCooldown = TimeSpan.FromSeconds(2);
-
-    /// <summary>
-    /// Chance to groan.
-    /// </summary>
-    public float DamageGroanChance = 0.5f;
-
-    /// <summary>
-    /// The last time the zombie groaned from taking damage.
-    /// </summary>
-    [ViewVariables(VVAccess.ReadWrite)]
-    public TimeSpan LastDamageGroan = TimeSpan.Zero;
-}
index 181b14a439b17490aa1ec25201e4b6abdc6c3f55..eafc6a54e0b2f2cac2a70fcfadf30bf9a839f85d 100644 (file)
@@ -12,7 +12,6 @@ using Content.Shared.Bed.Sleep;
 using Content.Shared.Chemistry.Components;
 using Content.Server.Emoting.Systems;
 using Content.Server.Speech.EntitySystems;
-using Content.Shared.Damage;
 using Content.Shared.Disease.Events;
 using Content.Shared.Inventory;
 using Content.Shared.Mobs;
@@ -33,6 +32,7 @@ namespace Content.Server.Zombies
         [Dependency] private readonly ServerInventorySystem _inv = default!;
         [Dependency] private readonly ChatSystem _chat = default!;
         [Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
+        [Dependency] private readonly EmoteOnDamageSystem _emoteOnDamage = default!;
         [Dependency] private readonly IGameTiming _gameTiming = default!;
         [Dependency] private readonly IPrototypeManager _protoManager = default!;
         [Dependency] private readonly IRobustRandom _robustRandom = default!;
@@ -49,12 +49,11 @@ namespace Content.Server.Zombies
             SubscribeLocalEvent<ZombieComponent, MeleeHitEvent>(OnMeleeHit);
             SubscribeLocalEvent<ZombieComponent, MobStateChangedEvent>(OnMobState);
             SubscribeLocalEvent<ZombieComponent, CloningEvent>(OnZombieCloning);
-            SubscribeLocalEvent<ActiveZombieComponent, DamageChangedEvent>(OnDamage);
-            SubscribeLocalEvent<ActiveZombieComponent, AttemptSneezeCoughEvent>(OnSneeze);
-            SubscribeLocalEvent<ActiveZombieComponent, TryingToSleepEvent>(OnSleepAttempt);
+            SubscribeLocalEvent<ZombieComponent, AttemptSneezeCoughEvent>(OnSneeze);
+            SubscribeLocalEvent<ZombieComponent, TryingToSleepEvent>(OnSleepAttempt);
         }
 
-        private void OnSleepAttempt(EntityUid uid, ActiveZombieComponent component, ref TryingToSleepEvent args)
+        private void OnSleepAttempt(EntityUid uid, ZombieComponent component, ref TryingToSleepEvent args)
         {
             args.Cancelled = true;
         }
@@ -76,11 +75,11 @@ namespace Content.Server.Zombies
 
         private void OnMobState(EntityUid uid, ZombieComponent component, MobStateChangedEvent args)
         {
-            //BUG: this won't work when an entity becomes a zombie some other way, such as admin smite
             if (args.NewMobState == MobState.Alive)
             {
                 // Groaning when damaged
-                EnsureComp<ActiveZombieComponent>(uid);
+                EnsureComp<EmoteOnDamageComponent>(uid);
+                _emoteOnDamage.AddEmote(uid, "Scream");
 
                 // Random groaning
                 EnsureComp<AutoEmoteComponent>(uid);
@@ -89,20 +88,14 @@ namespace Content.Server.Zombies
             else
             {
                 // Stop groaning when damaged
-                RemComp<ActiveZombieComponent>(uid);
+                _emoteOnDamage.RemoveEmote(uid, "Scream");
 
                 // Stop random groaning
                 _autoEmote.RemoveEmote(uid, "ZombieGroan");
             }
         }
 
-        private void OnDamage(EntityUid uid, ActiveZombieComponent component, DamageChangedEvent args)
-        {
-            if (args.DamageIncreased)
-                AttemptDamageGroan(uid, component);
-        }
-
-        private void OnSneeze(EntityUid uid, ActiveZombieComponent component, ref AttemptSneezeCoughEvent args)
+        private void OnSneeze(EntityUid uid, ZombieComponent component, ref AttemptSneezeCoughEvent args)
         {
             args.Cancelled = true;
         }
@@ -179,18 +172,6 @@ namespace Content.Server.Zombies
             }
         }
 
-        private void AttemptDamageGroan(EntityUid uid, ActiveZombieComponent component)
-        {
-            if (component.LastDamageGroan + component.DamageGroanCooldown > _gameTiming.CurTime)
-                return;
-
-            if (_robustRandom.Prob(component.DamageGroanChance))
-                return;
-
-            _chat.TryEmoteWithoutChat(uid, component.GroanEmoteId);
-            component.LastDamageGroan = _gameTiming.CurTime;
-        }
-
         /// <summary>
         ///     This is the function to call if you want to unzombify an entity.
         /// </summary>
index 9202e7fcefe7cce939aa351c6c29c7bd8dc008cc..7cf12b1328475b59e7d6ab89079c40455b266abc 100644 (file)
@@ -26,8 +26,11 @@ using Content.Server.Humanoid;
 using Content.Server.IdentityManagement;
 using Content.Shared.Humanoid;
 using Content.Shared.Mobs;
+using Content.Shared.Mobs.Components;
 using Content.Shared.Movement.Systems;
 using Content.Shared.Weapons.Melee;
+using Content.Server.Chat;
+using Content.Server.Chat.Systems;
 
 namespace Content.Server.Zombies
 {
@@ -47,6 +50,8 @@ namespace Content.Server.Zombies
         [Dependency] private readonly HumanoidAppearanceSystem _sharedHuApp = default!;
         [Dependency] private readonly IdentitySystem _identity = default!;
         [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
+        [Dependency] private readonly AutoEmoteSystem _autoEmote = default!;
+        [Dependency] private readonly EmoteOnDamageSystem _emoteOnDamage = default!;
         [Dependency] private readonly IChatManager _chatMan = default!;
         [Dependency] private readonly IPrototypeManager _proto = default!;
 
@@ -65,7 +70,7 @@ namespace Content.Server.Zombies
             if (args.NewMobState == MobState.Dead ||
                 args.NewMobState == MobState.Critical)
             {
-                ZombifyEntity(uid);
+                ZombifyEntity(uid, args.Component);
             }
         }
 
@@ -81,12 +86,15 @@ namespace Content.Server.Zombies
         ///     rewrite this, but this is how it shall lie eternal. Turn back now.
         ///     -emo
         /// </remarks>
-        public void ZombifyEntity(EntityUid target)
+        public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null)
         {
             //Don't zombfiy zombies
             if (HasComp<ZombieComponent>(target))
                 return;
 
+            if (!Resolve(target, ref mobState, logMissing: false))
+                return;
+
             //you're a real zombie now, son.
             var zombiecomp = AddComp<ZombieComponent>(target);
 
@@ -117,6 +125,17 @@ namespace Content.Server.Zombies
             melee.Range = 1.5f;
             Dirty(melee);
 
+            if (mobState.CurrentState == MobState.Alive)
+            {
+                // Groaning when damaged
+                EnsureComp<EmoteOnDamageComponent>(target);
+                _emoteOnDamage.AddEmote(target, "Scream");
+
+                // Random groaning
+                EnsureComp<AutoEmoteComponent>(target);
+                _autoEmote.AddEmote(target, "ZombieGroan");
+            }
+
             //We have specific stuff for humanoid zombies because they matter more
             if (TryComp<HumanoidAppearanceComponent>(target, out var huApComp)) //huapcomp
             {