diff --git a/Content.Client/_Sunrise/Disease/UI/DiseaseInfoBui.cs b/Content.Client/_Sunrise/Disease/UI/DiseaseInfoBui.cs new file mode 100644 index 00000000000..57111da707f --- /dev/null +++ b/Content.Client/_Sunrise/Disease/UI/DiseaseInfoBui.cs @@ -0,0 +1,49 @@ +using Content.Shared._Sunrise.Disease; +using JetBrains.Annotations; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface; + +namespace Content.Client._Sunrise.Disease.UI; + +[UsedImplicitly] +public sealed class DiseaseInfoBui : BoundUserInterface +{ + [ViewVariables] + private DiseaseInfoWindow? _window; + + public DiseaseInfoBui(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + } + + protected override void Open() + { + base.Open(); + _window = new DiseaseInfoWindow(); + _window.OnClose += Close; + _window.OpenCentered(); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not DiseaseInfoState diseaseState) + return; + + _window?.UpdateState( + diseaseState.BaseInfectChance, + diseaseState.CoughSneezeInfectChance, + diseaseState.Lethal, + diseaseState.Shield, + diseaseState.CurrentInfected, + diseaseState.TotalInfected + ); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (disposing) + _window?.Dispose(); + } +} diff --git a/Content.Client/_Sunrise/Disease/UI/DiseaseInfoWindow.xaml b/Content.Client/_Sunrise/Disease/UI/DiseaseInfoWindow.xaml new file mode 100644 index 00000000000..ee70a34ebbf --- /dev/null +++ b/Content.Client/_Sunrise/Disease/UI/DiseaseInfoWindow.xaml @@ -0,0 +1,37 @@ + + + + diff --git a/Content.Client/_Sunrise/Disease/UI/DiseaseInfoWindow.xaml.cs b/Content.Client/_Sunrise/Disease/UI/DiseaseInfoWindow.xaml.cs new file mode 100644 index 00000000000..21ec833a69e --- /dev/null +++ b/Content.Client/_Sunrise/Disease/UI/DiseaseInfoWindow.xaml.cs @@ -0,0 +1,25 @@ +using Content.Client.UserInterface.Controls; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client._Sunrise.Disease.UI; + +[GenerateTypedNameReferences] +public sealed partial class DiseaseInfoWindow : DefaultWindow +{ + public DiseaseInfoWindow() + { + RobustXamlLoader.Load(this); + } + + public void UpdateState(float baseChance, float infectChance, int lethal, int shield, int currentInfected, int totalInfected) + { + BaseChanceLabel.Text = (baseChance * 100).ToString("F0") + "%"; + InfectChanceLabel.Text = (infectChance * 100).ToString("F0") + "%"; + LethalLabel.Text = lethal.ToString(); + ShieldLabel.Text = shield.ToString(); + CurrentInfectedLabel.Text = currentInfected.ToString(); + TotalInfectedLabel.Text = totalInfected.ToString(); + } +} diff --git a/Content.Server/_Sunrise/Disease/DiseaseRoleSystem.cs b/Content.Server/_Sunrise/Disease/DiseaseRoleSystem.cs index d28de0aa333..13f0e0746fc 100644 --- a/Content.Server/_Sunrise/Disease/DiseaseRoleSystem.cs +++ b/Content.Server/_Sunrise/Disease/DiseaseRoleSystem.cs @@ -19,6 +19,8 @@ namespace Content.Server._Sunrise.Disease; +using Robust.Server.GameObjects; + public sealed class DiseaseRoleSystem : SharedDiseaseRoleSystem { [Dependency] private readonly IRobustRandom _random = default!; @@ -29,6 +31,7 @@ public sealed class DiseaseRoleSystem : SharedDiseaseRoleSystem [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly AudioSystem _audio = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; private static readonly List _bloodReagents = new() { @@ -66,16 +69,12 @@ private void OnInfects(InfectEvent args) { if (TryComp(args.Performer, out var component)) { - if (TryRemoveMoney(args.Performer, component.InfectCost)) - OnInfect(args, 1.0f); - else - { - _popup.PopupEntity(Loc.GetString("disease-not-enough-evolution-points"), args.Performer, PopupType.Medium); - return; - } + OnInfect(args, 1.0f); + _popup.PopupEntity(Loc.GetString("disease-infect-success"), args.Performer, PopupType.Medium); // Play Initial Infected antag audio (only for the disease player) _audio.PlayGlobal("/Audio/Ambience/Antag/zombie_start.ogg", args.Performer); + UpdateUi(args.Performer, component); } } @@ -105,25 +104,21 @@ private void OnShop(EntityUid uid, DiseaseRoleComponent component, DiseaseShopAc private void OnDiseaseInfo(EntityUid uid, DiseaseRoleComponent component, DiseaseInfoEvent args) { - // Create a simple formatted display using StringBuilder for better performance - var infoText = new StringBuilder(); - infoText.AppendLine(Loc.GetString("disease-info-header")); - infoText.AppendLine(); - - // Core Statistics Section - infoText.AppendLine(Loc.GetString("disease-info-core-statistics") + ":"); - infoText.AppendLine("├─ " + Loc.GetString("disease-info-base-chance") + ": " + (component.BaseInfectChance * 100).ToString("F0") + "%"); - infoText.AppendLine("├─ " + Loc.GetString("disease-info-cough-sneeze-chance") + ": " + (component.CoughSneezeInfectChance * 100).ToString("F0") + "%"); - infoText.AppendLine("├─ " + Loc.GetString("disease-info-lethal") + ": " + component.Lethal); - infoText.AppendLine("└─ " + Loc.GetString("disease-info-shield") + ": " + component.Shield); - infoText.AppendLine(); - - // Infection Statistics Section - infoText.AppendLine(Loc.GetString("disease-info-infection-statistics") + ":"); - infoText.AppendLine("├─ " + Loc.GetString("disease-info-infected-count") + ": " + component.Infected.Count); - infoText.Append("└─ " + Loc.GetString("disease-info-total-infected") + ": " + component.SickOfAllTime); - - _popup.PopupEntity(infoText.ToString(), uid, uid, PopupType.Large); + _ui.TryToggleUi(uid, DiseaseInfoUiKey.Key, uid); + UpdateUi(uid, component); + } + + private void UpdateUi(EntityUid uid, DiseaseRoleComponent component) + { + var state = new DiseaseInfoState( + component.BaseInfectChance, + component.CoughSneezeInfectChance, + component.Lethal, + component.Shield, + component.Infected.Count, + component.SickOfAllTime + ); + _ui.SetUiState(uid, DiseaseInfoUiKey.Key, state); } @@ -221,10 +216,84 @@ private void OnStorePurchase(ref StoreBuyFinishedEvent args) _popup.PopupEntity(Loc.GetString("disease-upgrade-max-reached"), storeOwner, PopupType.Medium); } break; + default: + if (!diseaseComp.Symptoms.ContainsKey(args.PurchasedItem.ID)) + { + // Check if it's a symptom listing from the ID + var symptom = args.PurchasedItem.ID; + int minLevel = 0; + int maxLevel = 5; + + // Ideally we should get this from the listing/action metadata, + // but since the previous system relied on hardcoded event args from actions, + // we might need to assume or look it up. + // For now, let's replicate the levels from the removed actions: + + switch (symptom) + { + case "Cough": minLevel = 2; break; + case "Sneeze": minLevel = 3; break; + case "Vomit": minLevel = 3; break; + case "Narcolepsy": minLevel = 3; break; + case "Crying": minLevel = 0; break; + case "Muted": minLevel = 4; break; + case "Slowness": minLevel = 2; break; + case "Bleed": minLevel = 3; break; + case "Blindness": minLevel = 4; break; + case "Insult": minLevel = 2; break; + // Zombie handled separately via special event if needed, or if it's just a symptom? + // The original action raised DiseaseZombieEvent for "Zombie". + // Wait, "Zombie" was a separate category in catalog? + // Checking catalog: "Zombie" listing productAction was ActionDiseaseZombie. + // So Zombie is NOT handled here in default block if we want to keep specific behavior. + } + + if (symptom == "Zombie") + { + // Zombie Logic - previously handled by DiseaseZombieEvent which was raised by InstantAction + // Now we trigger it directly on purchase + // We need to manually construct the event or call the logic. + // But wait, the original logic was: Purchase -> Get Item/Action -> Use Action -> Trigger Event. + // If we enable "instant buy", we trigger logic now. + var zombieArgs = new DiseaseZombieEvent(); // Empty event, needed for handler signature? + // Actually the handler uses the event args to get the action to remove it. + // We can just extract the logic. + + var infected = diseaseComp.Infected.ToArray(); + var convertedCount = 0; + + for (int i = 0; i < infected.Length; i++) + { + var target = infected[i]; + if (target.IsValid() && !Deleted(target)) + { + RemComp(target); + diseaseComp.Infected.Remove(target); + EnsureComp(target); + EnsureComp(target); + convertedCount++; + } + } + + if (convertedCount > 0) + { + _popup.PopupEntity(Loc.GetString("disease-zombie-success", ("count", convertedCount)), storeOwner, PopupType.Medium); + } + } + else + { + // Regular Symptom + diseaseComp.Symptoms.Add(symptom, new SymptomData(minLevel, maxLevel)); + _popup.PopupEntity(Loc.GetString("disease-upgrade-purchased"), storeOwner, PopupType.Medium); + } + } + break; } + UpdateUi(storeOwner, diseaseComp); + } - void AddMoney(EntityUid uid, FixedPoint2 value) + private void AddMoney(EntityUid uid, FixedPoint2 value) { if (TryComp(uid, out var diseaseComp)) { @@ -313,6 +382,10 @@ private void OnInfectedDeath(MobStateChangedEvent args) if (Deleted(args.Target)) return; + // Check if the dying entity was actually infected + if (!HasComp(args.Target)) + return; + // Reward all other disease antagonists when any infected dies var diseaseQuery = EntityQueryEnumerator(); while (diseaseQuery.MoveNext(out var diseaseUid, out var diseaseComp)) diff --git a/Content.Server/_Sunrise/Disease/SickSystem.cs b/Content.Server/_Sunrise/Disease/SickSystem.cs index e9bfce2510e..385c6b65483 100644 --- a/Content.Server/_Sunrise/Disease/SickSystem.cs +++ b/Content.Server/_Sunrise/Disease/SickSystem.cs @@ -3,26 +3,20 @@ using Robust.Shared.Timing; using Content.Shared._Sunrise.Disease; using System.Numerics; -using Content.Server.Body.Components; using Content.Server.Body.Systems; using Content.Server.Chat.Systems; -using Content.Shared.Interaction.Events; using Robust.Server.GameObjects; -using Robust.Shared.Map; using Robust.Shared.Random; using Content.Shared.Humanoid; -using Content.Server.Store.Components; using Content.Server.Store.Systems; using Content.Server.Popups; using Content.Shared.Popups; using Content.Server.Chat; using Content.Shared.Stunnable; using Content.Shared.Damage.Prototypes; -using Content.Shared.Damage; using Content.Server.Emoting.Systems; using Content.Server.Speech.EntitySystems; using Content.Shared.FixedPoint; -using Content.Server.Medical; using Content.Server.Traits.Assorted; using Content.Shared.Body.Components; using Content.Shared.Chat; @@ -34,6 +28,8 @@ using Content.Shared.Store.Components; using Content.Shared.Damage.Systems; using Content.Shared.Chemistry.Components; +using Content.Shared.Inventory; +using Content.Shared.Zombies; namespace Content.Server._Sunrise.Disease; public sealed class SickSystem : SharedSickSystem { @@ -48,6 +44,7 @@ public sealed class SickSystem : SharedSickSystem [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedStunSystem _stun = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; private EntityLookupSystem Lookup => _entityManager.System(); public override void Initialize() { @@ -115,6 +112,9 @@ public void OnShut(EntityUid uid, SickComponent component, ComponentShutdown arg solution.AddReagent(reagentId, FixedPoint2.New((int)stream.BloodReferenceSolution.MaxVolume)); } + if (solution.Volume == 0) + return; + _bloodstream.ChangeBloodReagents(uid, solution); } } @@ -153,6 +153,17 @@ public override void Update(float frameTime) RaiseNetworkEvent(new ClientInfectEvent(GetNetEntity(uid), GetNetEntity(component.owner))); diseaseComp.SickOfAllTime++; AddMoney(component.owner, 5); + _popupSystem.PopupEntity(Loc.GetString("disease-infect-reward", ("points", 5)), component.owner, component.owner, PopupType.Medium); + + var state = new DiseaseInfoState( + diseaseComp.BaseInfectChance, + diseaseComp.CoughSneezeInfectChance, + diseaseComp.Lethal, + diseaseComp.Shield, + diseaseComp.Infected.Count, + diseaseComp.SickOfAllTime + ); + _ui.SetUiState(component.owner, DiseaseInfoUiKey.Key, state); component.Inited = true; } @@ -222,7 +233,7 @@ private void UpdateInfection(EntityUid uid, SickComponent component, EntityUid d if (!HasComp(uid)) { var c = AddComp(uid); - EntityManager.EntitySysManager.GetEntitySystem().SetNarcolepsy(uid, new Vector2(10, 30), new Vector2(300, 600), c); + EntityManager.EntitySysManager.GetEntitySystem().SetNarcolepsy(uid, new Vector2(60, 80), new Vector2(8, 12), c); } break; case "Muted": @@ -272,7 +283,11 @@ private void OnEmote(EntityUid uid, SickComponent component, ref EmoteEvent args { if (HasComp(entity) && !HasComp(entity) && !HasComp(entity)) { - OnInfected(entity, component.owner, disease.CoughSneezeInfectChance); + var ev = new ZombificationResistanceQueryEvent(SlotFlags.HEAD | SlotFlags.MASK | SlotFlags.OUTERCLOTHING); + RaiseLocalEvent(entity, ev); + + if (_robustRandom.Prob(ev.TotalCoefficient)) + OnInfected(entity, component.owner, disease.CoughSneezeInfectChance); } } } @@ -290,7 +305,11 @@ private void OnEmote(EntityUid uid, SickComponent component, ref EmoteEvent args { if (HasComp(entity) && !HasComp(entity) && !HasComp(entity)) { - OnInfected(entity, component.owner, disease.CoughSneezeInfectChance); + var ev = new ZombificationResistanceQueryEvent(SlotFlags.HEAD | SlotFlags.MASK | SlotFlags.OUTERCLOTHING); + RaiseLocalEvent(entity, ev); + + if (_robustRandom.Prob(ev.TotalCoefficient)) + OnInfected(entity, component.owner, disease.CoughSneezeInfectChance); } } } diff --git a/Content.Server/_Sunrise/Disease/SmallDiseaseRuleSystem.cs b/Content.Server/_Sunrise/Disease/SmallDiseaseRuleSystem.cs new file mode 100644 index 00000000000..31df2924b62 --- /dev/null +++ b/Content.Server/_Sunrise/Disease/SmallDiseaseRuleSystem.cs @@ -0,0 +1,110 @@ +using Content.Shared._Sunrise.Disease; +using Content.Server.GameTicking.Rules; +using Content.Shared.GameTicking.Components; +using Robust.Shared.Random; +using Robust.Shared.Prototypes; +using Content.Shared.Store; +using Content.Shared.Humanoid; +using Content.Shared.Mobs.Components; +using Content.Shared.Mind.Components; +using Robust.Shared.Map; +using Content.Server.Chat.Systems; +using Robust.Shared.Timing; + +namespace Content.Server._Sunrise.Disease; + +public sealed class SmallDiseaseRuleSystem : GameRuleSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly SharedTransformSystem _transform = default!; + [Dependency] private readonly ChatSystem _chatSystem = default!; + + protected override void Started(EntityUid uid, SmallDiseaseRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + Timer.Spawn(TimeSpan.FromMinutes(6), () => + { + var message = Loc.GetString("disease-biohazard-announcement"); + var sender = Loc.GetString("disease-biohazard-announcement-sender"); + + _chatSystem.DispatchGlobalAnnouncement(message, sender, playDefault: true, colorOverride: Color.Red); + }); + + // 1. Find potential victims (Humanoid, Has Mind, Not Dead, Not Sick) + var query = EntityQueryEnumerator(); + var candidates = new List(); + + while (query.MoveNext(out var entity, out _, out _, out _, out var xform)) + { + if (HasComp(entity)) + continue; + + // Simple check to ensure they are on a station/grid generally + if (xform.GridUid == null) + continue; + + candidates.Add(entity); + } + + if (candidates.Count == 0) + return; + + // 2. Spawn the Disease Entity (Dummy) + var diseaseUid = Spawn("MobDisease", MapCoordinates.Nullspace); + if (!TryComp(diseaseUid, out var diseaseComp)) + { + return; + } + + // 3. Configure Symptoms + // Always add Cough + diseaseComp.Symptoms.TryAdd("Cough", new SymptomData(2, 5)); // Cough is level 2 typically + + var currentPoints = 0; + var availableSymptoms = new List(); + + // Gather all symptom listings + foreach (var listing in _prototypeManager.EnumeratePrototypes()) + { + if (listing.Categories.Contains("DiseaseSymptomsCategory")) + { + availableSymptoms.Add(listing); + } + } + + // Randomly add until 50 points + // Safety loop + for (int i = 0; i < 20 && currentPoints < component.TargetSymptomPoints; i++) + { + if (availableSymptoms.Count == 0) break; + + var pick = _random.Pick(availableSymptoms); + var cost = pick.Cost.GetValueOrDefault("DiseasePoints", 0); + + // Avoid adding same symptom twice + if (!diseaseComp.Symptoms.ContainsKey(pick.ID)) + { + // Determine level (rough approximation or default) + // We'll use 0-5 as safe defaults or check ID specific logic if strictly needed + // Using 1-5 generic + diseaseComp.Symptoms.Add(pick.ID, new SymptomData(1, 5)); + currentPoints += cost.Int(); + } + } + + // 4. Infect Targets + _random.Shuffle(candidates); + var targetCount = Math.Min(component.TargetInfectedCount, candidates.Count); + + for (int i = 0; i < targetCount; i++) + { + var victim = candidates[i]; + var sick = EnsureComp(victim); + sick.owner = diseaseUid; + diseaseComp.Infected.Add(victim); + } + } +} diff --git a/Content.Shared/Storage/Components/EntityStorageComponent.cs b/Content.Shared/Storage/Components/EntityStorageComponent.cs index ecfcccc45b7..107555514d1 100644 --- a/Content.Shared/Storage/Components/EntityStorageComponent.cs +++ b/Content.Shared/Storage/Components/EntityStorageComponent.cs @@ -42,7 +42,8 @@ public sealed partial class EntityStorageComponent : Component, IGasMixtureHolde public int MasksToRemove = (int)( CollisionGroup.MidImpassable | CollisionGroup.HighImpassable | - CollisionGroup.LowImpassable); + CollisionGroup.LowImpassable | + CollisionGroup.BulletImpassable); /// /// Collision masks that were removed from ANY layer when the storage was opened; diff --git a/Content.Shared/_Sunrise/Disease/CureDiseaseInfection.cs b/Content.Shared/_Sunrise/Disease/CureDiseaseInfection.cs index 43d8d33c854..3695406e5f5 100644 --- a/Content.Shared/_Sunrise/Disease/CureDiseaseInfection.cs +++ b/Content.Shared/_Sunrise/Disease/CureDiseaseInfection.cs @@ -11,11 +11,14 @@ public sealed partial class CureDiseaseInfectionEntityEffectSystem : EntityEffec protected override void Effect(Entity entity, ref EntityEffectEvent args) { - if (_entityManager.TryGetComponent(entity.Owner, out var disease)) + if (_entityManager.TryGetComponent(entity.Owner, out var sick)) { - var comp = _entityManager.EnsureComponent(entity.Owner); - comp.Immune = args.Effect.Innoculate; - comp.Delay = TimeSpan.FromMinutes(2) + TimeSpan.FromSeconds(disease.Shield * 30); + if (_entityManager.TryGetComponent(sick.owner, out var disease)) + { + var comp = _entityManager.EnsureComponent(entity.Owner); + comp.Immune = args.Effect.Innoculate; + comp.Delay = TimeSpan.FromMinutes(2) + TimeSpan.FromSeconds(disease.Shield * 30); + } } } } diff --git a/Content.Shared/_Sunrise/Disease/DiseaseInfoState.cs b/Content.Shared/_Sunrise/Disease/DiseaseInfoState.cs new file mode 100644 index 00000000000..5f052648060 --- /dev/null +++ b/Content.Shared/_Sunrise/Disease/DiseaseInfoState.cs @@ -0,0 +1,24 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.Disease; + +[Serializable, NetSerializable] +public sealed class DiseaseInfoState : BoundUserInterfaceState +{ + public float BaseInfectChance; + public float CoughSneezeInfectChance; + public int Lethal; + public int Shield; + public int CurrentInfected; + public int TotalInfected; + + public DiseaseInfoState(float baseInfectChance, float coughSneezeInfectChance, int lethal, int shield, int currentInfected, int totalInfected) + { + BaseInfectChance = baseInfectChance; + CoughSneezeInfectChance = coughSneezeInfectChance; + Lethal = lethal; + Shield = shield; + CurrentInfected = currentInfected; + TotalInfected = totalInfected; + } +} diff --git a/Content.Shared/_Sunrise/Disease/DiseaseInfoUiKey.cs b/Content.Shared/_Sunrise/Disease/DiseaseInfoUiKey.cs new file mode 100644 index 00000000000..ee161ef9429 --- /dev/null +++ b/Content.Shared/_Sunrise/Disease/DiseaseInfoUiKey.cs @@ -0,0 +1,9 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Sunrise.Disease; + +[Serializable, NetSerializable] +public enum DiseaseInfoUiKey +{ + Key +} diff --git a/Content.Shared/_Sunrise/Disease/DiseaseRoleComponent.cs b/Content.Shared/_Sunrise/Disease/DiseaseRoleComponent.cs index 414552cf9f5..c15a170efc8 100644 --- a/Content.Shared/_Sunrise/Disease/DiseaseRoleComponent.cs +++ b/Content.Shared/_Sunrise/Disease/DiseaseRoleComponent.cs @@ -38,7 +38,7 @@ public sealed partial class DiseaseRoleComponent : Component [DataField] public int SickOfAllTime = 0; [DataField("newBloodReagent")] - public List NewBloodReagent = new() { "ZombieBlood" }; + public List NewBloodReagent = new(); } [Serializable, NetSerializable] diff --git a/Content.Shared/_Sunrise/Disease/DiseaseVaccineTimerSystem.cs b/Content.Shared/_Sunrise/Disease/DiseaseVaccineTimerSystem.cs index 0eceeb571d8..07f5721b5a7 100644 --- a/Content.Shared/_Sunrise/Disease/DiseaseVaccineTimerSystem.cs +++ b/Content.Shared/_Sunrise/Disease/DiseaseVaccineTimerSystem.cs @@ -45,7 +45,7 @@ public override void Update(float frameTime) if (!HasComp(uid)) { RemComp(uid); - return; + continue; } RemComp(uid); diff --git a/Content.Shared/_Sunrise/Disease/SmallDiseaseRuleComponent.cs b/Content.Shared/_Sunrise/Disease/SmallDiseaseRuleComponent.cs new file mode 100644 index 00000000000..d9af32c2b96 --- /dev/null +++ b/Content.Shared/_Sunrise/Disease/SmallDiseaseRuleComponent.cs @@ -0,0 +1,13 @@ +using Content.Shared.GameTicking.Components; + +namespace Content.Shared._Sunrise.Disease; + +[RegisterComponent] +public sealed partial class SmallDiseaseRuleComponent : Component +{ + [DataField] + public int TargetInfectedCount = 3; + + [DataField] + public int TargetSymptomPoints = 50; +} diff --git a/Resources/Locale/en-US/_strings/_sunrise/disease/disease.ftl b/Resources/Locale/en-US/_strings/_sunrise/disease/disease.ftl index 89078f907c7..9be33ef20c7 100644 --- a/Resources/Locale/en-US/_strings/_sunrise/disease/disease.ftl +++ b/Resources/Locale/en-US/_strings/_sunrise/disease/disease.ftl @@ -36,4 +36,8 @@ disease-infect-charge-max-reached = Already at maximum charges ({ $maxCharges }) disease-death-reward = Received { $points } Disease Points from another disease's death! disease-upgrade-purchased = Upgrade purchased successfully! -disease-upgrade-max-reached = Maximum upgrade level reached! \ No newline at end of file +disease-upgrade-max-reached = Maximum upgrade level reached! + +disease-infect-success = Successful infection! + +disease-info-window-title = Disease Statistics diff --git a/Resources/Locale/ru-RU/_strings/_sunrise/disease/disease.ftl b/Resources/Locale/ru-RU/_strings/_sunrise/disease/disease.ftl index 5469a6b695f..93fa934cce3 100644 --- a/Resources/Locale/ru-RU/_strings/_sunrise/disease/disease.ftl +++ b/Resources/Locale/ru-RU/_strings/_sunrise/disease/disease.ftl @@ -36,4 +36,8 @@ disease-infect-charge-max-reached = Уже максимальное количе disease-death-reward = Получено { $points } очков болезни от смерти другого больного! disease-upgrade-purchased = Улучшение успешно куплено! -disease-upgrade-max-reached = Достигнут максимальный уровень улучшения! \ No newline at end of file +disease-upgrade-max-reached = Достигнут максимальный уровень улучшения! + +disease-infect-success = Успешное заражение! + +disease-info-window-title = Статистика Болезни diff --git a/Resources/Prototypes/_Sunrise/Catalog/disease_catalog.yml b/Resources/Prototypes/_Sunrise/Catalog/disease_catalog.yml index 3b67b7fd3d6..f6e69a7f3a1 100644 --- a/Resources/Prototypes/_Sunrise/Catalog/disease_catalog.yml +++ b/Resources/Prototypes/_Sunrise/Catalog/disease_catalog.yml @@ -4,11 +4,10 @@ description: listing-disease-cough-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: cough } raiseProductEventOnUser: true - productAction: ActionDiseaseCough cost: DiseasePoints: 0 categories: - - DiseaseInfectCategory + - DiseaseSymptomsCategory conditions: - !type:ListingLimitedStockCondition stock: 1 @@ -18,7 +17,7 @@ name: listing-disease-sneeze-name description: listing-disease-sneeze-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: sneeze } - productAction: ActionDiseaseSneeze + raiseProductEventOnUser: true cost: DiseasePoints: 20 categories: @@ -32,7 +31,7 @@ name: listing-disease-vomit-name description: listing-disease-vomit-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: vomit } - productAction: ActionDiseaseVomit + raiseProductEventOnUser: true cost: DiseasePoints: 25 categories: @@ -46,7 +45,7 @@ name: listing-disease-narcolepsy-name description: listing-disease-narcolepsy-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: narcolepsy } - productAction: ActionDiseaseNarcolepsy + raiseProductEventOnUser: true cost: DiseasePoints: 20 categories: @@ -60,7 +59,7 @@ name: listing-disease-cry-name description: listing-disease-cry-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: sob } - productAction: ActionDiseaseCrying + raiseProductEventOnUser: true cost: DiseasePoints: 10 categories: @@ -74,7 +73,7 @@ name: listing-disease-muted-name description: listing-disease-muted-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: muted } - productAction: ActionDiseaseMuted + raiseProductEventOnUser: true cost: DiseasePoints: 25 categories: @@ -88,7 +87,7 @@ name: listing-disease-slowness-name description: listing-disease-slowness-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: slowness } - productAction: ActionDiseaseSlowness + raiseProductEventOnUser: true cost: DiseasePoints: 15 categories: @@ -102,7 +101,7 @@ name: listing-disease-bleed-name description: listing-disease-bleed-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: bleed } - productAction: ActionDiseaseBleed + raiseProductEventOnUser: true cost: DiseasePoints: 30 categories: @@ -116,7 +115,7 @@ name: listing-disease-blindness-name description: listing-disease-blindness-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: blindness } - productAction: ActionDiseaseBlindness + raiseProductEventOnUser: true cost: DiseasePoints: 40 categories: @@ -130,7 +129,7 @@ name: listing-disease-insult-name description: listing-disease-insult-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: lethal } - productAction: ActionDiseaseInsult + raiseProductEventOnUser: true cost: DiseasePoints: 20 categories: @@ -144,9 +143,9 @@ name: listing-disease-zombie-name description: listing-disease-zombie-description icon: { sprite: /Textures/_Sunrise/Actions/disease.rsi, state: lethal } - productAction: ActionDiseaseZombie + raiseProductEventOnUser: true cost: - DiseasePoints: 200 + DiseasePoints: 500 categories: - DiseaseSymptomsCategory conditions: @@ -175,7 +174,7 @@ - DiseaseEvolutionCategory conditions: - !type:ListingLimitedStockCondition - stock: 1 + stock: 5 - type: listing id: InfectChance @@ -189,7 +188,7 @@ - DiseaseEvolutionCategory conditions: - !type:ListingLimitedStockCondition - stock: 1 + stock: 3 - type: listing id: Shield @@ -203,7 +202,7 @@ - DiseaseEvolutionCategory conditions: - !type:ListingLimitedStockCondition - stock: 1 + stock: 6 - type: listing id: Lethal diff --git a/Resources/Prototypes/_Sunrise/GameRules/smalldisease.yml b/Resources/Prototypes/_Sunrise/GameRules/smalldisease.yml new file mode 100644 index 00000000000..2da18d2a88a --- /dev/null +++ b/Resources/Prototypes/_Sunrise/GameRules/smalldisease.yml @@ -0,0 +1,5 @@ +- type: entity + id: SmallDisease + parent: BaseGameRule + components: + - type: SmallDiseaseRule diff --git a/Resources/Prototypes/_Sunrise/Roles/Antags/disease.yml b/Resources/Prototypes/_Sunrise/Roles/Antags/disease.yml index 9f65977b5b0..943570f6c19 100644 --- a/Resources/Prototypes/_Sunrise/Roles/Antags/disease.yml +++ b/Resources/Prototypes/_Sunrise/Roles/Antags/disease.yml @@ -10,7 +10,7 @@ context: "ghost" - type: MovementSpeedModifier baseWalkSpeed: 6 - baseSprintSpeed: 6 + baseSprintSpeed: 10 - type: Sprite noRot: true drawdepth: Ghosts @@ -61,6 +61,8 @@ interfaces: enum.StoreUiKey.Key: type: StoreBoundUserInterface + enum.DiseaseInfoUiKey.Key: + type: DiseaseInfoBui - type: Visibility layer: 2 #ghost vis layer - type: Store diff --git a/Resources/ServerInfo/Guidebook/_Sunrise/Antagonist/Disease.xml b/Resources/ServerInfo/Guidebook/_Sunrise/Antagonist/Disease.xml index b5e54f0dd7f..ea8a506fdb7 100644 --- a/Resources/ServerInfo/Guidebook/_Sunrise/Antagonist/Disease.xml +++ b/Resources/ServerInfo/Guidebook/_Sunrise/Antagonist/Disease.xml @@ -1,87 +1,121 @@ - # Разумная болезнь - - - [color=#999999][italic]"Ты просто простудился... или они уже думают за тебя?"[/italic][/color] - - - - - - Разумная болезнь — антагонист, способный заражать всех существ и активно распространяться. Болезнь мутирует: появляются разные симптомы, растёт летальность, заразность и устойчивость к лекарствам. Она легко распространяется, если не принять необходимые меры. - - ## Основные симптомы - - Базовые симптомы есть у каждой болезни: головная боль и головокружение. Основной способ передачи — кашель и чихание. - - ## Передача - - Передача вируса происходит при кашле и чихании: если рядом с вами находится заражённый с этими симптомами, есть шанс заразиться. Эта вероятность уменьшается при ношении защитных костюмов, масок и прочих средств. - - Однако у болезни есть возможность заразить вас, игнорируя упомянутые средства защиты, при этом она использует очки эволюции, что означает, что вы в любом случае можете заболеть. - - ## Возможные симптомы - - Также, помимо основных симптомов, у болезни есть перечень других, таких как: - - - Непроизвольные слёзы (10 ОЭ) - - [color=#999999][italic]У заражённых активно слезятся глаза, из-за чего кажется, что они плачут.[/italic][/color] - - - - Изнеможение (15 ОЭ) - - [color=#999999][italic]Вирус вызывает разрушение мышечных волокон, приводящее к атрофии и сопровождающееся слабостью. Снижает общую мобильность.[/italic][/color] - - - - Сонливость (20 ОЭ) - - [color=#999999][italic]У заражённых появляется постоянное желание спать, с которым они иногда не могут справиться.[/italic][/color] - - - - Судороги (20 ОЭ) - - [color=#999999][italic]Длительная болезнь вызывает гиперстимуляцию двигательных нейронов, в результате чего больные могут испытывать перенапряжение мышц, приводящее к судорогам.[/italic][/color] - - - - Немота (25 ОЭ) - - [color=#999999][italic]Мутация вызывает повреждение подъязычного нерва, приводя к параличу мышц языка, из-за чего больные теряют возможность нормально говорить.[/italic][/color] - - - - Тошнота (25 ОЭ) - - [color=#999999][italic]Заражённых начинает тошнить, вызывая рвоту.[/italic][/color] - - - - Кровопотеря (30 ОЭ) - - [color=#999999][italic]Вирус вызывает денатурацию гемоглобина крови, из-за чего у всех носителей появляется тяжёлая степень анемии.[/italic][/color] - - - - Слепота (40 ОЭ) - - [color=#999999][italic]Длительная болезнь приводит к отмиранию зрительного нерва, что приводит к практически полной слепоте больного.[/italic][/color] - - - ## Базовые противодействия - - Если вы заметили, что у вас появились какие-то из симптомов, не стоит сразу бежать в вирусологию, но стоит принять минимальные меры: носить маску, избегать контакта с персоналом. - - Если у вас обнаружилось два и более симптома, например кашель, головная боль и изнеможение, немедленно отправьтесь в медицинский отдел и оповестите врача о том, что вы, вероятно, заражены. - - ## Вакцина - - Для создания вакцины необходима пробирка, шприц, здоровый человек, заражённый человек и вакцинатор. Делайте всё строго в последовательности: - - - Набираем кровь заражённого человека в пробирку - - - Вставляем пробирку в вакцинатор - - - Получаем бумажку с указаниями для создания вакцины. - - - Делаем всё как указано на листочке. - - Готово, вы получили вакцину. + +[head=1][color=#ff9966]Разумная болезнь[/color][/head] + + +[color=#999999][italic]"Ты просто простудился... или они уже думают за тебя?"[/italic][/color] + + + + + +[color=#cccccc]Разумная болезнь — это антагонист, способный заражать всех существ и передаваться между ними. Болезнь обладает свойством «мутировать»: у неё появляются разные симптомы, а её летальность, заразность и устойчивость к лекарствам увеличиваются. Она легко распространяется, если не принять необходимые меры.[/color] + +[head=2][color=#ffaa66]Основные симптомы[/color][/head] + +[color=#dddddd]Первыми отличимыми симптомами, которые есть у каждой болезни, являются головная боль и головокружение. Также основными переносными симптомами являются кашель и чихание.[/color] + +[head=2][color=#ffaa66]Передача[/color][/head] + +[color=#dddddd]Передача вируса происходит при кашле и чихании. Если рядом с вами находится заражённый, у которого есть упомянутые симптомы, вы с определённой вероятностью можете заразиться. Эта вероятность уменьшается при ношении защитных костюмов, масок и прочих средств.[/color] + +[color=#dddddd]Однако у болезни есть возможность заразить вас, игнорируя упомянутые средства защиты, расходуя на это очки эволюции. Это означает, что вы в любом случае можете заболеть.[/color] + +[head=2][color=#ffaa66]Возможные симптомы[/color][/head] + +[color=#dddddd]Кроме основных симптомов, у болезни есть перечень других, таких как:[/color] + +[head=3][color=#ffbb77]Непроизвольные слёзы (10 ОЭ)[/color][/head] + +[color=#999999][italic]У заражённых активно слезятся глаза, из-за чего кажется, что они плачут.[/italic][/color] + + +[head=3][color=#ffbb77]Изнеможение (15 ОЭ)[/color][/head] + +[color=#999999][italic]Вирус вызывает разрушение мышечных волокон, приводящее к атрофии и сопровождающееся слабостью. Снижает общую мобильность.[/italic][/color] + + +[head=3][color=#ffbb77]Сонливость (20 ОЭ)[/color][/head] + +[color=#999999][italic]У заражённых появляется постоянное желание спать, с которым они иногда не могут справиться.[/italic][/color] + + +[head=3][color=#ffbb77]Судороги (20 ОЭ)[/color][/head] +[color=#999999][italic]Длительная болезнь вызывает гиперстимуляцию двигательных нейронов, в результате чего больные могут испытывать перенапряжение мышц, приводящее к судорогам.[/italic][/color] + + +[head=3][color=#ffbb77]Немота (25 ОЭ)[/color][/head] +[color=#999999][italic]Мутация вызывает повреждение подъязычного нерва, приводя к параличу мышц языка, из-за чего больные теряют возможность нормально говорить.[/italic][/color] + +[head=3][color=#ffbb77]Тошнота (25 ОЭ)[/color][/head] +[color=#999999][italic]Заражённых начинает тошнить, вызывая рвоту.[/italic][/color] + + +[head=3][color=#ffbb77]Кровопотеря (30 ОЭ)[/color][/head] +[color=#999999][italic]Вирус вызывает денатурацию гемоглобина крови, из-за чего у всех носителей появляется тяжёлая степень анемии.[/italic][/color] + + +[head=3][color=#ffbb77]Слепота (40 ОЭ)[/color][/head] +[color=#999999][italic]Длительная болезнь приводит к отмиранию зрительного нерва, что приводит к практически полной слепоте больного.[/italic][/color] + + +[head=2][color=#ffaa66]Базовые противодействия[/color][/head] + +[color=#dddddd]Если вы заметили, что у вас появились какие-то из симптомов, не стоит сразу бежать в вирусологию, но стоит принять минимальные меры: носить маску, избегать контакта с персоналом.[/color] + +[color=#dddddd]Если же у вас обнаружилось 2 и более симптома, например, кашель, головная боль и изнеможение, немедленно отправьтесь в медицинский отдел, а также оповестите врача о том, что вы, вероятно, заражены.[/color] + +[head=2][color=#ffaa66]Вакцина[/color][/head] + +[head=3][color=#ffbb77]Получение рецепта[/color][/head] + +[color=#dddddd]Используйте бумагу на Вакцинаторе. Вы получите рецепт, который зависит от случайной «Группы крови» болезни в этом раунде.[/color] + + + + + + + + +[head=3][color=#ffbb77]Создание заготовки[/color][/head] + +[color=#dddddd]Смешайте указанные в рецепте химикаты с [bold]Кровью заражённого[/bold] (получите у пациента), чтобы создать [color=#83a7b1]Незавершённую вакцину[/color].[/color] + + + + + + +[head=3][color=#ffbb77]Синтез вакцины (Антивирусина)[/color][/head] + +[color=#dddddd]Поместите полученную [color=#83a7b1]Вирусин[/color] и единицу [bold]Чистой крови[/bold] (здорового человека) в Вакцинатор. Он переработает их в [color=#86caf7]Антивирусин[/color] (20ед).[/color] + + + +[head=3][color=#ffbb77]Антивирусин+[/color][/head] + +[color=#dddddd]Можно создать улучшенную версию, смешав обычную [color=#86caf7]Антивирусин[/color] с Криптобиолином, Сигинатом и Кровью заражённого. Она даёт [color=#8192ea]постоянный иммунитет[/color].[/color] + + + + + + + +[head=2][color=#ffaa66]Действие вакцины[/color][/head] + +[color=#dddddd]При инъекции пациенту (10ед обычной или 7ед Антивирусина+):[/color] + +[head=3][color=#ffbb77]Таймер лечения[/color][/head] +[color=#dddddd]Запускается процесс исцеления. Время зависит от уровня защиты («Щита») болезни: [color=#ff9999]2 минуты + 30 сек за каждый уровень щита[/color].[/color] + +[head=3][color=#ffbb77]Побочный эффект[/color][/head] +[color=#dddddd]Пока вакцина действует, [bold]скорость бега пациента снижается в 2 раза[/bold]. Это делает его уязвимым![/color] + +[head=3][color=#ffbb77]Результат[/color][/head] +[color=#dddddd]По истечении таймера болезнь полностью исчезает. Если использовалась Вакцина+, пациент получает [color=#99ff99]иммунитет[/color].[/color] + +[color=#dddddd][bold]Будьте осторожны[/bold]: болезнь может попытаться убить ослабленного пациента, пока вакцина действует![/color]