diff --git a/Content.Server/Zombies/ActiveZombieComponent.cs b/Content.Server/Zombies/ActiveZombieComponent.cs new file mode 100644 index 0000000000..0d79672bd1 --- /dev/null +++ b/Content.Server/Zombies/ActiveZombieComponent.cs @@ -0,0 +1,31 @@ +namespace Content.Server.Zombies; + +[RegisterComponent] +public sealed class ActiveZombieComponent : Component +{ + /// + /// The chance that on a random attempt + /// that a zombie will do a groan + /// + [ViewVariables(VVAccess.ReadWrite)] + public float GroanChance = 0.2f; + + /// + /// Minimum time between groans + /// + [ViewVariables(VVAccess.ReadWrite)] + public float GroanCooldown = 2; + + /// + /// The length of time between each zombie's random groan + /// attempt. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float RandomGroanAttempt = 5; + + [ViewVariables(VVAccess.ReadWrite)] + public float LastDamageGroanCooldown = 0f; + + [ViewVariables(VVAccess.ReadWrite)] + public float Accumulator = 0f; +} diff --git a/Content.Server/Zombies/ZombieComponent.cs b/Content.Server/Zombies/ZombieComponent.cs index ba301783e8..7b00e87dfc 100644 --- a/Content.Server/Zombies/ZombieComponent.cs +++ b/Content.Server/Zombies/ZombieComponent.cs @@ -17,14 +17,14 @@ namespace Content.Server.Zombies /// /// The baseline infection chance you have if you are completely nude /// - [ViewVariables] + [ViewVariables(VVAccess.ReadWrite)] public float MaxZombieInfectionChance = 0.75f; /// /// The minimum infection chance possible. This is simply to prevent /// being invincible by bundling up. /// - [ViewVariables] + [ViewVariables(VVAccess.ReadWrite)] public float MinZombieInfectionChance = 0.1f; [ViewVariables(VVAccess.ReadWrite)] @@ -33,19 +33,19 @@ namespace Content.Server.Zombies /// /// The skin color of the zombie /// - [ViewVariables, DataField("skinColor")] + [DataField("skinColor")] public Color SkinColor = new(0.45f, 0.51f, 0.29f); /// /// The eye color of the zombie /// - [ViewVariables, DataField("eyeColor")] + [DataField("eyeColor")] public Color EyeColor = new(0.96f, 0.13f, 0.24f); /// /// The attack arc of the zombie /// - [ViewVariables, DataField("attackArc", customTypeSerializer: typeof(PrototypeIdSerializer))] + [DataField("attackArc", customTypeSerializer: typeof(PrototypeIdSerializer))] public string AttackArc = "claw"; /// diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 79d50a7724..e5c0e43268 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -8,8 +8,11 @@ using Content.Shared.Chemistry.Components; using Content.Shared.MobState.Components; using Content.Server.Disease; using Content.Shared.Inventory; +using Content.Shared.MobState; using Content.Server.Inventory; using Robust.Shared.Prototypes; +using Content.Server.Speech; +using Content.Server.Chat.Systems; using Content.Shared.Movement.Systems; using Content.Shared.Damage; @@ -22,6 +25,8 @@ namespace Content.Server.Zombies [Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly ZombifyOnDeathSystem _zombify = default!; [Dependency] private readonly ServerInventorySystem _inv = default!; + [Dependency] private readonly VocalSystem _vocal = default!; + [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; @@ -30,9 +35,25 @@ namespace Content.Server.Zombies base.Initialize(); SubscribeLocalEvent(OnMeleeHit); + SubscribeLocalEvent(OnMobState); + SubscribeLocalEvent(OnDamage); SubscribeLocalEvent(OnRefreshSpeed); } + private void OnMobState(EntityUid uid, ZombieComponent component, MobStateChangedEvent args) + { + if (args.CurrentMobState == DamageState.Alive) + EnsureComp(uid); + else + RemComp(uid); + } + + private void OnDamage(EntityUid uid, ActiveZombieComponent component, DamageChangedEvent args) + { + if (args.DamageIncreased) + DoGroan(uid, component); + } + private void OnRefreshSpeed(EntityUid uid, ZombieComponent component, RefreshMovementSpeedModifiersEvent args) { var mod = component.ZombieMovementSpeedDebuff; @@ -96,13 +117,13 @@ namespace Content.Server.Zombies if (HasComp(entity)) args.BonusDamage = -args.BaseDamage * zombieComp.OtherZombieDamageCoefficient; - if ((mobState.IsDead() || mobState.IsCritical()) + if ((mobState.CurrentState == DamageState.Dead || mobState.CurrentState == DamageState.Critical) && !HasComp(entity)) { _zombify.ZombifyEntity(entity); args.BonusDamage = -args.BaseDamage; } - else if (mobState.IsAlive()) //heals when zombies bite live entities + else if (mobState.CurrentState == DamageState.Alive) //heals when zombies bite live entities { var healingSolution = new Solution(); healingSolution.AddReagent("Bicaridine", 1.00); //if OP, reduce/change chem @@ -110,5 +131,39 @@ namespace Content.Server.Zombies } } } + + public void DoGroan(EntityUid uid, ActiveZombieComponent component) + { + if (component.LastDamageGroanCooldown > 0) + return; + + if (_robustRandom.Prob(0.5f)) //this message is never seen by players so it just says this for admins + _chat.TrySendInGameICMessage(uid, "[automated zombie groan]", InGameICChatType.Speak, false); + else + _vocal.TryScream(uid); + + component.LastDamageGroanCooldown = component.GroanCooldown; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var zombiecomp in EntityQuery()) + { + zombiecomp.Accumulator += frameTime; + zombiecomp.LastDamageGroanCooldown -= frameTime; + + if (zombiecomp.Accumulator < zombiecomp.RandomGroanAttempt) + continue; + zombiecomp.Accumulator -= zombiecomp.RandomGroanAttempt; + + if (!_robustRandom.Prob(zombiecomp.GroanChance)) + continue; + + //either do a random accent line or scream + DoGroan(zombiecomp.Owner, zombiecomp); + } + } } } diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs index dee705dd07..491c5f1013 100644 --- a/Content.Server/Zombies/ZombifyOnDeathSystem.cs +++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs @@ -30,6 +30,10 @@ using Content.Shared.Zombies; using Content.Shared.Popups; using Content.Server.Atmos.Miasma; using Content.Server.IdentityManagement; +using Content.Shared.Audio; +using Content.Shared.Sound; +using Robust.Shared.Random; +using Content.Server.Speech; using Content.Shared.Movement.Systems; namespace Content.Server.Zombies @@ -111,6 +115,11 @@ namespace Content.Server.Zombies RemComp(target); AddComp(target); + var vocal = EnsureComp(target); + var scream = new SoundCollectionSpecifier ("ZombieScreams"); + vocal.FemaleScream = scream; + vocal.MaleScream = scream; + ///This is the actual damage of the zombie. We assign the visual appearance ///and range here because of stuff we'll find out later var melee = EnsureComp(target); diff --git a/Resources/Audio/Voice/Zombie/attributions.yml b/Resources/Audio/Voice/Zombie/attributions.yml new file mode 100644 index 0000000000..9fa56bc389 --- /dev/null +++ b/Resources/Audio/Voice/Zombie/attributions.yml @@ -0,0 +1,12 @@ +- files: ["zombie-1.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Zombie 1 by Under7dude. Converted from MP3 to OGG." + source: "https://freesound.org/people/Under7dude/sounds/163440/" +- files: ["zombie-2.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Zombie gargles by Breviceps. Converted from MP3 to OGG." + source: "https://freesound.org/people/Breviceps/sounds/445983/" +- files: ["zombie-3.ogg"] + license: "CC-BY-NC-SA-3.0" + copyright: "Zombie Snarl by gneube. Converted from MP3 to OGG." + source: "https://freesound.org/people/gneube/sounds/315844/" \ No newline at end of file diff --git a/Resources/Audio/Voice/Zombie/zombie-1.ogg b/Resources/Audio/Voice/Zombie/zombie-1.ogg new file mode 100644 index 0000000000..59d3b54662 Binary files /dev/null and b/Resources/Audio/Voice/Zombie/zombie-1.ogg differ diff --git a/Resources/Audio/Voice/Zombie/zombie-2.ogg b/Resources/Audio/Voice/Zombie/zombie-2.ogg new file mode 100644 index 0000000000..41eaa17bcc Binary files /dev/null and b/Resources/Audio/Voice/Zombie/zombie-2.ogg differ diff --git a/Resources/Audio/Voice/Zombie/zombie-3.ogg b/Resources/Audio/Voice/Zombie/zombie-3.ogg new file mode 100644 index 0000000000..a03fff0719 Binary files /dev/null and b/Resources/Audio/Voice/Zombie/zombie-3.ogg differ diff --git a/Resources/Locale/en-US/accent/accents.ftl b/Resources/Locale/en-US/accent/accents.ftl index 54f4be86a5..8ffadc33fd 100644 --- a/Resources/Locale/en-US/accent/accents.ftl +++ b/Resources/Locale/en-US/accent/accents.ftl @@ -40,6 +40,9 @@ accent-words-zombie-1 = Gruaahhhh... accent-words-zombie-2 = Mmuaaaa.. accent-words-zombie-3 = Braainnssss... accent-words-zombie-4 = Grrrrr... +accent-words-zombie-5 = Ouuaahhhhh... +accent-words-zombie-6 = Graaaaaooohhlll... +accent-words-zombie-7 = Brainsss... Braaaiiinnsss.. # Generic Aggressive accent-words-generic-aggressive-1 = Grr! diff --git a/Resources/Prototypes/SoundCollections/screams.yml b/Resources/Prototypes/SoundCollections/screams.yml index 1033215005..c5507a0582 100644 --- a/Resources/Prototypes/SoundCollections/screams.yml +++ b/Resources/Prototypes/SoundCollections/screams.yml @@ -26,3 +26,10 @@ - /Audio/Voice/Human/science_scream4.ogg - /Audio/Voice/Human/science_scream5.ogg - /Audio/Voice/Human/science_scream6.ogg + +- type: soundCollection + id: ZombieScreams + files: + - /Audio/Voice/Zombie/zombie-1.ogg + - /Audio/Voice/Zombie/zombie-2.ogg + - /Audio/Voice/Zombie/zombie-3.ogg \ No newline at end of file diff --git a/Resources/Prototypes/accents.yml b/Resources/Prototypes/accents.yml index ae32d9371f..b02dc30790 100644 --- a/Resources/Prototypes/accents.yml +++ b/Resources/Prototypes/accents.yml @@ -54,6 +54,9 @@ - accent-words-zombie-2 - accent-words-zombie-3 - accent-words-zombie-4 + - accent-words-zombie-5 + - accent-words-zombie-6 + - accent-words-zombie-7 - type: accent id: genericAggressive