Zombie virus delayed from 20-30 minutes from rule start. (#16346)
This commit is contained in:
@@ -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"),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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.InfectedSecs += 1;
|
||||
// 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);
|
||||
|
||||
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
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user