* Disease system first pass

* Renamed HealthChange

* First working version of diseases (wtf???)

* Fix the cursed yaml initialization

* Pop-Up effect

* Generic status effect

* Create copy of prototype

* CureDiseaseEffect

* Disease resistance

* Spaceacillin

* Nerf spaceacillin now that we know it works

* Sneezing, Coughing, Snoughing

* Fix queuing, prevent future issues

* Disease protection

* Disease outbreak event

* Disease Reagent Cure

* Chem cause disease effect

* Disease artifacts

* Try infect when interacting with diseased

* Diseases don't have to be infectious

* Talking without a mask does a snough

* Temperature cure

* Bedrest

* DiseaseAdjustReagent

* Tweak how disease statuses work to be a bit less shit

* A few more diseases

* Natural immunity (can't get the same disease twice)

* Polished up some diseases, touched up spaceacillin production

* Rebalanced transmission

* Edit a few diseases, make disease cures support a minimum value

* Nitrile gloves, more disease protection sources

* Health scanner shows diseased status

* Clean up disease system

* Traitor item

* Mouth swabs

* Disease diagnoser machine

* Support for clean samples

* Vaccines + fixes

* Pass on disease resistant clothes

* More work on non-infectious diseases & vaccines

* Handle dead bodies

* Added the relatively CBT visualizer

* Pass over diseases and their populators

* Comment stuff

* Readability cleanup

* Add printing sound to diagnoser, fix printing bug

* vaccinator sound, seal up some classes

* Make disease protection equip detection not shit (thanks whoever wrote addaccentcomponent)

* Mirror review

* More review stuff

* More mirror review stuff

* Refactor snoughing

* Redid report creator

* Fix snough messages, new vaccinator sound

* Mirror review naming

* Woops, forgot the artifact

* Add recipes and fills

* Rebalance space cold and robovirus

* Give lizarb disease interaction stuff

* Tweak some stuff and move things around

* Add diseases to mice (since animal vectors are interesting and can be used to make vaccines)

* Remove unused reagent
This commit is contained in:
Rane
2022-03-13 21:02:55 -04:00
committed by GitHub
parent ce01e53579
commit bb9ad4259c
96 changed files with 2555 additions and 39 deletions

View File

@@ -0,0 +1,29 @@
using Robust.Client.GameObjects;
using Content.Shared.Disease;
namespace Content.Client.Disease
{
/// <summary>
/// Controls client-side visuals for the
/// disease machines.
/// </summary>
public sealed class DiseaseMachineSystem : VisualizerSystem<DiseaseMachineVisualsComponent>
{
protected override void OnAppearanceChange(EntityUid uid, DiseaseMachineVisualsComponent component, ref AppearanceChangeEvent args)
{
if (TryComp(uid, out SpriteComponent? sprite)
&& args.Component.TryGetData(DiseaseMachineVisuals.IsOn, out bool isOn)
&& args.Component.TryGetData(DiseaseMachineVisuals.IsRunning, out bool isRunning))
{
var state = isRunning ? component.RunningState : component.IdleState;
sprite.LayerSetVisible(DiseaseMachineVisualLayers.IsOn, isOn);
sprite.LayerSetState(DiseaseMachineVisualLayers.IsRunning, state);
}
}
}
}
public enum DiseaseMachineVisualLayers : byte
{
IsOn,
IsRunning
}

View File

@@ -0,0 +1,15 @@
namespace Content.Client.Disease;
/// <summary>
/// Holds the idle and running state for machines to control
/// playing animtions on the client.
/// </summary>
[RegisterComponent]
public sealed class DiseaseMachineVisualsComponent : Component
{
[DataField("idleState", required: true)]
public string IdleState = default!;
[DataField("runningState", required: true)]
public string RunningState = default!;
}

View File

@@ -37,6 +37,10 @@ namespace Content.Client.Entry
"Healing", "Healing",
"Material", "Material",
"RandomAppearance", "RandomAppearance",
"DiseaseProtection",
"DiseaseDiagnoser",
"DiseaseVaccine",
"DiseaseVaccineCreator",
"Mineable", "Mineable",
"RangedMagazine", "RangedMagazine",
"Ammo", "Ammo",
@@ -47,12 +51,15 @@ namespace Content.Client.Entry
"ResearchClient", "ResearchClient",
"IdCardConsole", "IdCardConsole",
"ThermalRegulator", "ThermalRegulator",
"DiseaseMachineRunning",
"DiseaseMachine",
"AtmosFixMarker", "AtmosFixMarker",
"CablePlacer", "CablePlacer",
"Drink", "Drink",
"Food", "Food",
"DeployableBarrier", "DeployableBarrier",
"MagicMirror", "MagicMirror",
"DiseaseSwab",
"FloorTile", "FloorTile",
"RandomInsulation", "RandomInsulation",
"Electrified", "Electrified",
@@ -62,6 +69,7 @@ namespace Content.Client.Entry
"Bloodstream", "Bloodstream",
"TransformableContainer", "TransformableContainer",
"Mind", "Mind",
"DiseaseCarrier",
"StorageFill", "StorageFill",
"Mop", "Mop",
"Bucket", "Bucket",
@@ -122,6 +130,7 @@ namespace Content.Client.Entry
"RCD", "RCD",
"RCDAmmo", "RCDAmmo",
"CursedEntityStorage", "CursedEntityStorage",
"DiseaseArtifact",
"Radio", "Radio",
"GasArtifact", "GasArtifact",
"SentienceTarget", "SentienceTarget",

View File

@@ -3,6 +3,7 @@ using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls; using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML; using Robust.Client.UserInterface.XAML;
using Content.Shared.Damage.Prototypes; using Content.Shared.Damage.Prototypes;
using Content.Shared.Disease.Components;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Content.Shared.Damage; using Content.Shared.Damage;
@@ -35,7 +36,17 @@ namespace Content.Client.HealthAnalyzer.UI
text.Append($"{Loc.GetString("health-analyzer-window-entity-health-text", ("entityName", entityName))}\n"); text.Append($"{Loc.GetString("health-analyzer-window-entity-health-text", ("entityName", entityName))}\n");
text.Append($"{Loc.GetString("health-analyzer-window-entity-damage-total-text", ("amount", damageable.TotalDamage))}\n"); /// Status Effects / Components
if (entities.HasComponent<DiseasedComponent>(msg.TargetEntity))
{
text.Append($"{Loc.GetString("disease-scanner-diseased")}\n");
}else
{
text.Append($"{Loc.GetString("disease-scanner-not-diseased")}\n");
}
/// Damage
text.Append($"\n{Loc.GetString("health-analyzer-window-entity-damage-total-text", ("amount", damageable.TotalDamage))}\n");
HashSet<string> shownTypes = new(); HashSet<string> shownTypes = new();

View File

@@ -1,5 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using Content.Server.Administration.Logs; using Content.Server.Administration.Logs;
@@ -10,6 +8,9 @@ using Content.Server.MoMMI;
using Content.Server.Players; using Content.Server.Players;
using Content.Server.Preferences.Managers; using Content.Server.Preferences.Managers;
using Content.Server.Radio.EntitySystems; using Content.Server.Radio.EntitySystems;
using Content.Server.Disease;
using Content.Server.Disease.Components;
using Content.Shared.Disease.Components;
using Content.Shared.ActionBlocker; using Content.Shared.ActionBlocker;
using Content.Shared.Administration; using Content.Shared.Administration;
using Content.Shared.CCVar; using Content.Shared.CCVar;
@@ -22,10 +23,6 @@ using Robust.Server.Player;
using Robust.Shared.Audio; using Robust.Shared.Audio;
using Robust.Shared.Configuration; using Robust.Shared.Configuration;
using Robust.Shared.Console; using Robust.Shared.Console;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Localization;
using Robust.Shared.Log;
using Robust.Shared.Network; using Robust.Shared.Network;
using Robust.Shared.Player; using Robust.Shared.Player;
using Robust.Shared.Players; using Robust.Shared.Players;
@@ -208,6 +205,11 @@ namespace Content.Server.Chat.Managers
return; return;
} }
if (_entManager.HasComponent<DiseasedComponent>(source) && _entManager.TryGetComponent<DiseaseCarrierComponent>(source,out var carrier))
{
EntitySystem.Get<DiseaseSystem>().SneezeCough(source, _random.Pick(carrier.Diseases), string.Empty);
}
if (MessageCharacterLimit(source, message)) if (MessageCharacterLimit(source, message))
{ {
return; return;

View File

@@ -0,0 +1,32 @@
using Content.Shared.Chemistry.Reagent;
using Content.Server.Disease;
using Content.Shared.Disease;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <summary>
/// Default metabolism for medicine reagents.
/// </summary>
[UsedImplicitly]
public sealed class ChemCauseDisease : ReagentEffect
{
/// <summary>
/// Chance it has each tick to cause disease, between 0 and 1
/// </summary>
[DataField("causeChance")]
public float CauseChance = 0.15f;
/// <summary>
/// The disease to add.
/// </summary>
[DataField("disease", customTypeSerializer: typeof(PrototypeIdSerializer<DiseasePrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string Disease = string.Empty;
public override void Effect(ReagentEffectArgs args)
{
EntitySystem.Get<DiseaseSystem>().TryAddDisease(null, null, Disease, args.SolutionEntity);
}
}
}

View File

@@ -0,0 +1,25 @@
using Content.Shared.Chemistry.Reagent;
using Content.Server.Disease;
using JetBrains.Annotations;
namespace Content.Server.Chemistry.ReagentEffects
{
/// <summary>
/// Default metabolism for medicine reagents.
/// </summary>
[UsedImplicitly]
public sealed class ChemCureDisease : ReagentEffect
{
/// <summary>
/// Chance it has each tick to cure a disease, between 0 and 1
/// </summary>
[DataField("cureChance")]
public float CureChance = 0.15f;
public override void Effect(ReagentEffectArgs args)
{
var ev = new CureDiseaseAttemptEvent(CureChance);
args.EntityManager.EventBus.RaiseLocalEvent(args.SolutionEntity, ev, false);
}
}
}

View File

@@ -1,7 +1,5 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Content.Shared.Chemistry.Reagent; using Content.Shared.Chemistry.Reagent;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.FixedPoint; using Content.Shared.FixedPoint;
using JetBrains.Annotations; using JetBrains.Annotations;

View File

@@ -0,0 +1,36 @@
using System.Linq;
using Content.Shared.Disease;
namespace Content.Server.Disease.Components
{
[RegisterComponent]
/// <summary>
/// Allows the enity to be infected with diseases.
/// Please use only on mobs.
/// </summary>
public sealed class DiseaseCarrierComponent : Component
{
/// <summary>
/// Shows the CURRENT diseases on the carrier
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public List<DiseasePrototype> Diseases = new();
/// <summary>
/// The carrier's resistance to disease
/// </summary>
[DataField("diseaseResist")]
[ViewVariables(VVAccess.ReadWrite)]
public float DiseaseResist = 0f;
/// <summary>
/// Diseases the carrier has had, used for immunity.
/// <summary>
[ViewVariables(VVAccess.ReadWrite)]
public List<DiseasePrototype> PastDiseases = new();
/// <summary>
/// All the diseases the carrier has or has had.
/// Checked against when trying to add a disease
/// <summary>
[ViewVariables(VVAccess.ReadWrite)]
public List<DiseasePrototype> AllDiseases => PastDiseases.Concat(Diseases).ToList();
}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.Disease.Components
{
/// <summary>
/// To give the disease diagnosing machine specific behavior
/// </summary>
[RegisterComponent]
public sealed class DiseaseDiagnoserComponent : Component
{}
}

View File

@@ -0,0 +1,31 @@
using Content.Shared.Disease;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Disease.Components
{
[RegisterComponent]
/// <summary>
/// For shared behavior between both disease machines
/// </summary>
public sealed class DiseaseMachineComponent : Component
{
[DataField("delay")]
public float Delay = 5f;
/// <summary>
/// How much time we've accumulated processing
/// </summary>
[ViewVariables]
public float Accumulator = 0f;
/// <summary>
/// The disease prototype currently being diagnosed
/// </summary>
[ViewVariables]
public DiseasePrototype? Disease;
/// <summary>
/// What the machine will spawn
/// </summary>
[DataField("machineOutput", customTypeSerializer: typeof(PrototypeIdSerializer<EntityPrototype>), required: true)]
public string MachineOutput = string.Empty;
}
}

View File

@@ -0,0 +1,9 @@
namespace Content.Server.Disease.Components
{
/// <summary>
/// For EntityQuery to keep track of which machines are running
/// <summary>
[RegisterComponent]
public sealed class DiseaseMachineRunningComponent : Component
{}
}

View File

@@ -0,0 +1,24 @@
namespace Content.Server.Disease.Components
{
/// <summary>
/// Value added to clothing to give its wearer
/// protection against infection from diseases
/// </summary>
[RegisterComponent]
public sealed class DiseaseProtectionComponent : Component
{
/// <summary>
/// Float value between 0 and 1, will be subtracted
/// from the infection chance (which is base 0.7)
/// Reference guide is a full biosuit w/gloves & mask
/// should add up to exactly 0.7
/// </summary>
[DataField("protection")]
public float Protection = 0.1f;
/// <summary>
/// Is the component currently being worn and affecting someone's disease
/// resistance? Making the unequip check not totally CBT
/// </summary>
public bool IsActive = false;
}
}

View File

@@ -0,0 +1,33 @@
using System.Threading;
using Content.Shared.Disease;
namespace Content.Server.Disease.Components
{
[RegisterComponent]
/// <summary>
/// For mouth swabs used to collect and process
/// disease samples.
/// </summary>
public sealed class DiseaseSwabComponent : Component
{
/// <summary>
/// How long it takes to swab someone.
/// </summary>
[DataField("swabDelay")]
[ViewVariables]
public float SwabDelay = 2f;
/// <summary>
/// If this swab has been used
/// </summary>
public bool Used = false;
/// <summary>
/// Token for interrupting swabbing do after.
/// </summary>
public CancellationTokenSource? CancelToken;
/// <summary>
/// The disease prototype currently on the swab
/// </summary>
[ViewVariables]
public DiseasePrototype? Disease;
}
}

View File

@@ -0,0 +1,33 @@
using System.Threading;
using Content.Shared.Disease;
namespace Content.Server.Disease.Components
{
[RegisterComponent]
/// <summary>
/// For disease vaccines
/// </summary>
public sealed class DiseaseVaccineComponent : Component
{
/// <summary>
/// How long it takes to inject someone
/// </summary>
[DataField("injectDelay")]
[ViewVariables]
public float InjectDelay = 2f;
/// <summary>
/// If this vaccine has been used
/// </summary>
public bool Used = false;
/// <summary>
/// Token for interrupting injection do after.
/// </summary>
public CancellationTokenSource? CancelToken;
/// <summary>
/// The disease prototype currently on the vaccine
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public DiseasePrototype? Disease;
}
}

View File

@@ -0,0 +1,10 @@
namespace Content.Server.Disease.Components
{
/// <summary>
/// Controls disease machine behavior specific to the
/// vaccine creating machine
/// </summary>
[RegisterComponent]
public sealed class DiseaseVaccineCreatorComponent : Component
{}
}

View File

@@ -0,0 +1,33 @@
using Content.Shared.Disease;
using Content.Server.Buckle.Components;
namespace Content.Server.Disease.Cures
{
/// <summary>
/// Cures the disease after a certain amount of time
/// strapped.
/// </summary>
/// TODO: Revisit after bed pr merged
public sealed class DiseaseBedrestCure : DiseaseCure
{
[ViewVariables(VVAccess.ReadWrite)]
public int Ticker = 0;
[DataField("maxLength", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public int MaxLength = 60;
public override bool Cure(DiseaseEffectArgs args)
{
if (!args.EntityManager.TryGetComponent<BuckleComponent>(args.DiseasedEntity, out var buckle))
return false;
if (buckle.Buckled)
Ticker++;
return Ticker >= MaxLength;
}
public override string CureText()
{
return (Loc.GetString("diagnoser-cure-bedrest", ("time", MaxLength)));
}
}
}

View File

@@ -0,0 +1,34 @@
using Content.Server.Temperature.Components;
using Content.Shared.Disease;
namespace Content.Server.Disease.Cures
{
/// <summary>
/// Cures the disease if temperature is within certain bounds.
/// </summary>
public sealed class DiseaseBodyTemperatureCure : DiseaseCure
{
[DataField("min")]
public float Min = 0;
[DataField("max")]
public float Max = float.MaxValue;
public override bool Cure(DiseaseEffectArgs args)
{
if (!args.EntityManager.TryGetComponent(args.DiseasedEntity, out TemperatureComponent temp))
return false;
return temp.CurrentTemperature > Min && temp.CurrentTemperature < float.MaxValue;
}
public override string CureText()
{
if (Min == 0)
return Loc.GetString("diagnoser-cure-temp-max", ("max", Math.Round(Max)));
if (Max == float.MaxValue)
return Loc.GetString("diagnoser-cure-temp-min", ("min", Math.Round(Min)));
return Loc.GetString("diagnoser-cure-temp-both", ("max", Math.Round(Max)), ("min", Math.Round(Min)));
}
}
}

View File

@@ -0,0 +1,31 @@
using Content.Shared.Disease;
namespace Content.Server.Disease.Cures
{
/// <summary>
/// Automatically removes the disease after a
/// certain amount of time.
/// </summary>
public sealed class DiseaseJustWaitCure : DiseaseCure
{
/// <summary>
/// All of these are in seconds
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
public int Ticker = 0;
[DataField("maxLength", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public int MaxLength = 150;
public override bool Cure(DiseaseEffectArgs args)
{
Ticker++;
return Ticker >= MaxLength;
}
public override string CureText()
{
return Loc.GetString("diagnoser-cure-wait", ("time", MaxLength));
}
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.Disease;
using Content.Shared.FixedPoint;
using Content.Server.Body.Components;
namespace Content.Server.Disease.Cures
{
/// <summary>
/// Cures the disease if a certain amount of reagent
/// is in the host's chemstream.
/// </summary>
public sealed class DiseaseReagentCure : DiseaseCure
{
[DataField("min")]
public FixedPoint2 Min = 5;
[DataField("reagent")]
public string? Reagent;
public override bool Cure(DiseaseEffectArgs args)
{
if (!args.EntityManager.TryGetComponent<BloodstreamComponent>(args.DiseasedEntity, out var bloodstream))
return false;
var quant = FixedPoint2.Zero;
if (Reagent != null && bloodstream.ChemicalSolution.ContainsReagent(Reagent))
{
quant = bloodstream.ChemicalSolution.GetReagentQuantity(Reagent);
}
return quant >= Min;
}
public override string CureText()
{
if (Reagent == null)
return string.Empty;
return (Loc.GetString("diagnoser-cure-reagent", ("units", Min), ("reagent", Reagent)));
}
}
}

View File

@@ -0,0 +1,415 @@
using System.Threading;
using Content.Server.Disease.Components;
using Content.Shared.Disease;
using Content.Shared.Disease.Components;
using Content.Shared.Interaction;
using Content.Shared.Inventory;
using Content.Shared.Examine;
using Content.Server.DoAfter;
using Content.Server.Popups;
using Content.Server.Hands.Components;
using Content.Server.Nutrition.EntitySystems;
using Content.Server.Paper;
using Content.Server.Tools.Components;
using Content.Server.Power.Components;
using Robust.Shared.Random;
using Robust.Shared.Player;
using Robust.Shared.Audio;
using Robust.Shared.Utility;
namespace Content.Server.Disease
{
/// Everything that's about disease diangosis and machines is in here
public sealed class DiseaseDiagnosisSystem : EntitySystem
{
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DiseaseSwabComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<DiseaseSwabComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<DiseaseDiagnoserComponent, AfterInteractUsingEvent>(OnAfterInteractUsing);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, AfterInteractUsingEvent>(OnAfterInteractUsingVaccine);
/// Visuals
SubscribeLocalEvent<DiseaseMachineComponent, PowerChangedEvent>(OnPowerChanged);
/// Private Events
SubscribeLocalEvent<DiseaseDiagnoserComponent, DiseaseMachineFinishedEvent>(OnDiagnoserFinished);
SubscribeLocalEvent<DiseaseVaccineCreatorComponent, DiseaseMachineFinishedEvent>(OnVaccinatorFinished);
SubscribeLocalEvent<TargetSwabSuccessfulEvent>(OnTargetSwabSuccessful);
SubscribeLocalEvent<SwabCancelledEvent>(OnSwabCancelled);
}
private Queue<EntityUid> AddQueue = new();
private Queue<EntityUid> RemoveQueue = new();
/// <summary>
/// This handles running disease machines
/// to handle their delay and visuals.
/// </summary>
public override void Update(float frameTime)
{
foreach (var uid in AddQueue)
EnsureComp<DiseaseMachineRunningComponent>(uid);
AddQueue.Clear();
foreach (var uid in RemoveQueue)
RemComp<DiseaseMachineRunningComponent>(uid);
RemoveQueue.Clear();
foreach (var (runningComp, diseaseMachine) in EntityQuery<DiseaseMachineRunningComponent, DiseaseMachineComponent>(false))
{
if (diseaseMachine.Accumulator < diseaseMachine.Delay)
{
diseaseMachine.Accumulator += frameTime;
return;
}
diseaseMachine.Accumulator = 0;
var ev = new DiseaseMachineFinishedEvent(diseaseMachine);
RaiseLocalEvent(diseaseMachine.Owner, ev, false);
RemoveQueue.Enqueue(diseaseMachine.Owner);
}
}
///
/// Event Handlers
///
/// <summary>
/// This handles using swabs on other people
/// and checks that the swab isn't already used
/// and the other person's mouth is accessible
/// and then adds a random disease from that person
/// to the swab if they have any
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseSwabComponent swab, AfterInteractEvent args)
{
if (swab.CancelToken != null)
{
swab.CancelToken.Cancel();
swab.CancelToken = null;
return;
}
if (args.Target == null || !args.CanReach)
return;
if (!TryComp<DiseaseCarrierComponent>(args.Target, out var carrier))
return;
if (swab.Used)
{
_popupSystem.PopupEntity(Loc.GetString("swab-already-used"), args.User, Filter.Entities(args.User));
return;
}
if (_inventorySystem.TryGetSlotEntity(args.Target.Value, "mask", out var maskUid) &&
EntityManager.TryGetComponent<IngestionBlockerComponent>(maskUid, out var blocker) &&
blocker.Enabled)
{
_popupSystem.PopupEntity(Loc.GetString("swab-mask-blocked", ("target", args.Target), ("mask", maskUid)), args.User, Filter.Entities(args.User));
return;
}
swab.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, swab.SwabDelay, swab.CancelToken.Token, target: args.Target)
{
BroadcastFinishedEvent = new TargetSwabSuccessfulEvent(args.User, args.Target, swab, carrier),
BroadcastCancelledEvent = new SwabCancelledEvent(swab),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
});
}
/// <summary>
/// This handles the disease diagnoser machine up
/// until it's turned on. It has some slight
/// differences in checks from the vaccinator.
/// </summary>
private void OnAfterInteractUsing(EntityUid uid, DiseaseDiagnoserComponent component, AfterInteractUsingEvent args)
{
var machine = Comp<DiseaseMachineComponent>(uid);
if (args.Handled || !args.CanReach)
return;
if (TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
return;
if (!HasComp<HandsComponent>(args.User) || HasComp<ToolComponent>(args.Used)) // Don't want to accidentally breach wrenching or whatever
return;
if (!TryComp<DiseaseSwabComponent>(args.Used, out var swab))
{
_popupSystem.PopupEntity(Loc.GetString("diagnoser-cant-use-swab", ("machine", uid), ("swab", args.Used)), uid, Filter.Entities(args.User));
return;
}
_popupSystem.PopupEntity(Loc.GetString("diagnoser-insert-swab", ("machine", uid), ("swab", args.Used)), uid, Filter.Entities(args.User));
machine.Disease = swab.Disease;
EntityManager.DeleteEntity(args.Used);
AddQueue.Enqueue(uid);
UpdateAppearance(uid, true, true);
SoundSystem.Play(Filter.Pvs(uid), "/Audio/Machines/diagnoser_printing.ogg", uid);
}
/// <summary>
/// This handles the vaccinator machine up
/// until it's turned on. It has some slight
/// differences in checks from the diagnoser.
/// </summary>
private void OnAfterInteractUsingVaccine(EntityUid uid, DiseaseVaccineCreatorComponent component, AfterInteractUsingEvent args)
{
if (args.Handled || !args.CanReach)
return;
if (TryComp<ApcPowerReceiverComponent>(uid, out var power) && !power.Powered)
return;
if (!HasComp<HandsComponent>(args.User) || HasComp<ToolComponent>(args.Used)) //This check ensures tools don't break without yaml ordering jank
return;
if (!TryComp<DiseaseSwabComponent>(args.Used, out var swab) || swab.Disease == null || !swab.Disease.Infectious)
{
_popupSystem.PopupEntity(Loc.GetString("diagnoser-cant-use-swab", ("machine", uid), ("swab", args.Used)), uid, Filter.Entities(args.User));
return;
}
_popupSystem.PopupEntity(Loc.GetString("diagnoser-insert-swab", ("machine", uid), ("swab", args.Used)), uid, Filter.Entities(args.User));
var machine = Comp<DiseaseMachineComponent>(uid);
machine.Disease = swab.Disease;
EntityManager.DeleteEntity(args.Used);
AddQueue.Enqueue(uid);
UpdateAppearance(uid, true, true);
SoundSystem.Play(Filter.Pvs(uid), "/Audio/Machines/vaccinator_running.ogg", uid);
}
/// <summary>
/// This handles swab examination text
/// so you can tell if they are used or not.
/// </summary>
private void OnExamined(EntityUid uid, DiseaseSwabComponent swab, ExaminedEvent args)
{
if (args.IsInDetailsRange)
{
if (swab.Used)
args.PushMarkup(Loc.GetString("swab-used"));
else
args.PushMarkup(Loc.GetString("swab-unused"));
}
}
///
/// Helper functions
///
/// <summary>
/// This assembles a disease report
/// With its basic details and
/// specific cures (i.e. not spaceacillin).
/// The cure resist field tells you how
/// effective spaceacillin etc will be.
/// </summary>
private FormattedMessage AssembleDiseaseReport(DiseasePrototype disease)
{
FormattedMessage report = new();
report.AddMarkup(Loc.GetString("diagnoser-disease-report-name", ("disease", disease.Name)));
report.PushNewline();
if (disease.Infectious)
{
report.AddMarkup(Loc.GetString("diagnoser-disease-report-infectious"));
report.PushNewline();
} else
{
report.AddMarkup(Loc.GetString("diagnoser-disease-report-not-infectious"));
report.PushNewline();
}
string cureResistLine = string.Empty;
cureResistLine += disease.CureResist switch
{
< 0f => Loc.GetString("diagnoser-disease-report-cureresist-none"),
<= 0.05f => Loc.GetString("diagnoser-disease-report-cureresist-low"),
<= 0.14f => Loc.GetString("diagnoser-disease-report-cureresist-medium"),
_ => Loc.GetString("diagnoser-disease-report-cureresist-high")
};
report.AddMarkup(cureResistLine);
report.PushNewline();
/// Add Cures
if (disease.Cures.Count == 0)
{
report.AddMarkup(Loc.GetString("diagnoser-no-cures"));
}
else
{
report.PushNewline();
report.AddMarkup(Loc.GetString("diagnoser-cure-has"));
report.PushNewline();
foreach (var cure in disease.Cures)
{
report.AddMarkup(cure.CureText());
report.PushNewline();
}
}
return report;
}
///
/// Appearance stuff
///
/// <summary>
/// Appearance helper function to
/// set the component's power and running states.
/// </summary>
private void UpdateAppearance(EntityUid uid, bool isOn, bool isRunning)
{
if (!TryComp<AppearanceComponent>(uid, out var appearance))
return;
appearance.SetData(DiseaseMachineVisuals.IsOn, isOn);
appearance.SetData(DiseaseMachineVisuals.IsRunning, isRunning);
}
/// <summary>
/// Makes sure the machine is visually off/on.
/// </summary>
private void OnPowerChanged(EntityUid uid, DiseaseMachineComponent component, PowerChangedEvent args)
{
UpdateAppearance(uid, args.Powered, false);
}
///
/// Private events
///
/// <summary>
/// Copies a disease prototype to the swab
/// after the doafter completes.
/// </summary>
private void OnTargetSwabSuccessful(TargetSwabSuccessfulEvent args)
{
if (args.Target == null)
return;
args.Swab.Used = true;
_popupSystem.PopupEntity(Loc.GetString("swab-swabbed", ("target", args.Target)), args.Target.Value, Filter.Entities(args.User));
if (args.Swab.Disease != null || args.Carrier.Diseases.Count == 0)
return;
args.Swab.Disease = _random.Pick(args.Carrier.Diseases);
}
/// <summary>
/// Cancels the swab doafter if needed.
/// </summary>
private static void OnSwabCancelled(SwabCancelledEvent args)
{
args.Swab.CancelToken = null;
}
/// <summary>
/// Prints a diagnostic report with its findings.
/// Also cancels the animation.
/// </summary>
private void OnDiagnoserFinished(EntityUid uid, DiseaseDiagnoserComponent component, DiseaseMachineFinishedEvent args)
{
var power = Comp<ApcPowerReceiverComponent>(uid);
UpdateAppearance(uid, power.Powered, false);
// spawn a piece of paper.
var printed = EntityManager.SpawnEntity(args.Machine.MachineOutput, Transform(uid).Coordinates);
if (!TryComp<PaperComponent>(printed, out var paper))
return;
var reportTitle = string.Empty;
FormattedMessage contents = new();
if (args.Machine.Disease != null)
{
reportTitle = Loc.GetString("diagnoser-disease-report", ("disease", args.Machine.Disease.Name));
contents = AssembleDiseaseReport(args.Machine.Disease);
} else
{
reportTitle = Loc.GetString("diagnoser-disease-report-none");
contents.AddMarkup(Loc.GetString("diagnoser-disease-report-none-contents"));
}
MetaData(printed).EntityName = reportTitle;
paper.SetContent(contents.ToMarkup());
}
/// <summary>
/// Prints a vaccine that will vaccinate
/// against the disease on the inserted swab.
/// <summary>
private void OnVaccinatorFinished(EntityUid uid, DiseaseVaccineCreatorComponent component, DiseaseMachineFinishedEvent args)
{
var power = Comp<ApcPowerReceiverComponent>(uid);
UpdateAppearance(uid, power.Powered, false);
// spawn a vaccine
var vaxx = EntityManager.SpawnEntity(args.Machine.MachineOutput, Transform(uid).Coordinates);
if (!TryComp<DiseaseVaccineComponent>(vaxx, out var vaxxComp))
return;
vaxxComp.Disease = args.Machine.Disease;
}
/// <summary>
/// Cancels the mouth-swabbing doafter
/// </summary>
private sealed class SwabCancelledEvent : EntityEventArgs
{
public readonly DiseaseSwabComponent Swab;
public SwabCancelledEvent(DiseaseSwabComponent swab)
{
Swab = swab;
}
}
/// <summary>
/// Fires if the doafter for swabbing someone's mouth succeeds
/// </summary>
private sealed class TargetSwabSuccessfulEvent : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid? Target { get; }
public DiseaseSwabComponent Swab { get; }
public DiseaseCarrierComponent Carrier { get; }
public TargetSwabSuccessfulEvent(EntityUid user, EntityUid? target, DiseaseSwabComponent swab, DiseaseCarrierComponent carrier)
{
User = user;
Target = target;
Swab = swab;
Carrier = carrier;
}
}
/// <summary>
/// Fires when a disease machine is done
/// with its production delay and ready to
/// create a report or vaccine
/// </summary>
private sealed class DiseaseMachineFinishedEvent : EntityEventArgs
{
public DiseaseMachineComponent Machine {get;}
public DiseaseMachineFinishedEvent(DiseaseMachineComponent machine)
{
Machine = machine;
}
}
}
}

View File

@@ -0,0 +1,442 @@
using System.Threading;
using Content.Shared.Disease;
using Content.Shared.Disease.Components;
using Content.Server.Disease.Components;
using Content.Server.Clothing.Components;
using Content.Shared.MobState.Components;
using Content.Shared.Examine;
using Content.Shared.Inventory;
using Content.Shared.Interaction;
using Content.Server.Popups;
using Content.Server.DoAfter;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Random;
using Robust.Shared.Serialization.Manager;
using Content.Shared.Inventory.Events;
using Content.Server.Nutrition.EntitySystems;
namespace Content.Server.Disease
{
/// <summary>
/// Handles disease propagation & curing
/// </summary>
public sealed class DiseaseSystem : EntitySystem
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly ISerializationManager _serializationManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
[Dependency] private readonly InventorySystem _inventorySystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DiseaseCarrierComponent, CureDiseaseAttemptEvent>(OnTryCureDisease);
SubscribeLocalEvent<DiseasedComponent, InteractHandEvent>(OnInteractDiseasedHand);
SubscribeLocalEvent<DiseasedComponent, InteractUsingEvent>(OnInteractDiseasedUsing);
SubscribeLocalEvent<DiseaseProtectionComponent, GotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<DiseaseProtectionComponent, GotUnequippedEvent>(OnUnequipped);
SubscribeLocalEvent<DiseaseVaccineComponent, AfterInteractEvent>(OnAfterInteract);
SubscribeLocalEvent<DiseaseVaccineComponent, ExaminedEvent>(OnExamined);
/// Private events stuff
SubscribeLocalEvent<TargetVaxxSuccessfulEvent>(OnTargetVaxxSuccessful);
SubscribeLocalEvent<VaxxCancelledEvent>(OnVaxxCancelled);
}
private Queue<EntityUid> AddQueue = new();
private Queue<(DiseaseCarrierComponent carrier, DiseasePrototype disease)> CureQueue = new();
/// <summary>
/// First, adds or removes diseased component from the queues and clears them.
/// Then, iterates over every diseased component to check for their effects
/// and cures
/// </summary>
public override void Update(float frameTime)
{
base.Update(frameTime);
foreach (var entity in AddQueue)
EnsureComp<DiseasedComponent>(entity);
AddQueue.Clear();
foreach (var tuple in CureQueue)
{
if (tuple.carrier.Diseases.Count == 1) //This is reliable unlike testing Count == 0 right after removal for reasons I don't quite get
RemComp<DiseasedComponent>(tuple.carrier.Owner);
tuple.carrier.PastDiseases.Add(tuple.disease);
tuple.carrier.Diseases.Remove(tuple.disease);
}
CureQueue.Clear();
foreach (var (diseasedComp, carrierComp, mobState) in EntityQuery<DiseasedComponent, DiseaseCarrierComponent, MobStateComponent>(false))
{
if (mobState.IsDead())
{
if (_random.Prob(0.005f * frameTime)) //Mean time to remove is 200 seconds per disease
CureDisease(carrierComp, _random.Pick(carrierComp.Diseases));
continue;
}
foreach(var disease in carrierComp.Diseases)
{
var args = new DiseaseEffectArgs(carrierComp.Owner, disease, EntityManager);
disease.Accumulator += frameTime;
if (disease.Accumulator >= disease.TickTime)
{
disease.Accumulator -= disease.TickTime;
foreach (var cure in disease.Cures)
{
if (cure.Cure(args))
CureDisease(carrierComp, disease);
}
foreach (var effect in disease.Effects)
{
if (_random.Prob(effect.Probability))
effect.Effect(args);
}
}
}
}
}
///
/// Event Handlers
///
/// <summary>
/// Used when something is trying to cure ANY disease on the target,
/// not for special disease interactions. Randomly
/// tries to cure every disease on the target.
/// </summary>
private void OnTryCureDisease(EntityUid uid, DiseaseCarrierComponent component, CureDiseaseAttemptEvent args)
{
foreach (var disease in component.Diseases)
{
var cureProb = ((args.CureChance / component.Diseases.Count) - disease.CureResist);
if (cureProb < 0)
return;
if (cureProb > 1)
{
CureDisease(component, disease);
return;
}
if (_random.Prob(cureProb))
{
CureDisease(component, disease);
return;
}
}
}
/// <summary>
/// Called when a component with disease protection
/// is equipped so it can be added to the person's
/// total disease resistance
/// </summary>
private void OnEquipped(EntityUid uid, DiseaseProtectionComponent component, GotEquippedEvent args)
{
/// This only works on clothing
if (!TryComp<ClothingComponent>(uid, out var clothing))
return;
/// Is the clothing in its actual slot?
if (!clothing.SlotFlags.HasFlag(args.SlotFlags))
return;
/// Give the user the component's disease resist
if(TryComp<DiseaseCarrierComponent>(args.Equipee, out var carrier))
carrier.DiseaseResist += component.Protection;
/// Set the component to active to the unequip check isn't CBT
component.IsActive = true;
}
/// <summary>
/// Called when a component with disease protection
/// is unequipped so it can be removed from the person's
/// total disease resistance
/// </summary>
private void OnUnequipped(EntityUid uid, DiseaseProtectionComponent component, GotUnequippedEvent args)
{
/// Only undo the resistance if it was affecting the user
if (!component.IsActive)
return;
if(TryComp<DiseaseCarrierComponent>(args.Equipee, out var carrier))
carrier.DiseaseResist -= component.Protection;
component.IsActive = false;
}
/// <summary>
/// Called when it's already decided a disease will be cured
/// so it can be safely queued up to be removed from the target
/// and added to past disease history (for immunity)
/// </summary>
private void CureDisease(DiseaseCarrierComponent carrier, DiseasePrototype disease)
{
var CureTuple = (carrier, disease);
CureQueue.Enqueue(CureTuple);
_popupSystem.PopupEntity(Loc.GetString("disease-cured"), carrier.Owner, Filter.Entities(carrier.Owner));
}
/// <summary>
/// Called when someone interacts with a diseased person with an empty hand
/// to check if they get infected
/// </summary>
private void OnInteractDiseasedHand(EntityUid uid, DiseasedComponent component, InteractHandEvent args)
{
if (!_interactionSystem.InRangeUnobstructed(args.User, args.Target))
return;
InteractWithDiseased (args.Target, args.User);
}
/// <summary>
/// Called when someone interacts with a diseased person with any object
/// to check if they get infected
/// </summary>
private void OnInteractDiseasedUsing(EntityUid uid, DiseasedComponent component, InteractUsingEvent args)
{
InteractWithDiseased(args.Target, args.User);
}
/// <summary>
/// Called when a vaccine is used on someone
/// to handle the vaccination doafter
/// </summary>
private void OnAfterInteract(EntityUid uid, DiseaseVaccineComponent vaxx, AfterInteractEvent args)
{
if (vaxx.CancelToken != null)
{
vaxx.CancelToken.Cancel();
vaxx.CancelToken = null;
return;
}
if (args.Target == null)
return;
if (!args.CanReach)
return;
if (vaxx.CancelToken != null)
return;
if (!TryComp<DiseaseCarrierComponent>(args.Target, out var carrier))
return;
if (vaxx.Used)
{
_popupSystem.PopupEntity(Loc.GetString("vaxx-already-used"), args.User, Filter.Entities(args.User));
return;
}
vaxx.CancelToken = new CancellationTokenSource();
_doAfterSystem.DoAfter(new DoAfterEventArgs(args.User, vaxx.InjectDelay, vaxx.CancelToken.Token, target: args.Target)
{
BroadcastFinishedEvent = new TargetVaxxSuccessfulEvent(args.User, args.Target, vaxx, carrier),
BroadcastCancelledEvent = new VaxxCancelledEvent(vaxx),
BreakOnTargetMove = true,
BreakOnUserMove = true,
BreakOnStun = true,
NeedHand = true
});
}
/// <summary>
/// Called when a vaccine is examined.
/// Currently doesn't do much because
/// vaccines don't have unique art with a seperate
/// state visualizer.
/// </summary>
private void OnExamined(EntityUid uid, DiseaseVaccineComponent vaxx, ExaminedEvent args)
{
if (args.IsInDetailsRange)
{
if (vaxx.Used)
args.PushMarkup(Loc.GetString("vaxx-used"));
else
args.PushMarkup(Loc.GetString("vaxx-unused"));
}
}
///
/// Helper functions
///
/// <summary>
/// Tries to infect anyone that
/// interacts with a diseased person or body
/// </summary>
private void InteractWithDiseased(EntityUid diseased, EntityUid target)
{
if (!TryComp<DiseaseCarrierComponent>(target, out var carrier))
return;
var disease = _random.Pick(Comp<DiseaseCarrierComponent>(diseased).Diseases);
if (disease != null)
TryInfect(carrier, disease, 0.4f);
}
/// <summary>
/// Adds a disease to a target
/// if it's not already in their current
/// or past diseases. If you want this
/// to not be guaranteed you are looking
/// for TryInfect.
/// </summary>
public void TryAddDisease(DiseaseCarrierComponent? target, DiseasePrototype? addedDisease, string? diseaseName = null, EntityUid host = default!)
{
if (diseaseName != null && _prototypeManager.TryIndex(diseaseName, out DiseasePrototype? diseaseProto))
addedDisease = diseaseProto;
if (host != default!)
target = Comp<DiseaseCarrierComponent>(host);
if (target != null)
{
foreach (var disease in target.AllDiseases)
{
if (disease.ID == addedDisease?.ID) //ID because of the way protoypes work
return;
}
var freshDisease = _serializationManager.CreateCopy(addedDisease) ?? default!;
target.Diseases.Add(freshDisease);
AddQueue.Enqueue(target.Owner);
}
}
/// <summary>
/// Pits the infection chance against the
/// person's disease resistance and
/// rolls the dice to see if they get
/// the disease.
/// </summary>
public void TryInfect(DiseaseCarrierComponent carrier, DiseasePrototype? disease, float chance = 0.7f)
{
if(disease == null || !disease.Infectious)
return;
var infectionChance = chance - carrier.DiseaseResist;
if (infectionChance <= 0)
return;
if (_random.Prob(infectionChance))
TryAddDisease(carrier, disease);
}
/// <summary>
/// Plays a sneeze/cough popup if applicable
/// and then tries to infect anyone in range
/// if the snougher is not wearing a mask.
/// </summary>
public void SneezeCough(EntityUid uid, DiseasePrototype? disease, string snoughMessage, bool airTransmit = true, float infectionChance = 0.3f)
{
var xform = Comp<TransformComponent>(uid);
if (snoughMessage != string.Empty)
_popupSystem.PopupEntity(Loc.GetString(snoughMessage, ("person", uid)), uid, Filter.Pvs(uid));
if (disease == null || !disease.Infectious || airTransmit == false)
return;
if (_inventorySystem.TryGetSlotEntity(uid, "mask", out var maskUid) &&
EntityManager.TryGetComponent<IngestionBlockerComponent>(maskUid, out var blocker) &&
blocker.Enabled)
return;
foreach (var entity in _lookup.GetEntitiesInRange(xform.MapID, xform.WorldPosition, 2f))
{
if (!_interactionSystem.InRangeUnobstructed(uid, entity))
continue;
if (TryComp<DiseaseCarrierComponent>(entity, out var carrier))
TryInfect(carrier, disease, 0.3f);
}
}
/// <summary>
/// Adds a disease to the carrier's
/// past diseases to give them immunity
/// IF they don't already have the disease.
/// <summary>
public void Vaccinate(DiseaseCarrierComponent carrier, DiseasePrototype disease)
{
foreach (var currentDisease in carrier.Diseases)
{
if (currentDisease.ID == disease.ID) //ID because of the way protoypes work
return;
}
carrier.PastDiseases.Add(disease);
}
///
/// Private Events Stuff
///
/// <summary>
/// Injects the vaccine into the target
/// if the doafter is completed
/// </summary>
private void OnTargetVaxxSuccessful(TargetVaxxSuccessfulEvent args)
{
if (args.Vaxx.Disease == null)
return;
Vaccinate(args.Carrier, args.Vaxx.Disease);
EntityManager.DeleteEntity(args.Vaxx.Owner);
}
/// <summary>
/// Cancels the vaccine doafter
/// </summary>
private static void OnVaxxCancelled(VaxxCancelledEvent args)
{
args.Vaxx.CancelToken = null;
}
/// These two are standard doafter stuff you can ignore
private sealed class VaxxCancelledEvent : EntityEventArgs
{
public readonly DiseaseVaccineComponent Vaxx;
public VaxxCancelledEvent(DiseaseVaccineComponent vaxx)
{
Vaxx = vaxx;
}
}
private sealed class TargetVaxxSuccessfulEvent : EntityEventArgs
{
public EntityUid User { get; }
public EntityUid? Target { get; }
public DiseaseVaccineComponent Vaxx { get; }
public DiseaseCarrierComponent Carrier { get; }
public TargetVaxxSuccessfulEvent(EntityUid user, EntityUid? target, DiseaseVaccineComponent vaxx, DiseaseCarrierComponent carrier)
{
User = user;
Target = target;
Vaxx = vaxx;
Carrier = carrier;
}
}
}
/// <summary>
/// This event is fired by chems
/// and other brute-force rather than
/// specific cures. It will roll the dice to attempt
/// to cure each disease on the target
/// </summary>
public sealed class CureDiseaseAttemptEvent : EntityEventArgs
{
public float CureChance { get; }
public CureDiseaseAttemptEvent(float cureChance)
{
CureChance = cureChance;
}
}
/// <summary>
/// Controls whether the snough is a sneeze, cough
/// or neither. If none, will not create
/// a popup. Mostly used for talking
/// </summary>
public enum SneezeCoughType
{
Sneeze,
Cough,
None
}
}

View File

@@ -0,0 +1,46 @@
using Content.Server.Chemistry.EntitySystems;
using Content.Shared.Chemistry.Reagent;
using Content.Shared.FixedPoint;
using JetBrains.Annotations;
using Content.Server.Body.Components;
using Content.Shared.Disease;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Disease.Effects
{
/// <summary>
/// Adds or removes reagents from the
/// host's chemstream.
/// </summary>
[UsedImplicitly]
public sealed class DiseaseAdjustReagent : DiseaseEffect
{
/// <summary>
/// The reagent ID to add or remove.
/// </summary>
[DataField("reagent", customTypeSerializer:typeof(PrototypeIdSerializer<ReagentPrototype>))]
public string? Reagent = null;
[DataField("amount", required: true)]
public FixedPoint2 Amount = default!;
public override void Effect(DiseaseEffectArgs args)
{
if (!args.EntityManager.TryGetComponent<BloodstreamComponent>(args.DiseasedEntity, out var bloodstream))
return;
var stream = bloodstream.ChemicalSolution;
if (stream != null)
{
var solutionSys = args.EntityManager.EntitySysManager.GetEntitySystem<SolutionContainerSystem>();
if (Reagent != null)
{
if (Amount < 0 && stream.ContainsReagent(Reagent))
solutionSys.TryRemoveReagent(args.DiseasedEntity, stream, Reagent, -Amount);
if (Amount > 0)
solutionSys.TryAddReagent(args.DiseasedEntity, stream, Reagent, Amount, out _);
}
}
}
}
}

View File

@@ -0,0 +1,67 @@
using Content.Shared.Disease;
using Content.Shared.StatusEffect;
using JetBrains.Annotations;
namespace Content.Server.Disease.Effects
{
/// <summary>
/// Adds a generic status effect to the entity.
/// Differs from the chem version in its defaults
/// to better facilitate adding components that
/// last the length of the disease.
/// </summary>
[UsedImplicitly]
public sealed class DiseaseGenericStatusEffect : DiseaseEffect
{
/// <summary>
/// The status effect key
/// Prevents other components from being with the same key
/// </summary>
[DataField("key", required: true)]
public string Key = default!;
/// <summary>
/// The component to add
/// </summary>
[DataField("component")]
public string Component = String.Empty;
[DataField("time")]
public float Time = 1.01f; /// I'm afraid if this was exact the key could get stolen by another thing
/// <remarks>
/// true - refresh status effect time, false - accumulate status effect time
/// </remarks>
[DataField("refresh")]
public bool Refresh = false;
/// <summary>
/// Should this effect add the status effect, remove time from it, or set its cooldown?
/// </summary>
[DataField("type")]
public StatusEffectDiseaseType Type = StatusEffectDiseaseType.Add;
public override void Effect(DiseaseEffectArgs args)
{
var statusSys = EntitySystem.Get<StatusEffectsSystem>();
if (Type == StatusEffectDiseaseType.Add && Component != String.Empty)
{
statusSys.TryAddStatusEffect(args.DiseasedEntity, Key, TimeSpan.FromSeconds(Time), Refresh, Component);
}
else if (Type == StatusEffectDiseaseType.Remove)
{
statusSys.TryRemoveTime(args.DiseasedEntity, Key, TimeSpan.FromSeconds(Time));
}
else if (Type == StatusEffectDiseaseType.Set)
{
statusSys.TrySetTime(args.DiseasedEntity, Key, TimeSpan.FromSeconds(Time));
}
}
}
/// See status effects for how these work
public enum StatusEffectDiseaseType
{
Add,
Remove,
Set
}
}

View File

@@ -0,0 +1,21 @@
using Content.Shared.Disease;
using Content.Shared.Damage;
using JetBrains.Annotations;
namespace Content.Server.Disease.Effects
{
/// <summary>
/// Deals or heals damage to the host
/// </summary>
[UsedImplicitly]
public sealed class DiseaseHealthChange : DiseaseEffect
{
[DataField("damage", required: true)]
[ViewVariables(VVAccess.ReadWrite)]
public DamageSpecifier Damage = default!;
public override void Effect(DiseaseEffectArgs args)
{
EntitySystem.Get<DamageableSystem>().TryChangeDamage(args.DiseasedEntity, Damage, true, false);
}
}
}

View File

@@ -0,0 +1,38 @@
using Content.Shared.Disease;
using Content.Shared.Popups;
using Robust.Shared.Player;
using JetBrains.Annotations;
namespace Content.Server.Disease.Effects
{
[UsedImplicitly]
/// <summary>
/// Plays a popup on the host's transform.
/// Supports passing the host's entity metadata
/// in PVS ones with {$person}
/// </summary>
public sealed class DiseasePopUp : DiseaseEffect
{
[DataField("message")]
public string Message = "disease-sick-generic";
[DataField("type")]
public PopupType Type = PopupType.Local;
public override void Effect(DiseaseEffectArgs args)
{
var popupSys = EntitySystem.Get<SharedPopupSystem>();
if (Type == PopupType.Local)
popupSys.PopupEntity(Loc.GetString(Message), args.DiseasedEntity, Filter.Entities(args.DiseasedEntity));
else if (Type == PopupType.Pvs)
popupSys.PopupEntity(Loc.GetString(Message, ("person", args.DiseasedEntity)), args.DiseasedEntity, Filter.Pvs(args.DiseasedEntity));
}
}
public enum PopupType
{
Pvs,
Local
}
}

View File

@@ -0,0 +1,30 @@
using Content.Shared.Disease;
using JetBrains.Annotations;
namespace Content.Server.Disease
{
[UsedImplicitly]
/// <summary>
/// Makes the diseased sneeze or cough
/// or neither.
/// </summary>
public sealed class DiseaseSnough : DiseaseEffect
{
/// <summary>
/// Message to play when snoughing
/// </summary>
[DataField("snoughMessage")]
public string SnoughMessage = "disease-sneeze";
/// <summary>
/// Whether to spread the disease throught he air
/// </summary>
[DataField("airTransmit")]
public bool AirTransmit = true;
public override void Effect(DiseaseEffectArgs args)
{
EntitySystem.Get<DiseaseSystem>().SneezeCough(args.DiseasedEntity, args.Disease, SnoughMessage, AirTransmit);
}
}
}

View File

@@ -17,6 +17,7 @@ namespace Content.Server.Entry
"ClientEntitySpawner", "ClientEntitySpawner",
"CharacterInfo", "CharacterInfo",
"ItemCabinetVisuals", "ItemCabinetVisuals",
"DiseaseMachineVisuals",
"HandheldGPS", "HandheldGPS",
"PotencyVisuals" "PotencyVisuals"
}; };

View File

@@ -1,7 +1,9 @@
using System.Threading; using System.Threading;
using Content.Server.UserInterface; using Content.Server.UserInterface;
using Content.Shared.MedicalScanner; using Content.Shared.MedicalScanner;
using Content.Shared.Disease;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Medical.Components namespace Content.Server.Medical.Components
{ {
@@ -23,5 +25,19 @@ namespace Content.Server.Medical.Components
/// </summary> /// </summary>
public CancellationTokenSource? CancelToken; public CancellationTokenSource? CancelToken;
public BoundUserInterface? UserInterface => Owner.GetUIOrNull(HealthAnalyzerUiKey.Key); public BoundUserInterface? UserInterface => Owner.GetUIOrNull(HealthAnalyzerUiKey.Key);
/// <summary>
/// Is this actually going to give people the disease below
/// </summary>
[DataField("fake")]
[ViewVariables(VVAccess.ReadWrite)]
public bool Fake = false;
/// <summary>
/// The disease this will give people if Fake == true
/// </summary>
[DataField("disease", customTypeSerializer: typeof(PrototypeIdSerializer<DiseasePrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string Disease = string.Empty;
} }
} }

View File

@@ -1,10 +1,13 @@
using System.Threading; using System.Threading;
using Content.Server.DoAfter; using Content.Server.DoAfter;
using Content.Server.Medical.Components; using Content.Server.Medical.Components;
using Content.Server.Disease;
using Content.Server.Popups;
using Content.Shared.Damage; using Content.Shared.Damage;
using Content.Shared.Interaction; using Content.Shared.Interaction;
using Content.Shared.MobState.Components; using Content.Shared.MobState.Components;
using Robust.Server.GameObjects; using Robust.Server.GameObjects;
using Robust.Shared.Player;
using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent; using static Content.Shared.MedicalScanner.SharedHealthAnalyzerComponent;
namespace Content.Server.Medical namespace Content.Server.Medical
@@ -12,6 +15,7 @@ namespace Content.Server.Medical
public sealed class HealthAnalyzerSystem : EntitySystem public sealed class HealthAnalyzerSystem : EntitySystem
{ {
[Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!;
[Dependency] private readonly PopupSystem _popupSystem = default!;
public override void Initialize() public override void Initialize()
{ {
@@ -64,6 +68,22 @@ namespace Content.Server.Medical
{ {
args.Component.CancelToken = null; args.Component.CancelToken = null;
UpdateScannedUser(args.Component.Owner, args.User, args.Target, args.Component); UpdateScannedUser(args.Component.Owner, args.User, args.Target, args.Component);
/// Below is for the traitor item
/// Piggybacking off another component's doafter is complete CBT so I gave up
/// and put it on the same component
if (!args.Component.Fake || args.Component.Disease == string.Empty || args.Target == null)
return;
EntitySystem.Get<DiseaseSystem>().TryAddDisease(null, null, args.Component.Disease, args.Target.Value);
if (args.User == args.Target)
{
_popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-self", ("disease", args.Component.Disease)),
args.User, Filter.Entities(args.User));
return;
}
_popupSystem.PopupEntity(Loc.GetString("disease-scanner-gave-other", ("target", args.Target), ("disease", args.Component.Disease)),
args.User, Filter.Entities(args.User));
} }
private void OpenUserInterface(EntityUid user, HealthAnalyzerComponent healthAnalyzer) private void OpenUserInterface(EntityUid user, HealthAnalyzerComponent healthAnalyzer)

View File

@@ -0,0 +1,62 @@
using System.Linq;
using Content.Server.Chat.Managers;
using Content.Server.Disease.Components;
using Content.Server.Disease;
using Content.Shared.Disease;
using Robust.Shared.Random;
using Robust.Shared.Prototypes;
namespace Content.Server.StationEvents.Events;
/// <summary>
/// Infects a couple people
/// with a random disease that isn't super deadly
/// </summary>
public sealed class DiseaseOutbreak : StationEvent
{
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
/// <summary>
/// Disease prototypes I decided were not too deadly for a random event
/// </summary>
public readonly IReadOnlyList<string> NotTooSeriousDiseases = new[]
{
"SpaceCold",
"VanAusdallsRobovirus",
"VentCough",
"AMIV"
};
public override string Name => "DiseaseOutbreak";
public override float Weight => WeightNormal;
protected override float EndAfter => 1.0f;
/// <summary>
/// Finds 2-5 random entities that can host diseases
/// and gives them a randomly selected disease.
/// They all get the same disease.
/// </summary>
public override void Startup()
{
base.Startup();
var targetList = _entityManager.EntityQuery<DiseaseCarrierComponent>().ToList();
_random.Shuffle(targetList);
var toInfect = _random.Next(2, 5);
var diseaseName = _random.Pick(NotTooSeriousDiseases);
if (!_prototypeManager.TryIndex(diseaseName, out DiseasePrototype? disease) || disease == null)
return;
foreach (var target in targetList)
{
if (toInfect-- == 0)
break;
EntitySystem.Get<DiseaseSystem>().TryAddDisease(target, disease);
}
_chatManager.DispatchStationAnnouncement(Loc.GetString("station-event-disease-outbreak-announcement"));
}
}

View File

@@ -42,7 +42,7 @@ public sealed class VentClog : StationEvent
public readonly IReadOnlyList<string> SafeishVentChemicals = new[] public readonly IReadOnlyList<string> SafeishVentChemicals = new[]
{ {
"Water", "Iron", "Oxygen", "Tritium", "Plasma", "SulfuricAcid", "Blood", "SpaceDrugs", "SpaceCleaner", "Flour", "Water", "Iron", "Oxygen", "Tritium", "Plasma", "SulfuricAcid", "Blood", "SpaceDrugs", "SpaceCleaner", "Flour",
"Nutriment", "Sugar", "SpaceLube", "Ethanol", "Mercury", "Ephedrine", "WeldingFuel" "Nutriment", "Sugar", "SpaceLube", "Ethanol", "Mercury", "Ephedrine", "WeldingFuel", "VentCrud"
}; };
public override void Startup() public override void Startup()

View File

@@ -0,0 +1,37 @@
using Content.Shared.Disease;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
/// <summary>
/// Spawn a random disease at regular intervals when artifact activated.
/// </summary>
[RegisterComponent]
public sealed class DiseaseArtifactComponent : Component
{
public override string Name => "DiseaseArtifact";
/// <summary>
/// Disease the artifact will spawn
/// If empty, picks a random one from its list
/// </summary>
[DataField("disease", customTypeSerializer: typeof(PrototypeIdSerializer<DiseasePrototype>))]
[ViewVariables(VVAccess.ReadWrite)]
public string SpawnDisease = string.Empty;
/// <summary>
/// How far away it will check for people
/// If empty, picks a random one from its list
/// </summary>
[DataField("range")]
[ViewVariables(VVAccess.ReadWrite)]
public float Range = 5f;
[ViewVariables(VVAccess.ReadWrite)]
public DiseasePrototype ResolveDisease = default!;
[ViewVariables(VVAccess.ReadWrite)]
public readonly IReadOnlyList<string> ArtifactDiseases = new[]
{
"VanAusdallsRobovirus",
"OwOnavirus",
"BleedersBite",
"Ultragigacancer",
"AMIV"
};
}

View File

@@ -0,0 +1,63 @@
using Content.Server.Xenoarchaeology.XenoArtifacts.Events;
using Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Components;
using Content.Shared.Disease;
using Content.Server.Disease;
using Content.Server.Disease.Components;
using Robust.Shared.Random;
using Robust.Shared.Prototypes;
using Content.Shared.Interaction;
namespace Content.Server.Xenoarchaeology.XenoArtifacts.Effects.Systems
{
/// <summary>
/// Handles disease-producing artifacts
/// </summary>
public sealed class DiseaseArtifactSystem : EntitySystem
{
[Dependency] private readonly EntityLookupSystem _lookup = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IRobustRandom _random = default!;
[Dependency] private readonly SharedInteractionSystem _interactionSystem = default!;
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<DiseaseArtifactComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<DiseaseArtifactComponent, ArtifactActivatedEvent>(OnActivate);
}
/// <summary>
/// Makes sure this artifact is assigned a disease
/// </summary>
private void OnMapInit(EntityUid uid, DiseaseArtifactComponent component, MapInitEvent args)
{
if (component.SpawnDisease == string.Empty && component.ArtifactDiseases.Count != 0)
{
var diseaseName = _random.Pick(component.ArtifactDiseases);
component.SpawnDisease = diseaseName;
}
if (_prototypeManager.TryIndex(component.SpawnDisease, out DiseasePrototype? disease) && disease != null)
component.ResolveDisease = disease;
}
/// <summary>
/// When activated, blasts everyone in LOS within 3 tiles
/// with a high-probability disease infection attempt
/// </summary>
private void OnActivate(EntityUid uid, DiseaseArtifactComponent component, ArtifactActivatedEvent args)
{
var xform = Transform(uid);
foreach (var entity in _lookup.GetEntitiesInRange(xform.MapID, xform.WorldPosition, 3f))
{
if (!_interactionSystem.InRangeUnobstructed(uid, entity, 3f))
continue;
if (TryComp<DiseaseCarrierComponent>(entity, out var carrier))
EntitySystem.Get<DiseaseSystem>().TryInfect(carrier, component.ResolveDisease);
}
}
}
}

View File

@@ -1,7 +1,5 @@
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using JetBrains.Annotations; using JetBrains.Annotations;
using Robust.Shared.GameObjects;
using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Shared.Chemistry.Reagent namespace Content.Shared.Chemistry.Reagent
{ {

View File

@@ -0,0 +1,24 @@
using JetBrains.Annotations;
namespace Content.Shared.Disease
{
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public abstract class DiseaseCure
{
/// <summary>
/// This returns true if the disease should be cured
/// and false otherwise
/// </summary>
public abstract bool Cure(DiseaseEffectArgs args);
/// <summary>
/// This is used by the disease diangoser machine
/// to generate reports to tell people all of a disease's
/// special cures using in-game methods.
/// So it should return a localization string describing
/// the cure
/// </summary>
public abstract string CureText();
}
}

View File

@@ -0,0 +1,29 @@
using JetBrains.Annotations;
namespace Content.Shared.Disease
{
[ImplicitDataDefinitionForInheritors]
[MeansImplicitUse]
public abstract class DiseaseEffect
{
/// <summary>
/// What's the chance, from 0 to 1, that this effect will occur?
/// </summary>
[DataField("probability")]
public float Probability = 1.0f;
/// <summary>
/// What effect the disease will have.
/// </summary>
public abstract void Effect(DiseaseEffectArgs args);
}
/// <summary>
/// What you have to work with in any disease effect/cure.
/// Includes an entity manager because it is out of scope
/// otherwise.
/// </summary>
public readonly record struct DiseaseEffectArgs(
EntityUid DiseasedEntity,
DiseasePrototype Disease,
IEntityManager EntityManager
);
}

View File

@@ -0,0 +1,16 @@
using Robust.Shared.Serialization;
namespace Content.Shared.Disease
{
[Serializable, NetSerializable]
/// <summary>
/// Stores bools for if the machine is on
/// and if it's currently running.
/// Used for the visualizer
/// </summary>
public enum DiseaseMachineVisuals : byte
{
IsOn,
IsRunning
}
}

View File

@@ -0,0 +1,68 @@
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
/// <summary>
/// Diseases encompass everything from viruses to cancers to heart disease.
/// It's not just a virology thing.
/// </summary>
namespace Content.Shared.Disease
{
[Prototype("disease")]
[DataDefinition]
public sealed class DiseasePrototype : IPrototype, IInheritingPrototype
{
[ViewVariables]
[DataField("id", required: true)]
public string ID { get; } = default!;
[DataField("name")]
public string Name { get; } = string.Empty;
[DataField("parent", customTypeSerializer: typeof(PrototypeIdSerializer<DiseasePrototype>))]
public string? Parent { get; private set; }
[NeverPushInheritance]
[DataField("abstract")]
public bool Abstract { get; private set; }
/// <summary>
/// Controls how often a disease ticks.
/// </summary>
public float TickTime = 1f;
/// <summary>
/// Since disease isn't mapped to metabolism or anything,
/// it needs something to control its tickrate
/// </summary>
public float Accumulator = 0f;
/// <summary>
/// List of effects the disease has that will
/// run every second (by default anyway)
/// </summary>
[DataField("effects", serverOnly: true)]
public readonly List<DiseaseEffect> Effects = new(0);
/// <summary>
/// List of SPECIFIC CURES the disease has that will
/// be checked every second.
/// Stuff like spaceacillin operates outside this.
/// </summary>
[DataField("cures", serverOnly: true)]
public readonly List<DiseaseCure> Cures = new(0);
/// <summary>
/// This flatly reduces the probabilty disease medicine
/// has to cure it every tick. Although, since spaceacillin is
/// used as a reference and it has 0.15 chance, this is
/// a base 33% reduction in cure chance
/// </summary>
[DataField("cureResist", serverOnly: true)]
public float CureResist = 0.05f;
/// <summary>
/// Whether the disease can infect other people.
/// Since this isn't just a virology thing, this
/// primary determines what sort of disease it is.
/// This also affects things like the vaccine machine.
/// You can't print a cancer vaccine
/// </summary>
[DataField("infectious", serverOnly: true)]
public bool Infectious = true;
}
}

View File

@@ -0,0 +1,12 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Disease.Components
{
[NetworkedComponent]
[RegisterComponent]
/// This is added to anyone with at least 1 disease
/// and helps cull event subscriptions and entity queries
/// when they are not relevant.
public sealed class DiseasedComponent : Component
{}
}

Binary file not shown.

View File

@@ -0,0 +1,3 @@
diagnoser_printing.ogg taken from https://freesound.org/people/RobSp1derp1g/sounds/615419/ and edited
vaccinator_running.ogg taken from https://freesound.org/people/RutgerMuller/sounds/365413/ and edited

Binary file not shown.

View File

@@ -0,0 +1,20 @@
diagnoser-cant-use-swab = {CAPITALIZE(THE($machine))} rejects {THE($swab)}.
diagnoser-insert-swab = You insert {THE($swab)} into {THE($machine)}.
diagnoser-disease-report = Disease Report: {CAPITALIZE($disease)}
diagnoser-disease-report-none = Bill of Good Health
diagnoser-disease-report-none-contents = [color=green]No diseases were found in this sample.[/color]
diagnoser-disease-report-name = Disease Name: {CAPITALIZE($disease)}
diagnoser-disease-report-infectious = Infectious: [color=red]Yes[/color]
diagnoser-disease-report-not-infectious = Infectious: [color=green]No[/color]
diagnoser-disease-report-cureresist-none = Medication Resistance: [color=green]None[/color]
diagnoser-disease-report-cureresist-low = Medication Resistance: [color=yellow]Low[/color]
diagnoser-disease-report-cureresist-medium = Medication Resistance: [color=orange]Medium[/color]
diagnoser-disease-report-cureresist-high = Medication Resistance: [color=red]High[/color]
diagnoser-cure-none = The disease has no specific cures.
diagnoser-cure-has = The disease has the following cures:
diagnoser-cure-bedrest = Rest in bed for {$time} seconds.
diagnoser-cure-reagent = Consume at least {$units}u of {$reagent}.
diagnoser-cure-wait = It will go away on its own after {$time} seconds.
diagnoser-cure-temp = Reach a body temperature below {$max}°K or above {$min}°K.
diagnoser-cure-temp-min = Reach a body temperature above {$min}°K.
diagnoser-cure-temp-max = Reach a body temperature below {$max}°K.

View File

@@ -0,0 +1,10 @@
disease-cured = You feel a bit better.
disease-sick-generic = You feel sick.
disease-sneeze = {CAPITALIZE($person)} sneezes.
disease-cough = {CAPITALIZE($person)} coughs.
disease-screech = {CAPITALIZE($person)} screeches.
disease-meow = {CAPITALIZE($person)} meows.
disease-beep= {CAPITALIZE($person)} beeps.
disease-eaten-inside = You feel like you're being eaten from the inside.
disease-steal-compulsion = You want to steal things.
disease-beat-chest-compulsion = {CAPITALIZE(THE($person))} beats {POSS-ADJ($person)} chest.

View File

@@ -0,0 +1,4 @@
disease-scanner-diseased = DISEASED
disease-scanner-not-diseased = No diseases
disease-scanner-gave-other = You gave {THE($target)} {CAPITALIZE($disease)}!
disease-scanner-gave-self = You gave yourself {CAPITALIZE($disease)}! Congratulations!

View File

@@ -0,0 +1,5 @@
swab-already-used = You already used this swab.
swab-swabbed = You swab {THE($target)}'s mouth.
swab-mask-blocked = {CAPITALIZE(THE($target))} needs to take off {THE($mask)}.
swab-used = It looks like it's been used.
swab-unused = It's clean and ready to use.

View File

@@ -0,0 +1,3 @@
vaxx-already-used = You already used this vaccine.
vaxx-used = It's spent.
vaxx-unused = It hasn't been spent.

View File

@@ -0,0 +1 @@
station-event-disease-outbreak-announcement = Ship systems have detected that some crewmates have been infected with a disease.

View File

@@ -58,6 +58,16 @@
- state: box - state: box
- state: latex - state: latex
- type: entity
name: mouth swab box
parent: BoxCardboard
id: BoxMouthSwab
components:
- type: StorageFill
contents:
- id: DiseaseSwab
amount: 30
- type: entity - type: entity
name: body bag box name: body bag box
parent: BoxCardboard parent: BoxCardboard

View File

@@ -19,6 +19,12 @@
amount: 2 amount: 2
- id: Gauze - id: Gauze
amount: 2 amount: 2
- id: BoxLatex
amount: 1
- id: BoxSterile
amount: 1
- id: BoxMouthSwab
amount: 1
- type: entity - type: entity
id: CrateChemistrySupplies id: CrateChemistrySupplies

View File

@@ -50,6 +50,8 @@
components: components:
- type: StorageFill - type: StorageFill
contents: contents:
- id: SyringeSpaceacillin
amount: 1
- id: PillDylovene - id: PillDylovene
amount: 3 amount: 3

View File

@@ -129,7 +129,7 @@
- type: StorageFill - type: StorageFill
contents: contents:
- id: MedkitFilled - id: MedkitFilled
- id: ClothingHandsGlovesLatex - id: ClothingHandsGlovesNitrile
#- name: ClothingEyesHudMedical #Removed until working properly #- name: ClothingEyesHudMedical #Removed until working properly
# prob: 1 # prob: 1
- id: ClothingHeadsetAltMedical - id: ClothingHeadsetAltMedical
@@ -139,6 +139,9 @@
- id: ClothingMaskSterile - id: ClothingMaskSterile
- id: ClothingHeadHelmetHardsuitMedical - id: ClothingHeadHelmetHardsuitMedical
- id: ClothingOuterHardsuitMedical - id: ClothingOuterHardsuitMedical
- id: DiagnoserMachineCircuitboard
- id: VaccinatorMachineCircuitboard
prob: 0.25
- id: Hypospray - id: Hypospray
- id: HandheldCrewMonitor - id: HandheldCrewMonitor
- id: DoorRemoteMedical - id: DoorRemoteMedical

View File

@@ -116,6 +116,8 @@
- ChemMasterMachineCircuitboard - ChemMasterMachineCircuitboard
- ChemDispenserMachineCircuitboard - ChemDispenserMachineCircuitboard
- CrewMonitoringComputerCircuitboard - CrewMonitoringComputerCircuitboard
- VaccinatorMachineCircuitboard
- DiagnoserMachineCircuitboard
- HandheldCrewMonitor - HandheldCrewMonitor
# Security Technology Tree # Security Technology Tree

View File

@@ -284,3 +284,11 @@
category: Misc category: Misc
itemId: ClothingBackpackDuffelSyndicatePyjamaBundle itemId: ClothingBackpackDuffelSyndicatePyjamaBundle
price: 4 price: 4
- type: uplinkListing
id: UplinkGigacancerScanner
category: Misc
itemId: HandheldHealthAnalyzerGigacancer
listingName: Ultragigacancer Health Analyzer
description: Works like a normal health analyzer, other than giving everyone it scans ultragigacancer.
price: 5

View File

@@ -83,6 +83,15 @@
Cold: 1.5 Cold: 1.5
Poison: 0.8 Poison: 0.8
- type: damageModifierSet
id: Scale # Skin tougher, bones weaker, strong stomachs, cold-blooded (kindof)
coefficients:
Blunt: 1.1
Slash: 0.9
Cold: 1.5
Heat: 0.9
Poison: 0.9
# 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

@@ -0,0 +1,138 @@
- type: disease
id: SpaceCold
name: space cold
cureResist: 0
effects:
- !type:DiseaseAdjustReagent
probability: 0.2
reagent: Histamine
amount: 0.5
- !type:DiseasePopUp
probability: 0.025
- !type:DiseaseSnough
probability: 0.025
cures:
- !type:DiseaseBedrestCure
maxLength: 20
- !type:DiseaseJustWaitCure
maxLength: 400
- !type:DiseaseReagentCure
reagent: Ultravasculine
### - !type:DiseaseReagentCure ### In Loving Memory, Lean
### reagent: Lean ### 2022/03/12 - 2022/03/13
- type: disease
id: VentCough
name: vent cough
effects:
- !type:DiseasePopUp
probability: 0.025
message: burning-insides
- !type:DiseaseSnough
probability: 0.025
snoughMessage: disease-cough
- !type:DiseaseHealthChange
probability: 0.015
damage:
groups:
Caustic: 1
cures:
- !type:DiseaseBedrestCure
maxLength: 30
- !type:DiseaseJustWaitCure
maxLength: 600
- !type:DiseaseReagentCure
reagent: SpaceCleaner
- type: disease
id: VanAusdallsRobovirus
name: Van Ausdall's Robovirus
cureResist: 0.1
effects:
- !type:DiseaseAdjustReagent
probability: 0.025
reagent: Licoxide
amount: 0.5
- !type:DiseaseSnough
probability: 0.02
snoughMessage: disease-beep
cures:
- !type:DiseaseJustWaitCure
maxLength: 900
- !type:DiseaseReagentCure
reagent: BeepskySmash
- type: disease
id: AMIV
name: AMIV
cureResist: 0.10
effects:
- !type:DiseasePopUp
probability: 0.015
type: Pvs
message: disease-beat-chest-compulsion
- !type:DiseasePopUp
probability: 0.03
message: disease-steal-compulsion
- !type:DiseaseSnough
probability: 0.02
snoughMessage: disease-screech
- !type:DiseaseGenericStatusEffect
probability: 0.3
key: Stutter
component: MonkeyAccent
- !type:DiseaseHealthChange
probability: 0.53
damage:
types:
Asphyxiation: 1
cures:
- !type:DiseaseJustWaitCure
maxLength: 1600
- !type:DiseaseReagentCure
reagent: BananaHonk
- type: disease
id: BleedersBite
name: Bleeder's Bite
effects:
- !type:DiseaseAdjustReagent
reagent: TranexamicAcid
amount: -2.5
- !type:DiseaseHealthChange
probability: 0.015
damage:
types:
Piercing: 20
- !type:DiseasePopUp
probability: 0.05
message: disease-eaten-inside
cures:
- !type:DiseaseJustWaitCure
maxLength: 900
- !type:DiseaseBodyTemperatureCure
min: 360
- !type:DiseaseReagentCure
reagent: DemonsBlood
- type: disease
id: OwOnavirus
name: OwOnavirus
cureResist: 0.25
effects:
- !type:DiseaseGenericStatusEffect
key: Stutter
component: OwOAccent
- !type:DiseaseAdjustReagent ## 20 / 0.013 / 60 is around 25 minutes before overdose (0.5u metabolize each tick)
probability: 0.513
reagent: Ephedrine
amount: 1
- !type:DiseaseSnough
probability: 0.02
snoughMessage: disease-meow
cures:
- !type:DiseaseBodyTemperatureCure
min: 420 ## Reachable with a flamer
- !type:DiseaseReagentCure
reagent: Theobromine
amount: 10

View File

@@ -0,0 +1,18 @@
- type: disease
id: Ultragigacancer
name: ultragigacancer
infectious: false
cureResist: 0.15
effects:
- !type:DiseaseHealthChange
probability: 0.5
damage:
types:
Cellular: 1
- !type:DiseasePopUp
probability: 0.03
cures:
- !type:DiseaseReagentCure
reagent: Phalanximine
min: 15
### Once radiation is refactored I want it to have a small chance of giving you regular cancer

View File

@@ -7,3 +7,5 @@
state: icon state: icon
- type: Clothing - type: Clothing
Slots: [gloves] Slots: [gloves]
- type: DiseaseProtection
protection: 0.05

View File

@@ -71,18 +71,33 @@
sprite: Clothing/Hands/Gloves/ihscombat.rsi sprite: Clothing/Hands/Gloves/ihscombat.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Hands/Gloves/ihscombat.rsi sprite: Clothing/Hands/Gloves/ihscombat.rsi
#### Medical
- type: entity - type: entity
parent: ClothingHandsBase parent: ClothingHandsBase
id: ClothingHandsGlovesLatex id: ClothingHandsGlovesLatex
name: latex gloves name: latex gloves
description: Thin sterile latex gloves. description: Thin sterile latex gloves. Basic PPE for any doctor.
components: components:
- type: Sprite - type: Sprite
sprite: Clothing/Hands/Gloves/latex.rsi sprite: Clothing/Hands/Gloves/latex.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Hands/Gloves/latex.rsi sprite: Clothing/Hands/Gloves/latex.rsi
- type: DiseaseProtection
protection: 0.1
- type: entity
parent: ClothingHandsBase
id: ClothingHandsGlovesNitrile
name: nitrile gloves
description: High-quality nitrile gloves. Expensive medical PPE.
components:
- type: Sprite
sprite: Clothing/Hands/Gloves/Color/blue.rsi
- type: Clothing
sprite: Clothing/Hands/Gloves/Color/blue.rsi
- type: DiseaseProtection
protection: 0.15
####
- type: entity - type: entity
parent: ClothingHandsBase parent: ClothingHandsBase
id: ClothingHandsGlovesLeather id: ClothingHandsGlovesLeather

View File

@@ -48,6 +48,8 @@
- type: Tag - type: Tag
tags: tags:
- HidesHair - HidesHair
- type: DiseaseProtection
protection: 0.05
- type: entity - type: entity
abstract: true abstract: true

View File

@@ -83,6 +83,8 @@
- type: PressureProtection - type: PressureProtection
highPressureMultiplier: 0.80 highPressureMultiplier: 0.80
lowPressureMultiplier: 55 lowPressureMultiplier: 55
- type: DiseaseProtection
protection: 0.15
- type: entity - type: entity
parent: ClothingHeadHardsuitWithLightBase parent: ClothingHeadHardsuitWithLightBase

View File

@@ -9,9 +9,14 @@
sprite: Clothing/Head/Hoods/Bio/general.rsi sprite: Clothing/Head/Hoods/Bio/general.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Head/Hoods/Bio/general.rsi sprite: Clothing/Head/Hoods/Bio/general.rsi
- type: DiseaseProtection
protection: 0.15
- type: Tag
tags:
- HidesHair
- type: entity - type: entity
parent: ClothingHeadBase parent: ClothingHeadHatHoodBioGeneral
id: ClothingHeadHatHoodBioCmo id: ClothingHeadHatHoodBioCmo
name: bio hood name: bio hood
suffix: CMO suffix: CMO
@@ -21,9 +26,11 @@
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: DiseaseProtection
protection: 0.25
- type: entity - type: entity
parent: ClothingHeadBase parent: ClothingHeadHatHoodBioGeneral
id: ClothingHeadHatHoodBioJanitor id: ClothingHeadHatHoodBioJanitor
name: bio hood name: bio hood
suffix: Janitor suffix: Janitor
@@ -34,8 +41,9 @@
- type: Clothing - type: Clothing
sprite: Clothing/Head/Hoods/Bio/janitor.rsi sprite: Clothing/Head/Hoods/Bio/janitor.rsi
- type: entity - type: entity
parent: ClothingHeadBase parent: ClothingHeadHatHoodBioGeneral
id: ClothingHeadHatHoodBioScientist id: ClothingHeadHatHoodBioScientist
name: bio hood name: bio hood
suffix: Science suffix: Science
@@ -47,7 +55,7 @@
sprite: Clothing/Head/Hoods/Bio/scientist.rsi sprite: Clothing/Head/Hoods/Bio/scientist.rsi
- type: entity - type: entity
parent: ClothingHeadBase parent: ClothingHeadHatHoodBioGeneral
id: ClothingHeadHatHoodBioSecurity id: ClothingHeadHatHoodBioSecurity
name: bio hood name: bio hood
suffix: Security suffix: Security
@@ -59,7 +67,7 @@
sprite: Clothing/Head/Hoods/Bio/security.rsi sprite: Clothing/Head/Hoods/Bio/security.rsi
- type: entity - type: entity
parent: ClothingHeadBase parent: ClothingHeadHatHoodBioGeneral
id: ClothingHeadHatHoodBioVirology id: ClothingHeadHatHoodBioVirology
name: bio hood name: bio hood
suffix: Virology suffix: Virology
@@ -69,6 +77,8 @@
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: DiseaseProtection
protection: 0.25
- type: entity - type: entity
parent: ClothingHeadBase parent: ClothingHeadBase

View File

@@ -10,6 +10,8 @@
sprite: Clothing/Mask/gas.rsi sprite: Clothing/Mask/gas.rsi
- type: BreathMask - type: BreathMask
- type: IngestionBlocker - type: IngestionBlocker
- type: DiseaseProtection
protection: 0.05
- type: entity - type: entity
parent: ClothingMaskBase parent: ClothingMaskBase
@@ -23,6 +25,8 @@
sprite: Clothing/Mask/breath.rsi sprite: Clothing/Mask/breath.rsi
- type: BreathMask - type: BreathMask
- type: IngestionBlocker - type: IngestionBlocker
- type: DiseaseProtection
protection: 0.05
- type: entity - type: entity
parent: ClothingMaskBase parent: ClothingMaskBase
@@ -72,6 +76,8 @@
sprite: Clothing/Mask/sterile.rsi sprite: Clothing/Mask/sterile.rsi
- type: BreathMask - type: BreathMask
- type: IngestionBlocker - type: IngestionBlocker
- type: DiseaseProtection
protection: 0.1
- type: entity - type: entity
parent: ClothingMaskBase parent: ClothingMaskBase

View File

@@ -30,3 +30,4 @@
Piercing: 0.95 Piercing: 0.95
Heat: 0.90 Heat: 0.90
Radiation: 0.25 Radiation: 0.25
- type: DiseaseProtection

View File

@@ -9,6 +9,8 @@
sprite: Clothing/OuterClothing/Bio/general.rsi sprite: Clothing/OuterClothing/Bio/general.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Bio/general.rsi sprite: Clothing/OuterClothing/Bio/general.rsi
- type: DiseaseProtection
protection: 0.2
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -21,6 +23,8 @@
sprite: Clothing/OuterClothing/Bio/cmo.rsi sprite: Clothing/OuterClothing/Bio/cmo.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Bio/cmo.rsi sprite: Clothing/OuterClothing/Bio/cmo.rsi
- type: DiseaseProtection
protection: 0.3
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -33,6 +37,8 @@
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: DiseaseProtection
protection: 0.2
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -45,6 +51,8 @@
sprite: Clothing/OuterClothing/Bio/scientist.rsi sprite: Clothing/OuterClothing/Bio/scientist.rsi
- type: Clothing - type: Clothing
sprite: Clothing/OuterClothing/Bio/scientist.rsi sprite: Clothing/OuterClothing/Bio/scientist.rsi
- type: DiseaseProtection
protection: 0.2
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -57,6 +65,8 @@
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: DiseaseProtection
protection: 0.2
- type: entity - type: entity
parent: ClothingOuterBase parent: ClothingOuterBase
@@ -69,3 +79,5 @@
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: DiseaseProtection
protection: 0.3

View File

@@ -180,6 +180,8 @@
- type: PressureProtection - type: PressureProtection
highPressureMultiplier: 0.75 highPressureMultiplier: 0.75
lowPressureMultiplier: 100 lowPressureMultiplier: 100
- type: DiseaseProtection
protection: 0.2
- type: entity - type: entity
parent: ClothingOuterHardsuitBase parent: ClothingOuterHardsuitBase

View File

@@ -89,6 +89,8 @@
sprite: Clothing/Uniforms/Jumpskirt/cmo.rsi sprite: Clothing/Uniforms/Jumpskirt/cmo.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Uniforms/Jumpskirt/cmo.rsi sprite: Clothing/Uniforms/Jumpskirt/cmo.rsi
- type: DiseaseProtection
protection: 0.15
- type: entity - type: entity
parent: ClothingUniformSkirtBase parent: ClothingUniformSkirtBase
@@ -199,6 +201,8 @@
sprite: Clothing/Uniforms/Jumpskirt/medical.rsi sprite: Clothing/Uniforms/Jumpskirt/medical.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Uniforms/Jumpskirt/medical.rsi sprite: Clothing/Uniforms/Jumpskirt/medical.rsi
- type: DiseaseProtection
protection: 0.1
- type: entity - type: entity
parent: ClothingUniformSkirtBase parent: ClothingUniformSkirtBase
@@ -221,6 +225,8 @@
sprite: Clothing/Uniforms/Jumpskirt/paramedic.rsi sprite: Clothing/Uniforms/Jumpskirt/paramedic.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Uniforms/Jumpskirt/paramedic.rsi sprite: Clothing/Uniforms/Jumpskirt/paramedic.rsi
- type: DiseaseProtection
protection: 0.1
- type: entity - type: entity
parent: ClothingUniformSkirtBase parent: ClothingUniformSkirtBase

View File

@@ -161,6 +161,8 @@
sprite: Clothing/Uniforms/Jumpsuit/cmo.rsi sprite: Clothing/Uniforms/Jumpsuit/cmo.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Uniforms/Jumpsuit/cmo.rsi sprite: Clothing/Uniforms/Jumpsuit/cmo.rsi
- type: DiseaseProtection
protection: 0.15
- type: entity - type: entity
parent: ClothingUniformBase parent: ClothingUniformBase
@@ -293,6 +295,8 @@
sprite: Clothing/Uniforms/Jumpsuit/medical.rsi sprite: Clothing/Uniforms/Jumpsuit/medical.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Uniforms/Jumpsuit/medical.rsi sprite: Clothing/Uniforms/Jumpsuit/medical.rsi
- type: DiseaseProtection
protection: 0.1
- type: entity - type: entity
parent: ClothingUniformBase parent: ClothingUniformBase
@@ -315,6 +319,8 @@
sprite: Clothing/Uniforms/Jumpsuit/paramedic.rsi sprite: Clothing/Uniforms/Jumpsuit/paramedic.rsi
- type: Clothing - type: Clothing
sprite: Clothing/Uniforms/Jumpsuit/paramedic.rsi sprite: Clothing/Uniforms/Jumpsuit/paramedic.rsi
- type: DiseaseProtection
protection: 0.1
- type: entity - type: entity
parent: ClothingUniformBase parent: ClothingUniformBase

View File

@@ -18,6 +18,7 @@
- ColdArtifact - ColdArtifact
- RadiateArtifact - RadiateArtifact
- GasArtifact - GasArtifact
- DiseaseArtifact
chance: 1 chance: 1
- type: entity - type: entity

View File

@@ -686,6 +686,7 @@
autoPopulate: false autoPopulate: false
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 50 bloodMaxVolume: 50
- type: DiseaseCarrier #The other class lab animal and disease vector
- type: entity - type: entity
@@ -708,6 +709,7 @@
dead: splat-1 dead: splat-1
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 50 bloodMaxVolume: 50
- type: DiseaseCarrier #Why doesn't this save if it's only on the parent wtf
- type: entity - type: entity
@@ -730,6 +732,7 @@
dead: splat-2 dead: splat-2
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 50 bloodMaxVolume: 50
- type: DiseaseCarrier
- type: entity - type: entity
@@ -775,6 +778,9 @@
interactFailureString: petting-failure-generic interactFailureString: petting-failure-generic
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 50 bloodMaxVolume: 50
- type: Damageable
damageContainer: Biological
damageModifierSet: Scale
- type: entity - type: entity
name: frog name: frog
@@ -947,6 +953,9 @@
interactFailureString: petting-failure-generic interactFailureString: petting-failure-generic
- type: Bloodstream - type: Bloodstream
bloodMaxVolume: 50 bloodMaxVolume: 50
- type: Damageable
damageContainer: Biological
damageModifierSet: Scale
# Code unique spider prototypes or combine them all into one spider and get a # Code unique spider prototypes or combine them all into one spider and get a
# random sprite state when you spawn it. # random sprite state when you spawn it.

View File

@@ -71,6 +71,7 @@
- SlowedDown - SlowedDown
- Stutter - Stutter
- Electrocution - Electrocution
- type: DiseaseCarrier
# Other # Other
- type: Inventory - type: Inventory
- type: Clickable - type: Clickable
@@ -294,6 +295,7 @@
proper: true proper: true
- type: StandingState - type: StandingState
- type: entity - type: entity
save: false save: false
name: Urist McHands name: Urist McHands

View File

@@ -110,6 +110,25 @@
template: HumanoidTemplate template: HumanoidTemplate
preset: HumanPreset preset: HumanPreset
- type: LizardAccent - type: LizardAccent
- type: DiseaseCarrier
diseaseResist: 0.1
- type: Damageable
damageContainer: Biological
damageModifierSet: Scale
- type: Temperature
heatDamageThreshold: 400
coldDamageThreshold: 285
currentTemperature: 310.15
specificHeat: 46
coldDamage:
types:
Cold : 1.1 #per second, scales with temperature & other constants
heatDamage:
types:
Heat : 0.9 #per second, scales with temperature & other constants
- type: MovementSpeedModifier
baseWalkSpeed : 2.7
baseSprintSpeed : 4.5
- type: entity - type: entity
save: false save: false

View File

@@ -32,7 +32,7 @@
- type: entity - type: entity
id: CircuitImprinterMachineCircuitboard id: CircuitImprinterMachineCircuitboard
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
name: Circuit Imprinter (Machine Board) name: circuit imprinter machine board
components: components:
- type: MachineBoard - type: MachineBoard
prototype: CircuitImprinter prototype: CircuitImprinter
@@ -48,7 +48,7 @@
- type: entity - type: entity
id: UniformPrinterMachineCircuitboard id: UniformPrinterMachineCircuitboard
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
name: Uniform Printer (Machine Board) name: uniform printer machine board
components: components:
- type: MachineBoard - type: MachineBoard
prototype: UniformPrinter prototype: UniformPrinter
@@ -57,10 +57,41 @@
Manipulator: 1 Manipulator: 1
Laser: 1 Laser: 1
- type: entity
id: VaccinatorMachineCircuitboard
parent: BaseMachineCircuitboard
name: vaccinator machine board
components:
- type: MachineBoard
prototype: Vaccinator
requirements:
MatterBin: 1
Manipulator: 1
materialRequirements:
Cable: 5
tagRequirements:
GlassBeaker:
Amount: 1
DefaultPrototype: Beaker
ExamineName: Glass Beaker
- type: entity
id: DiagnoserMachineCircuitboard
parent: BaseMachineCircuitboard
name: diagnoser machine board
components:
- type: MachineBoard
prototype: DiseaseDiagnoser
requirements:
Manipulator: 1
Laser: 2
materialRequirements:
Cable: 5
- type: entity - type: entity
id: ThermomachineFreezerMachineCircuitBoard id: ThermomachineFreezerMachineCircuitBoard
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
name: Freezer Thermomachine (Machine Board) name: freezer thermomachine machine board
description: Looks like you could use a screwdriver to change the board type. description: Looks like you could use a screwdriver to change the board type.
components: components:
- type: MachineBoard - type: MachineBoard
@@ -77,7 +108,7 @@
- type: entity - type: entity
id: ThermomachineHeaterMachineCircuitBoard id: ThermomachineHeaterMachineCircuitBoard
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
name: Heater Thermomachine (Machine Board) name: heather thermomachine machine board
description: Looks like you could use a screwdriver to change the board type. description: Looks like you could use a screwdriver to change the board type.
components: components:
- type: MachineBoard - type: MachineBoard
@@ -208,7 +239,7 @@
- type: entity - type: entity
parent: BaseMachineCircuitboard parent: BaseMachineCircuitboard
id: DawInstrumentMachineCircuitboard id: DawInstrumentMachineCircuitboard
name: Digital Audio Workstation (Machine Board) name: digital audio workstation machine board
components: components:
- type: MachineBoard - type: MachineBoard
prototype: DawInstrument prototype: DawInstrument

View File

@@ -0,0 +1,30 @@
- type: entity
parent: BaseItem
id: DiseaseSwab
name: mouth swab
description: Used to take saliva samples to test for diseases.
components:
- type: Item
size: 1
- type: Sprite
netsync: false
sprite: Objects/Specific/Medical/mouth_swab.rsi
state: icon
- type: Tag
tags:
- Recyclable
- type: DiseaseSwab
- type: entity
parent: BaseItem
id: Vaccine
name: Vaccine
description: There's no way you don't already have an opinion on these.
components:
- type: Item
size: 3
- type: Sprite
sprite: Objects/Specific/Medical/medipen.rsi
netsync: false
state: salpen
- type: DiseaseVaccine

View File

@@ -237,3 +237,16 @@
reagents: reagents:
- ReagentId: TranexamicAcid - ReagentId: TranexamicAcid
Quantity: 15 Quantity: 15
- type: entity
name: spaceacillin syringe
parent: Syringe
id: SyringeSpaceacillin
components:
- type: SolutionContainerManager
solutions:
injector:
maxVol: 15
reagents:
- ReagentId: Spaceacillin
Quantity: 15

View File

@@ -15,3 +15,23 @@
- key: enum.HealthAnalyzerUiKey.Key - key: enum.HealthAnalyzerUiKey.Key
type: HealthAnalyzerBoundUserInterface type: HealthAnalyzerBoundUserInterface
- type: HealthAnalyzer - type: HealthAnalyzer
- type: entity
parent: HandheldHealthAnalyzer
id: HandheldHealthAnalyzerGigacancer
suffix: gigacancer
components:
- type: HealthAnalyzer
fake: true
disease: Ultragigacancer
## I know admins will want this
- type: entity
parent: HandheldHealthAnalyzer
id: HandheldHealthAnalyzerOwOnavirus
name: OwOnavirus analyzer
suffix: admin abuse
components:
- type: HealthAnalyzer
fake: true
disease: OwOnavirus

View File

@@ -167,3 +167,10 @@
suffix: Gas suffix: Gas
components: components:
- type: GasArtifact - type: GasArtifact
- type: entity
parent: BaseXenoArtifact
id: DiseaseArtifact
suffix: Disease
components:
- type: DiseaseArtifact

View File

@@ -0,0 +1,24 @@
- type: entity
id: DiseaseDiagnoser
parent: BaseMachinePowered
name: Disease Diagnoser Delta Extreme
description: A machine that analyzes disease samples.
placement:
mode: SnapgridCenter
components:
- type: Sprite
sprite: Structures/Machines/diagnoser.rsi
layers:
- state: icon
map: ["enum.DiseaseMachineVisualLayers.IsRunning"]
- state: unlit
shader: unshaded
map: ["enum.DiseaseMachineVisualLayers.IsOn"]
netsync: false
- type: DiseaseDiagnoser
- type: DiseaseMachine
machineOutput: Paper
- type: Appearance
- type: DiseaseMachineVisuals
idleState: icon
runningState: running

View File

@@ -229,6 +229,9 @@
- ThermomachineFreezerMachineCircuitBoard - ThermomachineFreezerMachineCircuitBoard
- CloningPodMachineCircuitboard - CloningPodMachineCircuitboard
- MedicalScannerMachineCircuitboard - MedicalScannerMachineCircuitboard
- CrewMonitoringComputerCircuitboard
- VaccinatorMachineCircuitboard
- DiagnoserMachineCircuitboard
- ChemMasterMachineCircuitboard - ChemMasterMachineCircuitboard
- ChemDispenserMachineCircuitboard - ChemDispenserMachineCircuitboard
- HydroponicsTrayMachineCircuitboard - HydroponicsTrayMachineCircuitboard
@@ -237,7 +240,6 @@
- ProtolatheMachineCircuitboard - ProtolatheMachineCircuitboard
- ReagentGrinderMachineCircuitboard - ReagentGrinderMachineCircuitboard
- UniformPrinterMachineCircuitboard - UniformPrinterMachineCircuitboard
- CrewMonitoringComputerCircuitboard
- ShuttleConsoleCircuitboard - ShuttleConsoleCircuitboard
- CircuitImprinterMachineCircuitboard - CircuitImprinterMachineCircuitboard
- DawInstrumentMachineCircuitboard - DawInstrumentMachineCircuitboard

View File

@@ -0,0 +1,26 @@
- type: entity
id: Vaccinator
parent: BaseMachinePowered
name: Vaccinator
description: A machine that creates vaccines.
placement:
mode: SnapgridCenter
components:
- type: Sprite
sprite: Structures/Machines/vaccinator.rsi
layers:
- state: icon
map: ["enum.DiseaseMachineVisualLayers.IsRunning"]
- state: unlit
shader: unshaded
map: ["enum.DiseaseMachineVisualLayers.IsOn"]
netsync: false
- type: DiseaseVaccineCreator
- type: DiseaseMachine
machineOutput: Vaccine
- type: Appearance
- type: DiseaseMachineVisuals
idleState: icon
runningState: running
- type: Machine
board: VaccinatorMachineCircuitboard

View File

@@ -1,3 +1,11 @@
- type: reagent
id: Cryptobiolin
name: cryptobiolin
group: Medicine
desc: Causes confusion and dizziness. This is essential to make Spaceacillin.
physicalDesc: fizzy
color: "#081a80"
- type: reagent - type: reagent
id: Dylovene id: Dylovene
name: dylovene name: dylovene
@@ -308,8 +316,7 @@
- !type:HealthChange - !type:HealthChange
damage: damage:
types: types:
# close enough to what it says Cellular: -1
Poison: -1
Radiation: 1 Radiation: 1
- type: reagent - type: reagent
@@ -340,6 +347,18 @@
groups: groups:
Caustic: -5 Caustic: -5
- type: reagent
id: Spaceacillin
name: spaceacillin
group: Medicine
desc: A theta-lactam antibiotic. A common and very useful medicine, effective against many diseases likely to be encountered in space. Slows progression of diseases.
physicalDesc: opaque
color: "#9942f5"
metabolisms:
Medicine:
effects:
- !type:ChemCureDisease
- type: reagent - type: reagent
id: Stellibinin id: Stellibinin
name: stellibinin name: stellibinin

View File

@@ -269,3 +269,20 @@
damage: damage:
types: types:
Poison: 6 Poison: 6
- type: reagent
id: VentCrud
name: vent crud
desc: A jet black substance found in poorly maintained ventilation systems.
physicalDesc: sticky
color: "#000000"
metabolisms:
Poison:
effects:
- !type:HealthChange
damage:
types:
Poison: 2
- !type:ChemCauseDisease ##Since this mostly just comes from the event you won't ingest that much
causeChance: 0.6
disease: VentCough

View File

@@ -163,6 +163,27 @@
Steel: 100 Steel: 100
Glass: 900 Glass: 900
- type: latheRecipe
id: VaccinatorMachineCircuitboard
icon: Objects/Misc/module.rsi/id_mod.png
result: VaccinatorMachineCircuitboard
completetime: 100
materials:
Steel: 100
Glass: 900
Gold: 100
- type: latheRecipe
id: DiagnoserMachineCircuitboard
icon: Objects/Misc/module.rsi/id_mod.png
result: DiagnoserMachineCircuitboard
completetime: 100
materials:
Steel: 100
Glass: 900
Gold: 100
- type: latheRecipe - type: latheRecipe
id: ReagentGrinderMachineCircuitboard id: ReagentGrinderMachineCircuitboard
icon: Objects/Misc/module.rsi/id_mod.png icon: Objects/Misc/module.rsi/id_mod.png

View File

@@ -10,6 +10,18 @@
products: products:
Dylovene: 3 Dylovene: 3
- type: reaction
id: Cryptobiolin
reactants:
Potassium:
amount: 1
Oxygen:
amount: 1
Glucose:
amount: 1
products:
Cryptobiolin: 3
- type: reaction - type: reaction
id: Arithrazine id: Arithrazine
reactants: reactants:
@@ -288,3 +300,13 @@
amount: 1 amount: 1
products: products:
Siderlac: 2 Siderlac: 2
- type: reaction
id: Spaceacillin
reactants:
Cryptobiolin:
amount: 1
Inaprovaline:
amount: 1
products:
Spaceacillin: 2

Binary file not shown.

After

Width:  |  Height:  |  Size: 227 B

View File

@@ -0,0 +1,15 @@
{
"version": 1,
"size": {
"x": 32,
"y": 32
},
"license": "CC-BY",
"copyright": "Created by Willhudson#4576 (Discord user id: 935437363180613672) in the SS14 Discord",
"states": [
{
"name": "icon",
"directions": 1
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 B

View File

@@ -0,0 +1,52 @@
{
"version": 1,
"license": "CC-BY",
"copyright": "Created by Willhudson#4576 (Discord user id: 935437363180613672) in the SS14 Discord",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "unlit"
},
{
"name": "running",
"delays": [
[
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785,
0.1785
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 642 B

View File

@@ -0,0 +1,33 @@
{
"version": 1,
"license": "CC-BY",
"copyright": "Created by Willhudson#4576 (Discord user id: 935437363180613672) in the SS14 Discord",
"size": {
"x": 32,
"y": 32
},
"states": [
{
"name": "icon"
},
{
"name": "unlit"
},
{
"name": "running",
"delays": [
[
0.5555,
0.5555,
0.5555,
0.5555,
0.5555,
0.5555,
0.5555,
0.5555,
0.5555
]
]
}
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B