]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Zombie virus delayed from 20-30 minutes from rule start. (#16346)
authorTom Leys <tom@crump-leys.com>
Tue, 16 May 2023 05:59:39 +0000 (17:59 +1200)
committerGitHub <noreply@github.com>
Tue, 16 May 2023 05:59:39 +0000 (01:59 -0400)
Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs
Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs
Content.Server/GameTicking/Rules/ZombieRuleSystem.cs
Content.Server/Zombies/PendingZombieComponent.cs
Content.Server/Zombies/ZombieSystem.cs
Content.Server/Zombies/ZombifyOnDeathSystem.cs
Content.Shared/Zombies/ZombieComponent.cs
Resources/Locale/en-US/game-ticking/game-presets/preset-zombies.ftl

index 01caaa4e07be63a352d0b6d5f1352fc4c3699bfb..7b6ca14ea16049532d407ff343375a3e85f5d48f 100644 (file)
@@ -56,11 +56,7 @@ public sealed partial class AdminVerbSystem
             Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Wallmounts/signs.rsi"), "bio"),
             Act = () =>
             {
-                TryComp(args.Target, out MindComponent? mindComp);
-                if (mindComp == null || mindComp.Mind == null)
-                    return;
-
-                _zombify.ZombifyEntity(targetMindComp.Owner);
+                _zombify.ZombifyEntity(args.Target);
             },
             Impact = LogImpact.High,
             Message = Loc.GetString("admin-verb-make-zombie"),
index 17d87a1ec55624298fe755ef656b9b976e98887f..869765c14fcddec66531132fed05c9b5a3492cf8 100644 (file)
@@ -8,4 +8,28 @@ public sealed class ZombieRuleComponent : Component
 
     public string PatientZeroPrototypeID = "InitialInfected";
     public const string ZombifySelfActionPrototype = "TurnUndead";
+
+    /// <summary>
+    ///   After this many seconds the players will be forced to turn into zombies (at minimum)
+    ///   Defaults to 20 minutes. 20*60 = 1200 seconds.
+    ///
+    ///   Zombie time for a given player is:
+    ///   random MinZombieForceSecs to MaxZombieForceSecs + up to PlayerZombieForceVariation
+    /// </summary>
+    [DataField("minZombieForceSecs"), ViewVariables(VVAccess.ReadWrite)]
+    public float MinZombieForceSecs = 1200;
+
+    /// <summary>
+    ///   After this many seconds the players will be forced to turn into zombies (at maximum)
+    ///   Defaults to 30 minutes. 30*60 = 1800 seconds.
+    /// </summary>
+    [DataField("maxZombieForceSecs"), ViewVariables(VVAccess.ReadWrite)]
+    public float MaxZombieForceSecs = 1800;
+
+    /// <summary>
+    ///   How many additional seconds each player will get (at random) to scatter forced zombies over time.
+    ///   Defaults to 2 minutes. 2*60 = 120 seconds.
+    /// </summary>
+    [DataField("playerZombieForceVariationSecs"), ViewVariables(VVAccess.ReadWrite)]
+    public float PlayerZombieForceVariationSecs = 120;
 }
index a857c5c1c80598796bdd88b132d0b7e9fb8ec028..f7975e15602fea67fb25b86ab5e5db4260860ef3 100644 (file)
@@ -251,6 +251,10 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
             (int) Math.Min(
                 Math.Floor((double) playerList.Count / playersPerInfected), maxInfected));
 
+        // How long the zombies have as a group to decide to begin their attack.
+        //   Varies randomly from 20 to 30 minutes. After this the virus begins and they start
+        //   taking zombie virus damage.
+        var groupTimelimit = _random.NextFloat(component.MinZombieForceSecs, component.MaxZombieForceSecs);
         for (var i = 0; i < numInfected; i++)
         {
             IPlayerSession zombie;
@@ -283,9 +287,13 @@ public sealed class ZombieRuleSystem : GameRuleSystem<ZombieRuleComponent>
             mind.AddRole(new TraitorRole(mind, _prototypeManager.Index<AntagPrototype>(component.PatientZeroPrototypeID)));
 
             var inCharacterName = string.Empty;
+            // Create some variation between the times of each zombie, relative to the time of the group as a whole.
+            var personalDelay = _random.NextFloat(0.0f, component.PlayerZombieForceVariationSecs);
             if (mind.OwnedEntity != null)
             {
-                EnsureComp<PendingZombieComponent>(mind.OwnedEntity.Value);
+                var pending = EnsureComp<PendingZombieComponent>(mind.OwnedEntity.Value);
+                // Only take damage after this many seconds
+                pending.InfectedSecs = -(int)(groupTimelimit + personalDelay);
                 EnsureComp<ZombifyOnDeathComponent>(mind.OwnedEntity.Value);
                 inCharacterName = MetaData(mind.OwnedEntity.Value).EntityName;
 
index 957e2a712a85736093898c4fec258615b6c1b024..83e86467f6d083d97b6e99681e238155f115ac62 100644 (file)
@@ -14,8 +14,7 @@ public sealed class PendingZombieComponent : Component
     {
         DamageDict = new ()
         {
-            { "Blunt", 0.5 },
-            { "Cellular", 0.2 },
+            { "Blunt", 0.8 },
             { "Toxin", 0.2 },
         }
     };
@@ -23,7 +22,37 @@ public sealed class PendingZombieComponent : Component
     [DataField("nextTick", customTypeSerializer:typeof(TimeOffsetSerializer))]
     public TimeSpan NextTick;
 
-    // Scales damage over time.
-    [DataField("infectedSecs")]
+    /// <summary>
+    /// Scales damage over time.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("infectedSecs")]
     public int InfectedSecs;
+
+    /// <summary>
+    /// Number of seconds that a typical infection will last before the player is totally overwhelmed with damage and
+    ///   dies.
+    /// </summary>
+    [ViewVariables(VVAccess.ReadWrite), DataField("maxInfectionLength")]
+    public float MaxInfectionLength = 120f;
+
+    /// <summary>
+    /// Infection warnings are shown as popups, times are in seconds.
+    ///   -ve times shown to initial zombies (once timer counts from -ve to 0 the infection starts)
+    ///   +ve warnings are in seconds after being bitten
+    /// </summary>
+    [DataField("infectionWarnings")]
+    public Dictionary<int, string> InfectionWarnings = new()
+    {
+        {-45, "zombie-infection-warning"},
+        {-30, "zombie-infection-warning"},
+        {10, "zombie-infection-underway"},
+        {25, "zombie-infection-underway"},
+    };
+
+    /// <summary>
+    /// A minimum multiplier applied to Damage once you are in crit to get you dead and ready for your next life
+    ///   as fast as possible.
+    /// </summary>
+    [DataField("minimumCritMultiplier")]
+    public float MinimumCritMultiplier = 10;
 }
index 8d5d0cd793ed327c03f8c96c6b48301071681a9b..b051a7f89a620c11cdd77d6b29a98dc536f3958c 100644 (file)
@@ -55,6 +55,7 @@ namespace Content.Server.Zombies
             SubscribeLocalEvent<ZombieComponent, TryingToSleepEvent>(OnSleepAttempt);
 
             SubscribeLocalEvent<PendingZombieComponent, MapInitEvent>(OnPendingMapInit);
+            SubscribeLocalEvent<PendingZombieComponent, MobStateChangedEvent>(OnPendingMobState);
         }
 
         private void OnPendingMapInit(EntityUid uid, PendingZombieComponent component, MapInitEvent args)
@@ -65,24 +66,47 @@ namespace Content.Server.Zombies
         public override void Update(float frameTime)
         {
             base.Update(frameTime);
-            var query = EntityQueryEnumerator<PendingZombieComponent, DamageableComponent>();
+            var query = EntityQueryEnumerator<PendingZombieComponent, DamageableComponent, MobStateComponent>();
             var curTime = _timing.CurTime;
 
             var zombQuery = EntityQueryEnumerator<ZombieComponent, DamageableComponent, MobStateComponent>();
 
             // Hurt the living infected
-            while (query.MoveNext(out var uid, out var comp, out var damage))
+            while (query.MoveNext(out var uid, out var comp, out var damage, out var mobState))
             {
                 // Process only once per second
                 if (comp.NextTick + TimeSpan.FromSeconds(1) > curTime)
                     continue;
 
+                comp.NextTick = curTime;
+
                 comp.InfectedSecs += 1;
+                // See if there should be a warning popup for the player.
+                if (comp.InfectionWarnings.TryGetValue(comp.InfectedSecs, out var popupStr))
+                {
+                    _popup.PopupEntity(Loc.GetString(popupStr), uid, uid);
+                }
+
+                if (comp.InfectedSecs < 0)
+                {
+                    // This zombie has a latent virus, probably set up by ZombieRuleSystem. No damage yet.
+                    continue;
+                }
+
                 // Pain of becoming a zombie grows over time
-                // 1x at 30s, 3x at 60s, 6x at 90s, 10x at 120s.
-                var pain_multiple = 0.1 + 0.02 * comp.InfectedSecs + 0.0005 * comp.InfectedSecs * comp.InfectedSecs;
-                comp.NextTick = curTime;
-                _damageable.TryChangeDamage(uid, comp.Damage * pain_multiple, true, false, damage);
+                // By scaling the number of seconds we have an accessible way to scale this exponential function.
+                //   The function was hand tuned to 120 seconds, hence the 120 constant here.
+                var scaledSeconds = (120.0f / comp.MaxInfectionLength) * comp.InfectedSecs;
+
+                // 1x at 30s, 3x at 60s, 6x at 90s, 10x at 120s. Limit at 20x so we don't gib you.
+                var painMultiple = Math.Min(20f, 0.1f + 0.02f * scaledSeconds + 0.0005f * scaledSeconds * scaledSeconds);
+                if (mobState.CurrentState == MobState.Critical)
+                {
+                    // Speed up their transformation when they are (or have been) in crit by ensuring their damage
+                    //   multiplier is at least 10x
+                    painMultiple = Math.Max(comp.MinimumCritMultiplier, painMultiple);
+                }
+                _damageable.TryChangeDamage(uid, comp.Damage * painMultiple, true, false, damage);
             }
 
             // Heal the zombified
@@ -168,6 +192,15 @@ namespace Content.Server.Zombies
             }
         }
 
+        private void OnPendingMobState(EntityUid uid, PendingZombieComponent pending, MobStateChangedEvent args)
+        {
+            if (args.NewMobState == MobState.Critical)
+            {
+                // Immediately jump to an active virus when you crit
+                pending.InfectedSecs = Math.Max(0, pending.InfectedSecs);
+            }
+        }
+
         private float GetZombieInfectionChance(EntityUid uid, ZombieComponent component)
         {
             var baseChance = component.MaxZombieInfectionChance;
@@ -227,7 +260,8 @@ namespace Content.Server.Zombies
                 {
                     if (_random.Prob(GetZombieInfectionChance(entity, component)))
                     {
-                        EnsureComp<PendingZombieComponent>(entity);
+                        var pending = EnsureComp<PendingZombieComponent>(entity);
+                        pending.MaxInfectionLength = _random.NextFloat(0.25f, 1.0f) * component.ZombieInfectionTurnTime;
                         EnsureComp<ZombifyOnDeathComponent>(entity);
                     }
                 }
index d77024efad67d7b592f49e745581458174237aa7..a0a26eb6e23063435d056f823fcb59616aa99de8 100644 (file)
@@ -232,7 +232,8 @@ namespace Content.Server.Zombies
             }
             RemComp<HandsComponent>(target);
             // No longer waiting to become a zombie:
-            RemComp<PendingZombieComponent>(target);
+            // Requires deferral because this is (probably) the event which called ZombifyEntity in the first place.
+            RemCompDeferred<PendingZombieComponent>(target);
 
             //zombie gamemode stuff
             RaiseLocalEvent(new EntityZombifiedEvent(target));
index 87943531fc7bf7b5f8184bd65eec7d3bc12cbaa3..1549d712c05159e39e773f88759de71964499d4f 100644 (file)
@@ -55,6 +55,13 @@ namespace Content.Shared.Zombies
         [ViewVariables(VVAccess.ReadWrite)]
         public float ZombieMovementSpeedDebuff = 0.75f;
 
+        /// <summary>
+        /// How long it takes our bite victims to turn in seconds (max).
+        ///   Will roll 25% - 100% of this on bite.
+        /// </summary>
+        [DataField("zombieInfectionTurnTime"), ViewVariables(VVAccess.ReadWrite)]
+        public float ZombieInfectionTurnTime = 240.0f;
+
         /// <summary>
         /// The skin color of the zombie
         /// </summary>
index cef09786de547aa98dc2bd780412b748880cc292..8c4e963ffa87126e46f20aef2bb74cdde8aa14e5 100644 (file)
@@ -6,6 +6,8 @@ zombie-no-one-ready = No players readied up! Can't start Zombies.
 
 zombie-patientzero-role-greeting = You are patient 0. Hide your infection, get supplies, and be prepared to turn once you die.
 zombie-healing = You feel a stirring in your flesh
+zombie-infection-warning = You feel the zombie virus take hold
+zombie-infection-underway = Your blood begins to thicken
 
 zombie-alone = You feel entirely alone.