Zombification resistance rework (#36485)

* initial commit

* Commit cuz beck said so 👍

* Implement balance changes, and revert some stuff

* fix yaml test real

* Added full stop, ensured display of infection chance, buffed biosuit speed

* Maint reviews, I commit

* Review completed, winter coats nerfed, CMO bio suit removed for future PR

* Final-final commit-REAL (2)-COPY
This commit is contained in:
UpAndLeaves
2025-04-16 19:21:45 +01:00
committed by GitHub
parent c183231834
commit 1c7e6592e9
12 changed files with 153 additions and 34 deletions

View File

@@ -7,6 +7,7 @@ using Content.Server.Emoting.Systems;
using Content.Server.Speech.EntitySystems; using Content.Server.Speech.EntitySystems;
using Content.Server.Roles; using Content.Server.Roles;
using Content.Shared.Anomaly.Components; using Content.Shared.Anomaly.Components;
using Content.Shared.Armor;
using Content.Shared.Bed.Sleep; using Content.Shared.Bed.Sleep;
using Content.Shared.Cloning.Events; using Content.Shared.Cloning.Events;
using Content.Shared.Damage; using Content.Shared.Damage;
@@ -74,7 +75,6 @@ namespace Content.Server.Zombies
SubscribeLocalEvent<IncurableZombieComponent, MapInitEvent>(OnPendingMapInit); SubscribeLocalEvent<IncurableZombieComponent, MapInitEvent>(OnPendingMapInit);
SubscribeLocalEvent<ZombifyOnDeathComponent, MobStateChangedEvent>(OnDamageChanged); SubscribeLocalEvent<ZombifyOnDeathComponent, MobStateChangedEvent>(OnDamageChanged);
} }
private void OnBeforeRemoveAnomalyOnDeath(Entity<PendingZombieComponent> ent, ref BeforeRemoveAnomalyOnDeathEvent args) private void OnBeforeRemoveAnomalyOnDeath(Entity<PendingZombieComponent> ent, ref BeforeRemoveAnomalyOnDeathEvent args)
@@ -199,33 +199,29 @@ namespace Content.Server.Zombies
} }
} }
private float GetZombieInfectionChance(EntityUid uid, ZombieComponent component) private float GetZombieInfectionChance(EntityUid uid, ZombieComponent zombieComponent)
{ {
var max = component.MaxZombieInfectionChance; var chance = zombieComponent.BaseZombieInfectionChance;
if (!_inventory.TryGetContainerSlotEnumerator(uid, out var enumerator, ProtectiveSlots)) var armorEv = new CoefficientQueryEvent(ProtectiveSlots);
return max; RaiseLocalEvent(uid, armorEv);
foreach (var resistanceEffectiveness in zombieComponent.ResistanceEffectiveness.DamageDict)
var items = 0f;
var total = 0f;
while (enumerator.MoveNext(out var con))
{ {
total++; if (armorEv.DamageModifiers.Coefficients.TryGetValue(resistanceEffectiveness.Key, out var coefficient))
if (con.ContainedEntity != null) {
items++; // Scale the coefficient by the resistance effectiveness, very descriptive I know
// For example. With 30% slash resist (0.7 coeff), but only a 60% resistance effectiveness for slash,
// you'll end up with 1 - (0.3 * 0.6) = 0.82 coefficient, or a 18% resistance
var adjustedCoefficient = 1 - ((1 - coefficient) * resistanceEffectiveness.Value.Float());
chance *= adjustedCoefficient;
}
} }
if (total == 0) var zombificationResistanceEv = new ZombificationResistanceQueryEvent(ProtectiveSlots);
return max; RaiseLocalEvent(uid, zombificationResistanceEv);
chance *= zombificationResistanceEv.TotalCoefficient;
// Everyone knows that when it comes to zombies, socks & sandals provide just as much protection as an return MathF.Max(chance, zombieComponent.MinZombieInfectionChance);
// armored vest. Maybe these should be weighted per-item. I.e. some kind of coverage/protection component.
// Or at the very least different weights per slot.
var min = component.MinZombieInfectionChance;
//gets a value between the max and min based on how many items the entity is wearing
var chance = (max - min) * ((total - items) / total) + min;
return chance;
} }
private void OnMeleeHit(EntityUid uid, ZombieComponent component, MeleeHitEvent args) private void OnMeleeHit(EntityUid uid, ZombieComponent component, MeleeHitEvent args)

View File

@@ -22,6 +22,7 @@ using Content.Shared.Strip.Components;
using Content.Shared.Temperature; using Content.Shared.Temperature;
using Content.Shared.Verbs; using Content.Shared.Verbs;
using Content.Shared.Weapons.Ranged.Events; using Content.Shared.Weapons.Ranged.Events;
using Content.Shared.Zombies;
namespace Content.Shared.Inventory; namespace Content.Shared.Inventory;
@@ -44,6 +45,7 @@ public partial class InventorySystem
SubscribeLocalEvent<InventoryComponent, SelfBeforeGunShotEvent>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, SelfBeforeGunShotEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, SelfBeforeClimbEvent>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, SelfBeforeClimbEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, CoefficientQueryEvent>(RelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, CoefficientQueryEvent>(RelayInventoryEvent);
SubscribeLocalEvent<InventoryComponent, ZombificationResistanceQueryEvent>(RelayInventoryEvent);
// by-ref events // by-ref events
SubscribeLocalEvent<InventoryComponent, BeforeStaminaDamageEvent>(RefRelayInventoryEvent); SubscribeLocalEvent<InventoryComponent, BeforeStaminaDamageEvent>(RefRelayInventoryEvent);

View File

@@ -1,4 +1,6 @@
using Content.Shared.Movement.Systems; using Content.Shared.Armor;
using Content.Shared.Inventory;
using Content.Shared.Movement.Systems;
using Content.Shared.NameModifier.EntitySystems; using Content.Shared.NameModifier.EntitySystems;
namespace Content.Shared.Zombies; namespace Content.Shared.Zombies;
@@ -12,6 +14,24 @@ public abstract class SharedZombieSystem : EntitySystem
SubscribeLocalEvent<ZombieComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshSpeed); SubscribeLocalEvent<ZombieComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshSpeed);
SubscribeLocalEvent<ZombieComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers); SubscribeLocalEvent<ZombieComponent, RefreshNameModifiersEvent>(OnRefreshNameModifiers);
SubscribeLocalEvent<ZombificationResistanceComponent, ArmorExamineEvent>(OnArmorExamine);
SubscribeLocalEvent<ZombificationResistanceComponent, InventoryRelayedEvent<ZombificationResistanceQueryEvent>>(OnResistanceQuery);
}
private void OnResistanceQuery(Entity<ZombificationResistanceComponent> ent, ref InventoryRelayedEvent<ZombificationResistanceQueryEvent> query)
{
query.Args.TotalCoefficient *= ent.Comp.ZombificationResistanceCoefficient;
}
private void OnArmorExamine(Entity<ZombificationResistanceComponent> ent, ref ArmorExamineEvent args)
{
var value = MathF.Round((1f - ent.Comp.ZombificationResistanceCoefficient) * 100, 1);
if (value == 0)
return;
args.Msg.PushNewline();
args.Msg.AddMarkupOrThrow(Loc.GetString(ent.Comp.Examine, ("value", value)));
} }
private void OnRefreshSpeed(EntityUid uid, ZombieComponent component, RefreshMovementSpeedModifiersEvent args) private void OnRefreshSpeed(EntityUid uid, ZombieComponent component, RefreshMovementSpeedModifiersEvent args)

View File

@@ -1,7 +1,7 @@
using Content.Shared.Antag;
using Content.Shared.Chat.Prototypes; using Content.Shared.Chat.Prototypes;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.FixedPoint;
using Content.Shared.Humanoid; using Content.Shared.Humanoid;
using Content.Shared.Roles; using Content.Shared.Roles;
using Content.Shared.StatusIcon; using Content.Shared.StatusIcon;
@@ -17,17 +17,30 @@ namespace Content.Shared.Zombies;
public sealed partial class ZombieComponent : Component public sealed partial class ZombieComponent : Component
{ {
/// <summary> /// <summary>
/// The baseline infection chance you have if you are completely nude /// The baseline infection chance you have if you have no protective gear
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float MaxZombieInfectionChance = 0.80f; public float BaseZombieInfectionChance = 0.75f;
/// <summary> /// <summary>
/// The minimum infection chance possible. This is simply to prevent /// The minimum infection chance possible. This is simply to prevent
/// being invincible by bundling up. /// being overly protected by bundling up.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float MinZombieInfectionChance = 0.25f; public float MinZombieInfectionChance = 0.05f;
/// <summary>
/// How effective each resistance type on a piece of armor is. Using a damage specifier for this seems illegal.
/// </summary>
public DamageSpecifier ResistanceEffectiveness = new()
{
DamageDict = new ()
{
{"Slash", 0.5},
{"Piercing", 0.3},
{"Blunt", 0.1},
}
};
[ViewVariables(VVAccess.ReadWrite)] [ViewVariables(VVAccess.ReadWrite)]
public float ZombieMovementSpeedDebuff = 0.70f; public float ZombieMovementSpeedDebuff = 0.70f;

View File

@@ -0,0 +1,46 @@
using Content.Shared.Inventory;
using Robust.Shared.GameStates;
namespace Content.Shared.Zombies;
/// <summary>
/// An armor-esque component for clothing that grants "resistance" (lowers the chance) against getting infected.
/// It works on a coefficient system, so 0.3 is better than 0.9, 1 is no resistance, and 0 is full resistance.
/// </summary>
[NetworkedComponent, RegisterComponent]
public sealed partial class ZombificationResistanceComponent : Component
{
/// <summary>
/// The multiplier that will by applied to the zombification chance.
/// </summary>
[DataField]
public float ZombificationResistanceCoefficient = 1;
/// <summary>
/// Examine string for the zombification resistance.
/// Passed <c>value</c> from 0 to 100.
/// </summary>
[DataField]
public LocId Examine = "zombification-resistance-coefficient-value";
}
/// <summary>
/// Gets the total resistance from the ZombificationResistanceComponent, i.e. just all of them multiplied together.
/// </summary>
public sealed class ZombificationResistanceQueryEvent : EntityEventArgs, IInventoryRelayEvent
{
/// <summary>
/// All slots to relay to
/// </summary>
public SlotFlags TargetSlots { get; }
/// <summary>
/// The Total of all Coefficients.
/// </summary>
public float TotalCoefficient = 1.0f;
public ZombificationResistanceQueryEvent(SlotFlags slots)
{
TargetSlots = slots;
}
}

View File

@@ -7,3 +7,5 @@ zombie-role-desc = A malevolent creature of the dead.
zombie-role-rules = You are a [color={role-type-team-antagonist-color}][bold]{role-type-team-antagonist-name}[/bold][/color]. Search out the living and bite them in order to infect them and turn them into zombies. Work together with the other zombies and remaining initial infected to overtake the station. zombie-role-rules = You are a [color={role-type-team-antagonist-color}][bold]{role-type-team-antagonist-name}[/bold][/color]. Search out the living and bite them in order to infect them and turn them into zombies. Work together with the other zombies and remaining initial infected to overtake the station.
zombie-permadeath = This time, you're dead for real. zombie-permadeath = This time, you're dead for real.
zombification-resistance-coefficient-value = - [color=violet]Infection[/color] chance reduced by [color=lightblue]{$value}%[/color].

View File

@@ -249,6 +249,11 @@
- type: TemperatureProtection - type: TemperatureProtection
heatingCoefficient: 1.05 heatingCoefficient: 1.05
coolingCoefficient: 0.7 coolingCoefficient: 0.7
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.90
- type: Armor # so zombification resistance shows up
modifiers:
coefficients: { }
- type: GroupExamine - type: GroupExamine
- type: HideLayerClothing - type: HideLayerClothing
slots: slots:

View File

@@ -738,6 +738,8 @@
Slash: 0.9 Slash: 0.9
Piercing: 0.9 Piercing: 0.9
Heat: 0.9 Heat: 0.9
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.75
#Deathsquad Hardsuit #Deathsquad Hardsuit
- type: entity - type: entity

View File

@@ -11,6 +11,11 @@
sprite: Clothing/Head/Hoods/Bio/general.rsi sprite: Clothing/Head/Hoods/Bio/general.rsi
- type: BreathMask - type: BreathMask
- type: IngestionBlocker - type: IngestionBlocker
- type: GroupExamine
- type: Armor
modifiers: { }
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.9
- type: Tag - type: Tag
tags: tags:
- WhitelistChameleon - WhitelistChameleon
@@ -33,6 +38,8 @@
sprite: Clothing/Head/Hoods/Bio/cmo.rsi sprite: Clothing/Head/Hoods/Bio/cmo.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Head/Hoods/Bio/cmo.rsi sprite: Clothing/Head/Hoods/Bio/cmo.rsi
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.8
- type: entity - type: entity
parent: ClothingHeadHatHoodBioGeneral parent: ClothingHeadHatHoodBioGeneral
@@ -78,13 +85,15 @@
id: ClothingHeadHatHoodBioVirology id: ClothingHeadHatHoodBioVirology
name: bio hood name: bio hood
suffix: Virology suffix: Virology
description: A hood that protects the head and face from biological contaminants. description: A hood that strongly protects the head and face from biological contaminants.
components: components:
- type: IdentityBlocker - type: IdentityBlocker
- type: Sprite - type: Sprite
sprite: Clothing/Head/Hoods/Bio/virology.rsi sprite: Clothing/Head/Hoods/Bio/virology.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Head/Hoods/Bio/virology.rsi sprite: Clothing/Head/Hoods/Bio/virology.rsi
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.8
- type: entity - type: entity
parent: ClothingHeadBase parent: ClothingHeadBase

View File

@@ -12,7 +12,13 @@
- type: Armor - type: Armor
modifiers: modifiers:
coefficients: coefficients:
Caustic: 0.5 Caustic: 0.75
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.35
- type: GroupExamine
- type: ClothingSpeedModifier
walkModifier: 1
sprintModifier: 0.95
- type: entity - type: entity
parent: ClothingOuterBioGeneral parent: ClothingOuterBioGeneral
@@ -31,12 +37,16 @@
id: ClothingOuterBioJanitor id: ClothingOuterBioJanitor
name: bio suit name: bio suit
suffix: Janitor suffix: Janitor
description: A suit that protects against biological contamination, in Janitor colors. description: A suit that protects against biological contamination and caustic spills.
components: components:
- type: Sprite - type: Sprite
sprite: Clothing/OuterClothing/Bio/janitor.rsi sprite: Clothing/OuterClothing/Bio/janitor.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Bio/janitor.rsi sprite: Clothing/OuterClothing/Bio/janitor.rsi
- type: Armor
modifiers:
coefficients:
Caustic: 0.4
- type: entity - type: entity
parent: ClothingOuterBioGeneral parent: ClothingOuterBioGeneral
@@ -51,25 +61,35 @@
sprite: Clothing/OuterClothing/Bio/scientist.rsi sprite: Clothing/OuterClothing/Bio/scientist.rsi
- type: entity - type: entity
parent: ClothingOuterBioGeneral parent: [ClothingOuterBioGeneral, BaseSecurityContraband]
id: ClothingOuterBioSecurity id: ClothingOuterBioSecurity
name: bio suit name: bio suit
suffix: Security suffix: Security
description: A suit that protects against biological contamination, in Security colors. description: A suit that protects against biological contamination, kitted out with additional armor.
components: components:
- type: Sprite - type: Sprite
sprite: Clothing/OuterClothing/Bio/security.rsi sprite: Clothing/OuterClothing/Bio/security.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Bio/security.rsi sprite: Clothing/OuterClothing/Bio/security.rsi
- type: Armor
modifiers:
coefficients:
Caustic: 0.8
Slash: 0.6
Piercing: 0.8
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.4
- type: entity - type: entity
parent: ClothingOuterBioGeneral parent: ClothingOuterBioGeneral
id: ClothingOuterBioVirology id: ClothingOuterBioVirology
name: bio suit name: bio suit
suffix: Virology suffix: Virology
description: A suit that protects against biological contamination, in Virology colors. description: A suit that strongly protects against biological contamination.
components: components:
- type: Sprite - type: Sprite
sprite: Clothing/OuterClothing/Bio/virology.rsi sprite: Clothing/OuterClothing/Bio/virology.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Bio/virology.rsi sprite: Clothing/OuterClothing/Bio/virology.rsi
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.25

View File

@@ -968,6 +968,8 @@
Shock: 0.1 Shock: 0.1
Radiation: 0.1 Radiation: 0.1
Caustic: 0.1 Caustic: 0.1
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.25
- type: ClothingSpeedModifier - type: ClothingSpeedModifier
walkModifier: 1.0 walkModifier: 1.0
sprintModifier: 1.0 sprintModifier: 1.0

View File

@@ -19,6 +19,8 @@
Slash: 0.95 Slash: 0.95
Heat: 0.90 Heat: 0.90
priceMultiplier: 0 priceMultiplier: 0
- type: ZombificationResistance
zombificationResistanceCoefficient: 0.55
- type: Food - type: Food
requiresSpecialDigestion: true requiresSpecialDigestion: true
- type: SolutionContainerManager - type: SolutionContainerManager