diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index aa5c2682bc..e34ecb4ad5 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -7,6 +7,7 @@ using Content.Server.Emoting.Systems; using Content.Server.Speech.EntitySystems; using Content.Server.Roles; using Content.Shared.Anomaly.Components; +using Content.Shared.Armor; using Content.Shared.Bed.Sleep; using Content.Shared.Cloning.Events; using Content.Shared.Damage; @@ -74,7 +75,6 @@ namespace Content.Server.Zombies SubscribeLocalEvent(OnPendingMapInit); SubscribeLocalEvent(OnDamageChanged); - } private void OnBeforeRemoveAnomalyOnDeath(Entity 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)) - return max; - - var items = 0f; - var total = 0f; - while (enumerator.MoveNext(out var con)) + var armorEv = new CoefficientQueryEvent(ProtectiveSlots); + RaiseLocalEvent(uid, armorEv); + foreach (var resistanceEffectiveness in zombieComponent.ResistanceEffectiveness.DamageDict) { - total++; - if (con.ContainedEntity != null) - items++; + if (armorEv.DamageModifiers.Coefficients.TryGetValue(resistanceEffectiveness.Key, out var coefficient)) + { + // 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) - return max; + var zombificationResistanceEv = new ZombificationResistanceQueryEvent(ProtectiveSlots); + RaiseLocalEvent(uid, zombificationResistanceEv); + chance *= zombificationResistanceEv.TotalCoefficient; - // Everyone knows that when it comes to zombies, socks & sandals provide just as much protection as an - // 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; + return MathF.Max(chance, zombieComponent.MinZombieInfectionChance); } private void OnMeleeHit(EntityUid uid, ZombieComponent component, MeleeHitEvent args) diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index efa88fb23a..01d3e87246 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -22,6 +22,7 @@ using Content.Shared.Strip.Components; using Content.Shared.Temperature; using Content.Shared.Verbs; using Content.Shared.Weapons.Ranged.Events; +using Content.Shared.Zombies; namespace Content.Shared.Inventory; @@ -44,6 +45,7 @@ public partial class InventorySystem SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); SubscribeLocalEvent(RelayInventoryEvent); + SubscribeLocalEvent(RelayInventoryEvent); // by-ref events SubscribeLocalEvent(RefRelayInventoryEvent); diff --git a/Content.Shared/Zombies/SharedZombieSystem.cs b/Content.Shared/Zombies/SharedZombieSystem.cs index 0388450a8c..076917c4ac 100644 --- a/Content.Shared/Zombies/SharedZombieSystem.cs +++ b/Content.Shared/Zombies/SharedZombieSystem.cs @@ -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; namespace Content.Shared.Zombies; @@ -12,6 +14,24 @@ public abstract class SharedZombieSystem : EntitySystem SubscribeLocalEvent(OnRefreshSpeed); SubscribeLocalEvent(OnRefreshNameModifiers); + SubscribeLocalEvent(OnArmorExamine); + SubscribeLocalEvent>(OnResistanceQuery); + } + + private void OnResistanceQuery(Entity ent, ref InventoryRelayedEvent query) + { + query.Args.TotalCoefficient *= ent.Comp.ZombificationResistanceCoefficient; + } + + private void OnArmorExamine(Entity 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) diff --git a/Content.Shared/Zombies/ZombieComponent.cs b/Content.Shared/Zombies/ZombieComponent.cs index f9576dd3aa..47ae35b4c5 100644 --- a/Content.Shared/Zombies/ZombieComponent.cs +++ b/Content.Shared/Zombies/ZombieComponent.cs @@ -1,7 +1,7 @@ -using Content.Shared.Antag; using Content.Shared.Chat.Prototypes; using Content.Shared.Chemistry.Reagent; using Content.Shared.Damage; +using Content.Shared.FixedPoint; using Content.Shared.Humanoid; using Content.Shared.Roles; using Content.Shared.StatusIcon; @@ -17,17 +17,30 @@ namespace Content.Shared.Zombies; public sealed partial class ZombieComponent : Component { /// - /// The baseline infection chance you have if you are completely nude + /// The baseline infection chance you have if you have no protective gear /// [ViewVariables(VVAccess.ReadWrite)] - public float MaxZombieInfectionChance = 0.80f; + public float BaseZombieInfectionChance = 0.75f; /// /// The minimum infection chance possible. This is simply to prevent - /// being invincible by bundling up. + /// being overly protected by bundling up. /// [ViewVariables(VVAccess.ReadWrite)] - public float MinZombieInfectionChance = 0.25f; + public float MinZombieInfectionChance = 0.05f; + + /// + /// How effective each resistance type on a piece of armor is. Using a damage specifier for this seems illegal. + /// + public DamageSpecifier ResistanceEffectiveness = new() + { + DamageDict = new () + { + {"Slash", 0.5}, + {"Piercing", 0.3}, + {"Blunt", 0.1}, + } + }; [ViewVariables(VVAccess.ReadWrite)] public float ZombieMovementSpeedDebuff = 0.70f; diff --git a/Content.Shared/Zombies/ZombificationResistanceComponent.cs b/Content.Shared/Zombies/ZombificationResistanceComponent.cs new file mode 100644 index 0000000000..58dd99721c --- /dev/null +++ b/Content.Shared/Zombies/ZombificationResistanceComponent.cs @@ -0,0 +1,46 @@ +using Content.Shared.Inventory; +using Robust.Shared.GameStates; + +namespace Content.Shared.Zombies; + +/// +/// 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. +/// +[NetworkedComponent, RegisterComponent] +public sealed partial class ZombificationResistanceComponent : Component +{ + /// + /// The multiplier that will by applied to the zombification chance. + /// + [DataField] + public float ZombificationResistanceCoefficient = 1; + + /// + /// Examine string for the zombification resistance. + /// Passed value from 0 to 100. + /// + [DataField] + public LocId Examine = "zombification-resistance-coefficient-value"; +} + +/// +/// Gets the total resistance from the ZombificationResistanceComponent, i.e. just all of them multiplied together. +/// +public sealed class ZombificationResistanceQueryEvent : EntityEventArgs, IInventoryRelayEvent +{ + /// + /// All slots to relay to + /// + public SlotFlags TargetSlots { get; } + + /// + /// The Total of all Coefficients. + /// + public float TotalCoefficient = 1.0f; + + public ZombificationResistanceQueryEvent(SlotFlags slots) + { + TargetSlots = slots; + } +} diff --git a/Resources/Locale/en-US/zombies/zombie.ftl b/Resources/Locale/en-US/zombies/zombie.ftl index b46e2ebc30..4643cd228b 100644 --- a/Resources/Locale/en-US/zombies/zombie.ftl +++ b/Resources/Locale/en-US/zombies/zombie.ftl @@ -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-permadeath = This time, you're dead for real. + +zombification-resistance-coefficient-value = - [color=violet]Infection[/color] chance reduced by [color=lightblue]{$value}%[/color]. diff --git a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml index cc4c84840a..646a555037 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/base_clothinghead.yml @@ -249,6 +249,11 @@ - type: TemperatureProtection heatingCoefficient: 1.05 coolingCoefficient: 0.7 + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.90 + - type: Armor # so zombification resistance shows up + modifiers: + coefficients: { } - type: GroupExamine - type: HideLayerClothing slots: diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index 9aa80e9a6a..eafd384864 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -738,6 +738,8 @@ Slash: 0.9 Piercing: 0.9 Heat: 0.9 + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.75 #Deathsquad Hardsuit - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/Head/hoods.yml b/Resources/Prototypes/Entities/Clothing/Head/hoods.yml index 1330d38f40..0f081679dd 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hoods.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hoods.yml @@ -11,6 +11,11 @@ sprite: Clothing/Head/Hoods/Bio/general.rsi - type: BreathMask - type: IngestionBlocker + - type: GroupExamine + - type: Armor + modifiers: { } + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.9 - type: Tag tags: - WhitelistChameleon @@ -33,6 +38,8 @@ sprite: Clothing/Head/Hoods/Bio/cmo.rsi - type: Clothing sprite: Clothing/Head/Hoods/Bio/cmo.rsi + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.8 - type: entity parent: ClothingHeadHatHoodBioGeneral @@ -78,13 +85,15 @@ id: ClothingHeadHatHoodBioVirology name: bio hood 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: - type: IdentityBlocker - type: Sprite sprite: Clothing/Head/Hoods/Bio/virology.rsi - type: Clothing sprite: Clothing/Head/Hoods/Bio/virology.rsi + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.8 - type: entity parent: ClothingHeadBase diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/bio.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/bio.yml index fec4d4df6c..fa550a83d6 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/bio.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/bio.yml @@ -12,7 +12,13 @@ - type: Armor modifiers: coefficients: - Caustic: 0.5 + Caustic: 0.75 + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.35 + - type: GroupExamine + - type: ClothingSpeedModifier + walkModifier: 1 + sprintModifier: 0.95 - type: entity parent: ClothingOuterBioGeneral @@ -31,12 +37,16 @@ id: ClothingOuterBioJanitor name: bio suit 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: - type: Sprite sprite: Clothing/OuterClothing/Bio/janitor.rsi - type: Clothing sprite: Clothing/OuterClothing/Bio/janitor.rsi + - type: Armor + modifiers: + coefficients: + Caustic: 0.4 - type: entity parent: ClothingOuterBioGeneral @@ -51,25 +61,35 @@ sprite: Clothing/OuterClothing/Bio/scientist.rsi - type: entity - parent: ClothingOuterBioGeneral + parent: [ClothingOuterBioGeneral, BaseSecurityContraband] id: ClothingOuterBioSecurity name: bio suit 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: - type: Sprite sprite: Clothing/OuterClothing/Bio/security.rsi - type: Clothing 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 parent: ClothingOuterBioGeneral id: ClothingOuterBioVirology name: bio suit suffix: Virology - description: A suit that protects against biological contamination, in Virology colors. + description: A suit that strongly protects against biological contamination. components: - type: Sprite sprite: Clothing/OuterClothing/Bio/virology.rsi - type: Clothing sprite: Clothing/OuterClothing/Bio/virology.rsi + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.25 diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index a841b5878f..ba57ef9c53 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -968,6 +968,8 @@ Shock: 0.1 Radiation: 0.1 Caustic: 0.1 + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.25 - type: ClothingSpeedModifier walkModifier: 1.0 sprintModifier: 1.0 diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml index c141971700..142cac767e 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/wintercoats.yml @@ -19,6 +19,8 @@ Slash: 0.95 Heat: 0.90 priceMultiplier: 0 + - type: ZombificationResistance + zombificationResistanceCoefficient: 0.55 - type: Food requiresSpecialDigestion: true - type: SolutionContainerManager