Zombies!!! (#7509)

* wip

* heal on bite

* more fixes and additions

* don't crash

* Update medicine.yml

* zombie claw item and damage resist

* ignoredcomponents.cs

* Add zombie claw, fix infection, add immunities

* fix

* razzle dazzle

* yaml fix

* Update Content.Server/Disease/DiseaseZombieSystem.cs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* Update Content.Server/Disease/DiseaseZombieSystem.cs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* Update Content.Server/Disease/DiseaseZombieSystem.cs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* Update Content.Server/Disease/DiseaseZombieSystem.cs

Co-authored-by: Moony <moonheart08@users.noreply.github.com>

* sdasadsadsadasd

* Generalize DiseaseProgression.cs

* final final final final final final cope seethe

* Update medicine.yml

* Update Content.Server/Disease/Components/DiseaseZombieComponent.cs

Co-authored-by: mirrorcult <lunarautomaton6@gmail.com>

* Update BloodstreamSystem.cs

* Update Content.Server/Disease/Components/DiseaseZombieComponent.cs

Co-authored-by: mirrorcult <lunarautomaton6@gmail.com>

* Update Content.Server/Disease/DiseaseZombieSystem.cs

Co-authored-by: mirrorcult <lunarautomaton6@gmail.com>

* fixing until i die

* folder + zombietransfer fix

* smol fixe

* the smallest of fixes

* aaaa

* Infection timer buff

Co-authored-by: Moony <moonheart08@users.noreply.github.com>
Co-authored-by: mirrorcult <lunarautomaton6@gmail.com>
This commit is contained in:
EmoGarbage404
2022-04-18 18:30:22 -04:00
committed by GitHub
parent 4901a61b5e
commit db56d5ef60
28 changed files with 549 additions and 1 deletions

View File

@@ -45,6 +45,9 @@ namespace Content.Client.Entry
"DiseaseDiagnoser", "DiseaseDiagnoser",
"DiseaseVaccine", "DiseaseVaccine",
"DiseaseVaccineCreator", "DiseaseVaccineCreator",
"DiseaseZombie",
"DiseaseBuildup",
"ZombieTransfer",
"Mineable", "Mineable",
"RangedMagazine", "RangedMagazine",
"Ammo", "Ammo",

View File

@@ -226,6 +226,14 @@ public sealed class BloodstreamSystem : EntitySystem
return (component.BloodSolution.CurrentVolume / component.BloodSolution.MaxVolume).Float(); return (component.BloodSolution.CurrentVolume / component.BloodSolution.MaxVolume).Float();
} }
public void SetBloodLossThreshold(EntityUid uid, float threshold, BloodstreamComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
comp.BloodlossThreshold = threshold;
}
/// <summary> /// <summary>
/// Attempts to modify the blood level of this entity directly. /// Attempts to modify the blood level of this entity directly.
/// </summary> /// </summary>

View File

@@ -0,0 +1,19 @@
namespace Content.Server.Disease.Components
{
/// <summary>
/// The component which records the buildup/progression of an infection
/// </summary>
[RegisterComponent]
public sealed class DiseaseBuildupComponent : Component
{
/// This could be served to be generalized to allow for multiple
/// diseases to build up at once, but it doesn't matter too much.
/// <summary>
/// The current amount of progression that has built up.
/// </summary>
[DataField("progression")]
[ViewVariables(VVAccess.ReadWrite)]
public float Progression = 0.00f;
}
}

View File

@@ -94,8 +94,10 @@ namespace Content.Server.Disease
continue; continue;
} }
foreach (var disease in carrierComp.Diseases) for (var i = 0; i < carrierComp.Diseases.Count; i++) //this is a for-loop so that it doesn't break when new diseases are added
{ {
var disease = carrierComp.Diseases[i];
var args = new DiseaseEffectArgs(carrierComp.Owner, disease, EntityManager); var args = new DiseaseEffectArgs(carrierComp.Owner, disease, EntityManager);
disease.Accumulator += frameTime; disease.Accumulator += frameTime;
if (disease.Accumulator >= disease.TickTime) if (disease.Accumulator >= disease.TickTime)

View File

@@ -0,0 +1,46 @@
using Content.Server.Disease.Components;
using JetBrains.Annotations;
using Content.Shared.Disease;
using Robust.Shared.IoC;
namespace Content.Server.Disease.Effects
{
/// <summary>
/// Handles a disease which incubates over a period of time
/// before adding another component to the infected entity
/// currently used for zombie virus
/// </summary>
[UsedImplicitly]
public sealed class DiseaseProgression : DiseaseEffect
{
/// <summary>
/// The rate that's increased over time. Defaults to 1% so the probability can be varied in yaml
/// </summary>
[DataField("rate")]
[ViewVariables(VVAccess.ReadWrite)]
public float Rate = 0.01f;
/// <summary>
/// The component that is added at the end of build up
/// </summary>
[DataField("comp")]
public string? Comp = null;
public override void Effect(DiseaseEffectArgs args)
{
args.EntityManager.EnsureComponent<DiseaseBuildupComponent>(args.DiseasedEntity, out var buildup);
if (buildup.Progression < 1) //increases steadily until 100%
{
buildup.Progression += Rate;
}
else if (Comp != null)//adds the component for the later stage of the disease.
{
EntityUid uid = args.DiseasedEntity;
var newComponent = (Component) IoCManager.Resolve<IComponentFactory>().GetComponent(Comp);
newComponent.Owner = uid;
if (!args.EntityManager.HasComponent(uid, newComponent.GetType()))
args.EntityManager.AddComponent(uid, newComponent);
}
}
}
}

View File

@@ -0,0 +1,37 @@
namespace Content.Server.Disease.Zombie.Components
{
/// <summary>
/// The component which gives an entity zombie traits.
/// </summary>
[RegisterComponent]
public sealed class DiseaseZombieComponent : Component
{
/// <summary>
/// The probability that a given bite will infect a player.
/// zombie infection is not based on disease resist items like masks or gloves.
/// </summary>
[DataField("probability")]
[ViewVariables(VVAccess.ReadWrite)]
public float Probability = 0.33f;
/// <summary>
/// A multiplier on the movement speed that zombies recieve.
/// </summary>
[DataField("slowAmount")]
[ViewVariables(VVAccess.ReadWrite)]
public float SlowAmount = 0.75f;
/// <summary>
/// Whether or not the zombie needs all the zombie traits initialized upon component init
/// useful for entities that already are zombies and do not need the additional traits.
/// </summary>
[DataField("applyZombieTraits")]
public bool ApplyZombieTraits = true;
/// <summary>
/// The color of the zombie's skin
/// </summary>
[DataField("skinColor")]
public readonly Color SkinColor = (0.70f, 0.72f, 0.48f, 1);
}
}

View File

@@ -0,0 +1,139 @@
using Robust.Shared.Containers;
using Content.Server.Speech.Components;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Disease.Components;
using Content.Server.Disease.Zombie.Components;
using Content.Server.Body.Components;
using Content.Server.Body.Systems;
using Content.Server.Popups;
using Content.Server.Atmos.Components;
using Content.Server.Hands.Components;
using Content.Server.Nutrition.Components;
using Content.Server.Mind.Components;
using Content.Server.Chat.Managers;
using Content.Shared.Damage;
using Content.Shared.MobState.Components;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Movement.EntitySystems;
using Content.Shared.CharacterAppearance.Components;
using Content.Shared.CharacterAppearance.Systems;
using Content.Server.Weapons.Melee.ZombieTransfer.Components;
namespace Content.Server.Disease.Zombie
{
/// <summary>
/// Handles zombie propagation and inherent zombie traits
/// </summary>
public sealed class DiseaseZombieSystem : EntitySystem
{
[Dependency] private readonly DamageableSystem _damageable = default!;
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
[Dependency] private readonly SharedHandsSystem _sharedHands = default!;
[Dependency] private readonly SharedHumanoidAppearanceSystem _sharedHumanoidAppearance = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DiseaseZombieComponent, ComponentInit>(OnComponentInit);
SubscribeLocalEvent<DiseaseZombieComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
}
/// <remarks>
/// I would imagine that if this component got assigned to something other than a mob, it would throw hella errors.
/// </remarks>
private void OnComponentInit(EntityUid uid, DiseaseZombieComponent component, ComponentInit args)
{
if (!component.ApplyZombieTraits || !HasComp<MobStateComponent>(uid))
return;
RemComp<DiseaseCarrierComponent>(uid);
RemComp<DiseaseBuildupComponent>(uid);
RemComp<RespiratorComponent>(uid);
RemComp<BarotraumaComponent>(uid);
RemComp<HungerComponent>(uid);
RemComp<ThirstComponent>(uid);
EntityManager.EnsureComponent<BloodstreamComponent>(uid, out var bloodstream); //zoms need bloodstream anyway for healing
_bloodstream.SetBloodLossThreshold(uid, 0f, bloodstream);
_bloodstream.TryModifyBleedAmount(uid, -bloodstream.BleedAmount, bloodstream);
_movementSpeedModifier.RefreshMovementSpeedModifiers(uid);
EntityManager.EnsureComponent<ReplacementAccentComponent>(uid).Accent = "zombie";
if (TryComp<DamageableComponent>(uid, out var comp))
{
_damageable.SetDamageModifierSetId(uid, "Zombie", comp);
_damageable.SetAllDamage(comp, 0);
}
if (TryComp<HumanoidAppearanceComponent>(uid, out var spritecomp))
{
var oldapp = spritecomp.Appearance;
var newapp = oldapp.WithSkinColor(component.SkinColor);
_sharedHumanoidAppearance.UpdateAppearance(uid, newapp);
_sharedHumanoidAppearance.ForceAppearanceUpdate(uid);
}
if (TryComp<HandsComponent>(uid, out var handcomp))
{
foreach (var hand in handcomp.Hands)
{
_sharedHands.TrySetActiveHand(uid, hand.Key);
_sharedHands.TryDrop(uid);
var pos = EntityManager.GetComponent<TransformComponent>(uid).Coordinates;
var virtualItem = EntityManager.SpawnEntity("ZombieClaw", pos);
_sharedHands.DoPickup(uid, hand.Value, virtualItem);
}
}
else
{
EnsureComp<ZombieTransferComponent>(uid);
}
if (TryComp<ContainerManagerComponent>(uid, out var cmcomp))
{
foreach (var container in cmcomp.Containers)
{
if (container.Value.ID == "gloves")
{
foreach (var entity in container.Value.ContainedEntities)
{
container.Value.Remove(entity);
}
}
}
}
if (TryComp<MindComponent>(uid, out var mindcomp))
{
if (mindcomp.Mind != null && mindcomp.Mind.TryGetSession(out var session))
{
var chatMgr = IoCManager.Resolve<IChatManager>();
chatMgr.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting"));
}
}
uid.PopupMessageEveryone(Loc.GetString("zombie-transform", ("target", uid)));
if (TryComp<MetaDataComponent>(uid, out var metacomp))
{
metacomp.EntityName = Loc.GetString("zombie-name-prefix", ("target", metacomp.EntityName));
if (!HasComp<GhostRoleMobSpawnerComponent>(uid)) //this specific component gives build test trouble so pop off, ig
{
EntityManager.EnsureComponent<GhostTakeoverAvailableComponent>(uid, out var ghostcomp);
ghostcomp.RoleName = metacomp.EntityName;
ghostcomp.RoleDescription = Loc.GetString("zombie-role-desc");
ghostcomp.RoleRules = Loc.GetString("zombie-role-rules");
}
}
}
private void OnRefreshMovementSpeedModifiers(EntityUid uid, DiseaseZombieComponent component, RefreshMovementSpeedModifiersEvent args)
{
args.ModifySpeed(component.SlowAmount, component.SlowAmount);
}
}
}

View File

@@ -0,0 +1,54 @@
using Robust.Shared.Random;
using Content.Server.Chat.Managers;
using Content.Server.Disease.Zombie.Components;
using Content.Shared.MobState.Components;
namespace Content.Server.StationEvents.Events
{
/// <summary>
/// Revives several dead entities as zombies
/// </summary>
public sealed class ZombieOutbreak : StationEvent
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
public override string Name => "ZombieOutbreak";
public override float Weight => WeightLow;
public override string? StartAudio => "/Audio/Announcements/bloblarm.ogg";
protected override float EndAfter => 1.0f;
public override int? MaxOccurrences => 1;
/// <summary>
/// Finds 1-3 random, dead entities accross the station
/// and turns them into zombies.
/// </summary>
public override void Startup()
{
base.Startup();
List<MobStateComponent> deadList = new();
foreach (var mobState in _entityManager.EntityQuery<MobStateComponent>())
{
if (mobState.IsDead() || mobState.IsCritical())
deadList.Add(mobState);
}
_random.Shuffle(deadList);
var toInfect = _random.Next(1, 3);
/// Now we give it to people in the list of dead entities earlier.
foreach (var target in deadList)
{
if (toInfect-- == 0)
break;
_entityManager.EnsureComponent<DiseaseZombieComponent>(target.Owner);
}
_chatManager.DispatchStationAnnouncement(Loc.GetString("station-event-zombie-outbreak-announcement"),
playDefaultSound: false, colorOverride: Color.DarkMagenta);
}
}
}

View File

@@ -0,0 +1,7 @@
namespace Content.Server.Weapons.Melee.ZombieTransfer.Components
{
[RegisterComponent]
public sealed class ZombieTransferComponent : Component
{
}
}

View File

@@ -0,0 +1,67 @@
using System.Linq;
using Robust.Shared.Random;
using Content.Server.Body.Systems;
using Content.Server.Disease.Components;
using Content.Server.Disease.Zombie.Components;
using Content.Server.Drone.Components;
using Content.Server.Weapon.Melee;
using Content.Shared.Chemistry.Components;
using Content.Shared.Damage;
using Content.Shared.MobState.Components;
using Content.Server.Disease;
using Content.Server.Weapons.Melee.ZombieTransfer.Components;
namespace Content.Server.Weapons.Melee.ZombieTransfer
{
public sealed class ZombieTransferSystem : EntitySystem
{
[Dependency] private readonly DiseaseSystem _disease = default!;
[Dependency] private readonly BloodstreamSystem _bloodstream = default!;
[Dependency] private readonly IRobustRandom _robustRandom = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ZombieTransferComponent, MeleeHitEvent>(OnMeleeHit);
}
private void OnMeleeHit(EntityUid uid, ZombieTransferComponent component, MeleeHitEvent args)
{
if (!EntityManager.TryGetComponent<DiseaseZombieComponent>(args.User, out var diseaseZombieComp))
return;
if (!args.HitEntities.Any())
return;
foreach (EntityUid entity in args.HitEntities)
{
if (args.User == entity)
continue;
if (!HasComp<MobStateComponent>(entity))
continue;
if (_robustRandom.Prob(diseaseZombieComp.Probability) && HasComp<DiseaseCarrierComponent>(entity))
{
_disease.TryAddDisease(entity, "ZombieInfection");
}
EntityManager.EnsureComponent<MobStateComponent>(entity, out var mobState);
if ((mobState.IsDead() || mobState.IsCritical()) && !HasComp<DiseaseZombieComponent>(entity)) //dead entities are eautomatically infected. MAYBE: have activated infect ability?
{
EntityManager.AddComponent<DiseaseZombieComponent>(entity);
var dspec = new DamageSpecifier();
//these damages match the zombie claw
dspec.DamageDict.TryAdd("Slash", -12);
dspec.DamageDict.TryAdd("Piercing", -7);
args.BonusDamage += dspec;
}
else if (mobState.IsAlive() && !HasComp<DroneComponent>(entity)) //heals when zombies bite live entities
{
var healingSolution = new Solution();
healingSolution.AddReagent("Bicaridine", 1.00); //if OP, reduce/change chem
_bloodstream.TryAddToChemicals(args.User, healingSolution);
}
}
}
}
}

View File

@@ -48,6 +48,17 @@ namespace Content.Shared.CharacterAppearance.Systems
RaiseLocalEvent(uid, new ChangedHumanoidAppearanceEvent(appearance, sex, gender, species)); RaiseLocalEvent(uid, new ChangedHumanoidAppearanceEvent(appearance, sex, gender, species));
} }
public void UpdateAppearance(EntityUid uid, HumanoidCharacterAppearance appearance, HumanoidAppearanceComponent? component = null)
{
if (!Resolve(uid, ref component)) return;
component.Appearance = appearance;
component.Dirty();
RaiseLocalEvent(uid, new ChangedHumanoidAppearanceEvent(appearance, component.Sex, component.Gender, component.Species));
}
private void OnAppearanceGetState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentGetState args) private void OnAppearanceGetState(EntityUid uid, HumanoidAppearanceComponent component, ref ComponentGetState args)
{ {
args.State = new HumanoidAppearanceComponentState(component.Appearance, component.Sex, component.Gender, component.Species); args.State = new HumanoidAppearanceComponentState(component.Appearance, component.Sex, component.Gender, component.Species);

View File

@@ -186,6 +186,16 @@ namespace Content.Shared.Damage
DamageChanged(component, new DamageSpecifier()); DamageChanged(component, new DamageSpecifier());
} }
public void SetDamageModifierSetId(EntityUid uid, string damageModifierSetId, DamageableComponent? comp = null)
{
if (!Resolve(uid, ref comp))
return;
comp.DamageModifierSetId = damageModifierSetId;
Dirty(comp);
}
private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args) private void DamageableGetState(EntityUid uid, DamageableComponent component, ref ComponentGetState args)
{ {
args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId); args.State = new DamageableComponentState(component.Damage.DamageDict, component.DamageModifierSetId);

View File

@@ -0,0 +1,6 @@
zombie-transform = {CAPITALIZE(THE($target))} turned into a zombie!
zombie-infection-greeting = You have become a zombie. Your goal is to seek out the living and to try to infect them. Work together with your fellow zombies to overpower the remaining crewmates.
zombie-name-prefix = zombified {$target}
zombie-role-desc = A malevolent creature of the dead.
zombie-role-rules = You are an antagonist. Search out the living and bite them in order to infect them and turn them into zombies. Work together with other the zombies to overtake the station.

View File

@@ -0,0 +1 @@
station-event-zombie-outbreak-announcement = Confirmed sightings of various undead aboard the station. All personnel should arm themselves, barricade doors, and secure their location in order to prevent further infection.

View File

@@ -104,6 +104,17 @@
Heat: 0.9 Heat: 0.9
Poison: 0.9 Poison: 0.9
- type: damageModifierSet
id: Zombie #Blunt resistent and immune to biological threats, but can be hacked apart and burned
coefficients:
Blunt: 0.4
Slash: 0.8
Piercing: 0.9
Shock: 0.8
Cold: 0.2
Heat: 3.0
Poison: 0.0
# Represents which damage types should be modified # Represents which damage types should be modified
# in relation to how they cause bloodloss damage. # in relation to how they cause bloodloss damage.
- type: damageModifierSet - type: damageModifierSet

View File

@@ -15,4 +15,29 @@
- !type:DiseaseReagentCure - !type:DiseaseReagentCure
reagent: Phalanximine reagent: Phalanximine
min: 15 min: 15
### Once radiation is refactored I want it to have a small chance of giving you regular cancer ### Once radiation is refactored I want it to have a small chance of giving you regular cancer
- type: disease
id: ZombieInfection
name: Zombie Infection #This is the incubation period of the zombie disease.
infectious: false
cureResist: 0.2
effects:
- !type:DiseaseHealthChange
probability: 0.05
damage:
types:
Blunt: 3
- !type:DiseaseSnough
probability: 0.005
snoughMessage: disease-cough
snoughSound:
collection: Coughs
- !type:DiseaseProgression
probability: 0.5
comp: DiseaseZombie
cures:
- !type:DiseaseReagentCure
reagent: Romerol
min: 10

View File

@@ -35,3 +35,13 @@
- type: HealthAnalyzer - type: HealthAnalyzer
fake: true fake: true
disease: OwOnavirus disease: OwOnavirus
- type: entity
parent: HandheldHealthAnalyzer
id: HandheldHealthAnalyzerZombie
name: Zombie Infector
suffix: DEBUG
components:
- type: HealthAnalyzer
fake: true
disease: ZombieInfection

View File

@@ -0,0 +1,21 @@
# Special entity used to fill zombie's hands when they turn undead
- type: entity
id: ZombieClaw
name: Zombie Claw
noSpawn: true
components:
- type: Sprite
sprite: Objects/Weapons/Melee/zombie_claw.rsi
state: icon
- type: Item
- type: ZombieTransfer
- type: Unremoveable
- type: MeleeWeapon
damage:
types:
Slash: 13
Piercing: 7
range: 1
arcwidth: 0
arc: claw

View File

@@ -726,6 +726,14 @@
- type: Sprite - type: Sprite
state: xenolab state: xenolab
- type: entity
parent: BaseSign
id: SignZomlab
name: zombie lab sign
components:
- type: Sprite
state: zomlab
- type: entity - type: entity
parent: BaseSign parent: BaseSign
id: SignSecureMedRed id: SignSecureMedRed

View File

@@ -319,6 +319,21 @@
Cellular: -1 Cellular: -1
Radiation: 1 Radiation: 1
- type: reagent
id: Romerol
name: romerol
group: Medicine
desc: A difficult to procure chemical used in the reversal of the zombification process. Tastes like death.
physicalDesc: acrid
color: "#7e916e"
metabolisms:
Medicine:
effects:
- !type:HealthChange
damage:
types:
Poison: 2
- type: reagent - type: reagent
id: PulpedBananaPeel id: PulpedBananaPeel
name: pulped banana peel name: pulped banana peel

View File

@@ -189,6 +189,16 @@
products: products:
Phalanximine: 3 Phalanximine: 3
- type: reaction
id: Romerol
reactants:
Phalanximine:
amount: 1
Tricordrazine:
amount: 1
products:
Romerol: 2
- type: reaction - type: reaction
id: Synaptizine id: Synaptizine
reactants: reactants:

View File

@@ -47,6 +47,14 @@
- Hisssuuu... - Hisssuuu...
- Hiss...! - Hiss...!
- type: accent
id: zombie
words:
- Gruaahhhh...
- Mmuaaaa..
- Braainnssss...
- Grrrrr...
- type: accent - type: accent
id: genericAggressive id: genericAggressive
words: words:

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,22 @@
{
"version": 1,
"license": "CC-BY-SA-3.0",
"copyright": "Illustrated by EmoGarbage",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "inhand-left",
"directions": 4
},
{
"name": "inhand-right",
"directions": 4
}
]
}

View File

@@ -975,6 +975,14 @@
] ]
] ]
}, },
{
"name": "zomlab",
"delays": [
[
1
]
]
},
{ {
"name": "small_secure_red", "name": "small_secure_red",
"delays": [ "delays": [

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB