From 836c0bb1c47a3df6d99513ce1a235907f0f1fa1d Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Tue, 5 Jul 2022 23:42:51 -0400 Subject: [PATCH] Zombie Mode [New Game Mode] (#8501) Co-authored-by: Kara Co-authored-by: metalgearsloth --- Content.Server/Chat/Managers/ChatManager.cs | 6 +- Content.Server/Chat/Managers/IChatManager.cs | 2 +- .../GameTicking/Rules/ZombieRuleSystem.cs | 312 ++++++++++++++++++ Content.Server/Zombies/ZombieComponent.cs | 44 ++- Content.Server/Zombies/ZombieSystem.cs | 48 ++- .../Zombies/ZombifyOnDeathComponent.cs | 9 +- .../Zombies/ZombifyOnDeathSystem.cs | 111 +++++-- Content.Shared/CCVar/CCVars.cs | 13 + .../Inventory/InventorySystem.Equip.cs | 1 - Content.Shared/Zombies/ZombieEvents.cs | 25 ++ .../Locale/en-US/actions/actions/zombie.ftl | 2 + .../game-presets/preset-zombies.ftl | 27 ++ Resources/Locale/en-US/zombies/zombie.ftl | 2 +- Resources/Prototypes/Actions/types.yml | 7 + .../Catalog/Fills/Backpacks/duffelbag.yml | 16 + Resources/Prototypes/Damage/modifier_sets.yml | 5 +- Resources/Prototypes/Diseases/zombie.yml | 11 +- .../Clothing/Head/hardsuit-helmets.yml | 50 +++ .../Clothing/OuterClothing/hardsuits.yml | 32 ++ .../Prototypes/Entities/Mobs/NPCs/human.yml | 14 + Resources/Prototypes/Roles/Antags/zombie.yml | 13 + .../Roles/Jobs/Fun/misc_startinggear.yml | 21 ++ Resources/Prototypes/game_presets.yml | 14 + Resources/Prototypes/game_rules.yml | 6 + Resources/Prototypes/secret_weights.yml | 3 +- .../cburn.rsi/equipped-head-unshaded.png | Bin 0 -> 162 bytes .../Hardsuits/cburn.rsi/equipped-head.png | Bin 0 -> 623 bytes .../Head/Hardsuits/cburn.rsi/icon-flash.png | Bin 0 -> 652 bytes .../Hardsuits/cburn.rsi/icon-unshaded.png | Bin 0 -> 141 bytes .../Head/Hardsuits/cburn.rsi/icon.png | Bin 0 -> 408 bytes .../cburn.rsi/inhand-left-unshaded.png | Bin 0 -> 465 bytes .../Head/Hardsuits/cburn.rsi/inhand-left.png | Bin 0 -> 519 bytes .../cburn.rsi/inhand-right-unshaded.png | Bin 0 -> 415 bytes .../Head/Hardsuits/cburn.rsi/inhand-right.png | Bin 0 -> 527 bytes .../Hardsuits/cburn.rsi/light-overlay.png | Bin 0 -> 333 bytes .../Head/Hardsuits/cburn.rsi/meta.json | 47 +++ .../cburn.rsi/equipped-OUTERCLOTHING.png | Bin 0 -> 2333 bytes .../Hardsuits/cburn.rsi/icon.png | Bin 0 -> 1265 bytes .../Hardsuits/cburn.rsi/inhand-left.png | Bin 0 -> 1247 bytes .../Hardsuits/cburn.rsi/inhand-right.png | Bin 0 -> 1306 bytes .../Hardsuits/cburn.rsi/meta.json | 26 ++ .../Textures/Interface/Actions/meta.json | 5 +- .../Interface/Actions/zombie-turn.png | Bin 0 -> 858 bytes 43 files changed, 821 insertions(+), 51 deletions(-) create mode 100644 Content.Server/GameTicking/Rules/ZombieRuleSystem.cs create mode 100644 Content.Shared/Zombies/ZombieEvents.cs create mode 100644 Resources/Locale/en-US/actions/actions/zombie.ftl create mode 100644 Resources/Locale/en-US/game-ticking/game-presets/preset-zombies.ftl create mode 100644 Resources/Prototypes/Roles/Antags/zombie.yml create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-unshaded.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/icon-flash.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/icon-unshaded.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/icon.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left-unshaded.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right-unshaded.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/light-overlay.png create mode 100644 Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/meta.json create mode 100644 Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/inhand-left.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/inhand-right.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/meta.json create mode 100644 Resources/Textures/Interface/Actions/zombie-turn.png diff --git a/Content.Server/Chat/Managers/ChatManager.cs b/Content.Server/Chat/Managers/ChatManager.cs index 237d97520c..08db5ede9d 100644 --- a/Content.Server/Chat/Managers/ChatManager.cs +++ b/Content.Server/Chat/Managers/ChatManager.cs @@ -207,7 +207,7 @@ namespace Content.Server.Chat.Managers #region Utility - public void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, INetChannel client) + public void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, INetChannel client, Color? colorOverride = null) { var msg = new MsgChatMessage(); msg.Channel = channel; @@ -215,6 +215,10 @@ namespace Content.Server.Chat.Managers msg.MessageWrap = messageWrap; msg.SenderEntity = source; msg.HideChat = hideChat; + if (colorOverride != null) + { + msg.MessageColorOverride = colorOverride.Value; + } _netManager.ServerSendMessage(msg, client); } diff --git a/Content.Server/Chat/Managers/IChatManager.cs b/Content.Server/Chat/Managers/IChatManager.cs index c3e7c8bd81..606a186163 100644 --- a/Content.Server/Chat/Managers/IChatManager.cs +++ b/Content.Server/Chat/Managers/IChatManager.cs @@ -24,7 +24,7 @@ namespace Content.Server.Chat.Managers void SendAdminAnnouncement(string message); void ChatMessageToOne(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, - INetChannel client); + INetChannel client, Color? colorOverride = null); void ChatMessageToMany(ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, List clients, Color? colorOverride = null); void ChatMessageToManyFiltered(Filter filter, ChatChannel channel, string message, string messageWrap, EntityUid source, bool hideChat, Color? colorOverride); diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs new file mode 100644 index 0000000000..f86ad355ef --- /dev/null +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -0,0 +1,312 @@ +using System.Linq; +using Content.Server.Actions; +using Content.Server.Chat.Managers; +using Content.Server.Disease; +using Content.Server.GameTicking.Rules.Configurations; +using Content.Server.Mind.Components; +using Content.Server.Players; +using Content.Server.Popups; +using Content.Server.Preferences.Managers; +using Content.Server.RoundEnd; +using Content.Server.Traitor; +using Content.Server.Zombies; +using Content.Shared.Actions.ActionTypes; +using Content.Shared.CCVar; +using Content.Shared.CharacterAppearance.Components; +using Content.Shared.FixedPoint; +using Content.Shared.MobState; +using Content.Shared.MobState.Components; +using Content.Shared.Preferences; +using Content.Shared.Roles; +using Content.Shared.Zombies; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Utility; + +namespace Content.Server.GameTicking.Rules; + +public sealed class ZombieRuleSystem : GameRuleSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IServerPreferencesManager _prefs = default!; + [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; + [Dependency] private readonly DiseaseSystem _diseaseSystem = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly ActionsSystem _action = default!; + [Dependency] private readonly ZombifyOnDeathSystem _zombify = default!; + + private Dictionary _initialInfectedNames = new(); + + public override string Prototype => "Zombie"; + + private const string PatientZeroPrototypeID = "InitialInfected"; + private const string InitialZombieVirusPrototype = "PassiveZombieVirus"; + private const string ZombifySelfActionPrototype = "TurnUndead"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartAttempt); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnRoundEndText); + SubscribeLocalEvent(OnJobAssigned); + + SubscribeLocalEvent(OnEntityZombified); + SubscribeLocalEvent(OnZombifySelf); + } + + private void OnRoundEndText(RoundEndTextAppendEvent ev) + { + if (!Enabled) + return; + + //this is just the general condition thing used for determining the win/lose text + var percent = Math.Round(GetInfectedPercentage(out var livingHumans), 2); + + if (percent <= 0) + ev.AddLine(Loc.GetString("zombie-round-end-amount-none")); + else if (percent <= 0.25) + ev.AddLine(Loc.GetString("zombie-round-end-amount-low")); + else if (percent <= 0.5) + ev.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", (percent * 100).ToString()))); + else if (percent < 1) + ev.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", (percent * 100).ToString()))); + else + ev.AddLine(Loc.GetString("zombie-round-end-amount-all")); + + ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", _initialInfectedNames.Count))); + foreach (var player in _initialInfectedNames) + { + ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial", + ("name", player.Key), + ("username", player.Value))); + } + + ///Gets a bunch of the living players and displays them if they're under a threshold. + ///InitialInfected is used for the threshold because it scales with the player count well. + if (livingHumans.Count > 0 && livingHumans.Count <= _initialInfectedNames.Count) + { + ev.AddLine(""); + ev.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", livingHumans.Count))); + foreach (var survivor in livingHumans) + { + var meta = MetaData(survivor); + var username = string.Empty; + if (TryComp(survivor, out var mindcomp)) + if (mindcomp.Mind != null && mindcomp.Mind.Session != null) + username = mindcomp.Mind.Session.Name; + + ev.AddLine(Loc.GetString("zombie-round-end-user-was-survivor", + ("name", meta.EntityName), + ("username", username))); + } + } + } + + private void OnJobAssigned(RulePlayerJobsAssignedEvent ev) + { + if (!Enabled) + return; + + _initialInfectedNames = new(); + + InfectInitialPlayers(); + } + + /// + /// This is just checked if the last human somehow dies + /// by starving or flying off into space. + /// + private void OnMobStateChanged(MobStateChangedEvent ev) + { + if (!Enabled) + return; + CheckRoundEnd(ev.Entity); + } + + private void OnEntityZombified(EntityZombifiedEvent ev) + { + if (!Enabled) + return; + CheckRoundEnd(ev.Target); + } + + /// + /// The big kahoona function for checking if the round is gonna end + /// + /// depending on this uid, we should care about the round ending + private void CheckRoundEnd(EntityUid target) + { + //we only care about players, not monkeys and such. + if (!HasComp(target)) + return; + + var percent = GetInfectedPercentage(out var num); + if (num.Count == 1) //only one human left. spooky + _popup.PopupEntity(Loc.GetString("zombie-alone"), num[0], Filter.Entities(num[0])); + if (percent >= 1) //oops, all zombies + _roundEndSystem.EndRound(); + } + + private void OnStartAttempt(RoundStartAttemptEvent ev) + { + if (!Enabled) + return; + + var minPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers); + if (!ev.Forced && ev.Players.Length < minPlayers) + { + _chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers))); + ev.Cancel(); + return; + } + + if (ev.Players.Length == 0) + { + _chatManager.DispatchServerAnnouncement(Loc.GetString("zombie-no-one-ready")); + ev.Cancel(); + return; + } + } + + public override void Started(GameRuleConfiguration configuration) + { + //this technically will run twice with zombies on roundstart, but it doesn't matter because it fails instantly + InfectInitialPlayers(); + } + + public override void Ended(GameRuleConfiguration configuration) { } + + private void OnZombifySelf(EntityUid uid, ZombifyOnDeathComponent component, ZombifySelfActionEvent args) + { + _zombify.ZombifyEntity(uid); + + var action = new InstantAction(_prototypeManager.Index(ZombifySelfActionPrototype)); + _action.RemoveAction(uid, action); + } + + private float GetInfectedPercentage(out List livingHumans) + { + var allPlayers = EntityQuery(true); + var allZombers = GetEntityQuery(); + + var totalPlayers = new List(); + var livingZombies = new List(); + + livingHumans = new(); + + foreach (var ent in allPlayers) + { + if (ent.Item2.IsAlive()) + { + totalPlayers.Add(ent.Item2.Owner); + + if (allZombers.HasComponent(ent.Item1.Owner)) + livingZombies.Add(ent.Item2.Owner); + else + livingHumans.Add(ent.Item2.Owner); + } + } + return ((float) livingZombies.Count) / (float) totalPlayers.Count; + } + + /// + /// Infects the first players with the passive zombie virus. + /// Also records their names for the end of round screen. + /// + /// + /// The reason this code is written separately is to facilitate + /// allowing this gamemode to be started midround. As such, it doesn't need + /// any information besides just running. + /// + private void InfectInitialPlayers() + { + var allPlayers = _playerManager.ServerSessions.ToList(); + var playerList = new List(); + var prefList = new List(); + foreach (var player in allPlayers) + { + if (player.AttachedEntity != null) + { + playerList.Add(player); + + var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(player.UserId).SelectedCharacter; + if (pref.AntagPreferences.Contains(PatientZeroPrototypeID)) + prefList.Add(player); + } + } + + if (playerList.Count == 0) + return; + + var playersPerInfected = _cfg.GetCVar(CCVars.ZombiePlayersPerInfected); + var maxInfected = _cfg.GetCVar(CCVars.ZombieMaxInitialInfected); + + var numInfected = Math.Max(1, + (int) Math.Min( + Math.Floor((double) playerList.Count / playersPerInfected), maxInfected)); + + for (var i = 0; i < numInfected; i++) + { + IPlayerSession zombie; + if (prefList.Count == 0) + { + if (playerList.Count == 0) + { + Logger.InfoS("preset", "Insufficient number of players. stopping selection."); + break; + } + zombie = _random.PickAndTake(playerList); + Logger.InfoS("preset", "Insufficient preferred patient 0, picking at random."); + } + else + { + zombie = _random.PickAndTake(prefList); + playerList.Remove(zombie); + Logger.InfoS("preset", "Selected a patient 0."); + } + + var mind = zombie.Data.ContentData()?.Mind; + if (mind == null) + { + Logger.ErrorS("preset", "Failed getting mind for picked patient 0."); + continue; + } + + DebugTools.AssertNotNull(mind.OwnedEntity); + + mind.AddRole(new TraitorRole(mind, _prototypeManager.Index(PatientZeroPrototypeID))); + + var inCharacterName = string.Empty; + if (mind.OwnedEntity != null) + { + _diseaseSystem.TryAddDisease(mind.OwnedEntity.Value, InitialZombieVirusPrototype); + inCharacterName = MetaData(mind.OwnedEntity.Value).EntityName; + + var action = new InstantAction(_prototypeManager.Index(ZombifySelfActionPrototype)); + _action.AddAction(mind.OwnedEntity.Value, action, null); + } + + if (mind.Session != null) + { + var messageWrapper = Loc.GetString("chat-manager-server-wrap-message"); + + //gets the names now in case the players leave. + if (inCharacterName != null) + _initialInfectedNames.Add(inCharacterName, mind.Session.Name); + + // I went all the way to ChatManager.cs and all i got was this lousy T-shirt + _chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server, Loc.GetString("zombie-patientzero-role-greeting"), + messageWrapper, default, false, mind.Session.ConnectedClient, Color.Plum); + } + } + } +} diff --git a/Content.Server/Zombies/ZombieComponent.cs b/Content.Server/Zombies/ZombieComponent.cs index 57f59c5b5f..0204ac1df8 100644 --- a/Content.Server/Zombies/ZombieComponent.cs +++ b/Content.Server/Zombies/ZombieComponent.cs @@ -1,3 +1,7 @@ +using Content.Shared.Roles; +using Content.Shared.Weapons.Melee; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + namespace Content.Server.Zombies { [RegisterComponent] @@ -7,6 +11,44 @@ namespace Content.Server.Zombies /// The coefficient of the damage reduction applied when a zombie /// attacks another zombie. longe name /// - public float OtherZombieDamageCoefficient = 0.75f; + [ViewVariables] + public float OtherZombieDamageCoefficient = 0.5f; + + /// + /// The baseline infection chance you have if you are completely nude + /// + [ViewVariables] + public float MaxZombieInfectionChance = 0.75f; + + /// + /// The minimum infection chance possible. This is simply to prevent + /// being invincible by bundling up. + /// + [ViewVariables] + public float MinZombieInfectionChance = 0.1f; + + /// + /// The skin color of the zombie + /// + [ViewVariables, DataField("skinColor")] + public Color SkinColor = new(0.45f, 0.51f, 0.29f); + + /// + /// The eye color of the zombie + /// + [ViewVariables, DataField("eyeColor")] + public Color EyeColor = new(0.96f, 0.13f, 0.24f); + + /// + /// The attack arc of the zombie + /// + [ViewVariables, DataField("attackArc", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string AttackArc = "claw"; + + /// + /// The role prototype of the zombie antag role + /// + [ViewVariables, DataField("zombieRoldId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public readonly string ZombieRoleId = "Zombie"; } } diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 818e3941b4..902b58f255 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -7,6 +7,11 @@ using Content.Server.Weapon.Melee; using Content.Shared.Chemistry.Components; using Content.Shared.MobState.Components; using Content.Server.Disease; +using Content.Shared.Inventory; +using Content.Server.Popups; +using Robust.Shared.Player; +using Content.Server.Inventory; +using Robust.Shared.Prototypes; namespace Content.Server.Zombies { @@ -15,13 +20,52 @@ namespace Content.Server.Zombies [Dependency] private readonly DiseaseSystem _disease = default!; [Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly ZombifyOnDeathSystem _zombify = default!; + [Dependency] private readonly ServerInventorySystem _inv = default!; + [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IRobustRandom _robustRandom = default!; + public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnMeleeHit); } + private float GetZombieInfectionChance(EntityUid uid, ZombieComponent component) + { + float baseChance = component.MaxZombieInfectionChance; + + if (!TryComp(uid, out var inventoryComponent)) + return baseChance; + + var enumerator = + new InventorySystem.ContainerSlotEnumerator(uid, inventoryComponent.TemplateId, _protoManager, _inv, + SlotFlags.FEET | + SlotFlags.HEAD | + SlotFlags.EYES | + SlotFlags.GLOVES | + SlotFlags.MASK | + SlotFlags.NECK | + SlotFlags.INNERCLOTHING | + SlotFlags.OUTERCLOTHING); + + var items = 0f; + var total = 0f; + while (enumerator.MoveNext(out var con)) + { + total++; + + if (con.ContainedEntity != null) + items++; + } + + var max = component.MaxZombieInfectionChance; + var min = component.MinZombieInfectionChance; + //gets a value between the max and min based on how many items the entity is wearing + float chance = (max-min) * ((total - items)/total) + min; + return chance; + } + private void OnMeleeHit(EntityUid uid, ZombieComponent component, MeleeHitEvent args) { if (!EntityManager.TryGetComponent(args.User, out var zombieComp)) @@ -38,11 +82,11 @@ namespace Content.Server.Zombies if (!TryComp(entity, out var mobState) || HasComp(entity)) continue; - if (_robustRandom.Prob(0.5f) && HasComp(entity)) + if (HasComp(entity) && _robustRandom.Prob(GetZombieInfectionChance(entity, component))) _disease.TryAddDisease(entity, "ActiveZombieVirus"); if (HasComp(entity)) - args.BonusDamage = args.BaseDamage * zombieComp.OtherZombieDamageCoefficient; + args.BonusDamage = -args.BaseDamage * zombieComp.OtherZombieDamageCoefficient; if ((mobState.IsDead() || mobState.IsCritical()) && !HasComp(entity)) diff --git a/Content.Server/Zombies/ZombifyOnDeathComponent.cs b/Content.Server/Zombies/ZombifyOnDeathComponent.cs index c43ad57f51..793e356eeb 100644 --- a/Content.Server/Zombies/ZombifyOnDeathComponent.cs +++ b/Content.Server/Zombies/ZombifyOnDeathComponent.cs @@ -1,15 +1,8 @@ -using Content.Shared.Weapons.Melee; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Server.Zombies { [RegisterComponent] public sealed class ZombifyOnDeathComponent : Component { - [DataField("skinColor")] - public Color SkinColor = new Color(0.70f, 0.72f, 0.48f, 1); - - [DataField("attackArc", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string AttackArc = "claw"; + //this is not the component you are looking for } } diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs index 580bfb9ced..51b181f3b2 100644 --- a/Content.Server/Zombies/ZombifyOnDeathSystem.cs +++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs @@ -20,16 +20,23 @@ using Content.Server.Hands.Components; using Content.Server.Mind.Commands; using Content.Server.Temperature.Components; using Content.Server.Weapon.Melee.Components; -using Content.Server.Disease; using Robust.Shared.Containers; using Content.Shared.Movement.Components; using Content.Shared.MobState; +using Robust.Shared.Prototypes; +using Content.Shared.Roles; +using Content.Server.Traitor; +using Content.Shared.Zombies; +using Content.Server.Atmos.Miasma; namespace Content.Server.Zombies { /// - /// Handles zombie propagation and inherent zombie traits + /// Handles zombie propagation and inherent zombie traits /// + /// + /// Don't Shitcode Open Inside + /// public sealed class ZombifyOnDeathSystem : EntitySystem { [Dependency] private readonly SharedHandsSystem _sharedHands = default!; @@ -37,9 +44,9 @@ namespace Content.Server.Zombies [Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly ServerInventorySystem _serverInventory = default!; [Dependency] private readonly DamageableSystem _damageable = default!; - [Dependency] private readonly DiseaseSystem _disease = default!; [Dependency] private readonly SharedHumanoidAppearanceSystem _sharedHuApp = default!; [Dependency] private readonly IChatManager _chatMan = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; public override void Initialize() { @@ -53,95 +60,135 @@ namespace Content.Server.Zombies /// private void OnDamageChanged(EntityUid uid, ZombifyOnDeathComponent component, MobStateChangedEvent args) { - if (!TryComp(uid, out var mobstate)) - return; - - if (mobstate.IsDead() || - mobstate.IsCritical()) + if (args.CurrentMobState.IsDead() || + args.CurrentMobState.IsCritical()) { ZombifyEntity(uid); } } /// - /// This is the general purpose function to call if you want to zombify an entity. - /// It handles both humanoid and nonhumanoid transformation. + /// This is the general purpose function to call if you want to zombify an entity. + /// It handles both humanoid and nonhumanoid transformation and everything should be called through it. /// /// the entity being zombified + /// + /// ALRIGHT BIG BOY. YOU'VE COME TO THE LAYER OF THE BEAST. THIS IS YOUR WARNING. + /// This function is the god function for zombie stuff, and it is cursed. I have + /// attempted to label everything thouroughly for your sanity. I have attempted to + /// rewrite this, but this is how it shall lie eternal. Turn back now. + /// -emo + /// public void ZombifyEntity(EntityUid target) { + //Don't zombfiy zombies if (HasComp(target)) return; - _disease.CureAllDiseases(target); + //you're a real zombie now, son. + var zombiecomp = AddComp(target); + + ///we need to basically remove all of these because zombies shouldn't + ///get diseases, breath, be thirst, be hungry, or die in space RemComp(target); RemComp(target); RemComp(target); RemComp(target); RemComp(target); - var zombiecomp = EnsureComp(target); - if (TryComp(target, out var huApComp)) - { - var appearance = huApComp.Appearance; - _sharedHuApp.UpdateAppearance(target, appearance.WithSkinColor(zombiecomp.SkinColor), huApComp); - _sharedHuApp.ForceAppearanceUpdate(target, huApComp); - } - - if (!HasComp(target)) - MakeSentientCommand.MakeSentient(target, EntityManager); - + //funny voice EnsureComp(target).Accent = "zombie"; + EnsureComp(target); - //funny add delet go brrr + ///This is needed for stupid entities that fuck up combat mode component + ///in an attempt to make an entity not attack. This is the easiest way to do it. RemComp(target); AddComp(target); + ///This is the actual damage of the zombie. We assign the visual appearance + ///and range here because of stuff we'll find out later var melee = EnsureComp(target); melee.Arc = zombiecomp.AttackArc; melee.ClickArc = zombiecomp.AttackArc; - //lord forgive me for the hardcoded damage - DamageSpecifier dspec = new(); - dspec.DamageDict.Add("Slash", 13); - dspec.DamageDict.Add("Piercing", 7); - melee.Damage = dspec; + melee.Range = 0.75f; + //We have specific stuff for humanoid zombies because they matter more + if (TryComp(target, out var huApComp)) //huapcomp + { + //this bs is done because you can't directly update humanoid appearances + var appearance = huApComp.Appearance; + appearance = appearance.WithSkinColor(zombiecomp.SkinColor).WithEyeColor(zombiecomp.EyeColor); + _sharedHuApp.UpdateAppearance(target, appearance, huApComp); + _sharedHuApp.ForceAppearanceUpdate(target, huApComp); + + ///This is done here because non-humanoids shouldn't get baller damage + ///lord forgive me for the hardcoded damage + DamageSpecifier dspec = new(); + dspec.DamageDict.Add("Slash", 13); + dspec.DamageDict.Add("Piercing", 7); + melee.Damage = dspec; + } + + //The zombie gets the assigned damage weaknesses and strengths _damageable.SetDamageModifierSetId(target, "Zombie"); + + ///This makes it so the zombie doesn't take bloodloss damage. + ///NOTE: they are supposed to bleed, just not take damage _bloodstream.SetBloodLossThreshold(target, 0f); - _popupSystem.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, Filter.Pvs(target)); + //This is specifically here to combat insuls, because frying zombies on grilles is funny as shit. _serverInventory.TryUnequip(target, "gloves", true, true); + //popup + _popupSystem.PopupEntity(Loc.GetString("zombie-transform", ("target", target)), target, Filter.Pvs(target)); + + //Make it sentient if it's an animal or something + if (!HasComp(target)) //this component is cursed and fucks shit up + MakeSentientCommand.MakeSentient(target, EntityManager); + + //Make the zombie not die in the cold. Good for space zombies if (TryComp(target, out var tempComp)) tempComp.ColdDamage.ClampMax(0); + //Heals the zombie from all the damage it took while human if (TryComp(target, out var damageablecomp)) _damageable.SetAllDamage(damageablecomp, 0); + //gives it the funny "Zombie ___" name. if (TryComp(target, out var meta)) meta.EntityName = Loc.GetString("zombie-name-prefix", ("target", meta.EntityName)); + //He's gotta have a mind var mindcomp = EnsureComp(target); if (mindcomp.Mind != null && mindcomp.Mind.TryGetSession(out var session)) - _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); + { + //Zombie role for player manifest + mindcomp.Mind.AddRole(new TraitorRole(mindcomp.Mind, _proto.Index(zombiecomp.ZombieRoleId))); + //Greeting message for new bebe zombers + _chatMan.DispatchServerMessage(session, Loc.GetString("zombie-infection-greeting")); + } if (!HasComp(target) && !mindcomp.HasMind) //this specific component gives build test trouble so pop off, ig { + //yet more hardcoding. Visit zombie.ftl for more information. EntityManager.EnsureComponent(target, out var ghostcomp); ghostcomp.RoleName = Loc.GetString("zombie-generic"); ghostcomp.RoleDescription = Loc.GetString("zombie-role-desc"); ghostcomp.RoleRules = Loc.GetString("zombie-role-rules"); } + ///Goes through every hand, drops the items in it, then removes the hand + ///may become the source of various bugs. foreach (var hand in _sharedHands.EnumerateHands(target)) { _sharedHands.SetActiveHand(target, hand); - hand.Container?.EmptyContainer(); + _sharedHands.DoDrop(target, hand); _sharedHands.RemoveHand(target, hand.Name); } RemComp(target); - EnsureComp(target); + //zombie gamemode stuff + RaiseLocalEvent(new EntityZombifiedEvent(target)); } } } diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 68724b75d5..d2652756a2 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -299,6 +299,19 @@ namespace Content.Shared.CCVar public static readonly CVarDef NukeopsPlayersPerOp = CVarDef.Create("nukeops.players_per_op", 5); + /* + * Zombie + */ + + public static readonly CVarDef ZombieMinPlayers = + CVarDef.Create("zombie.min_players", 20); + + public static readonly CVarDef ZombieMaxInitialInfected = + CVarDef.Create("zombie.max_initial_infected", 6); + + public static readonly CVarDef ZombiePlayersPerInfected = + CVarDef.Create("zombie.players_per_infected", 10); + /* * Pirates */ diff --git a/Content.Shared/Inventory/InventorySystem.Equip.cs b/Content.Shared/Inventory/InventorySystem.Equip.cs index 88498a3ffe..6dc5270a0e 100644 --- a/Content.Shared/Inventory/InventorySystem.Equip.cs +++ b/Content.Shared/Inventory/InventorySystem.Equip.cs @@ -25,7 +25,6 @@ public abstract partial class InventorySystem [Dependency] private readonly MovementSpeedModifierSystem _movementSpeed = default!; [Dependency] private readonly SharedInteractionSystem _interactionSystem = default!; [Dependency] private readonly SharedContainerSystem _containerSystem = default!; - [Dependency] private readonly ActionBlockerSystem _actionBlockerSystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly INetManager _netMan = default!; diff --git a/Content.Shared/Zombies/ZombieEvents.cs b/Content.Shared/Zombies/ZombieEvents.cs new file mode 100644 index 0000000000..9fa0e3c728 --- /dev/null +++ b/Content.Shared/Zombies/ZombieEvents.cs @@ -0,0 +1,25 @@ +using Content.Shared.Actions; + +namespace Content.Shared.Zombies; + +/// +/// Event that is broadcast whenever an entity is zombified. +/// Used by the zombie gamemode to track total infections. +/// +public readonly struct EntityZombifiedEvent +{ + /// + /// The entity that was zombified. + /// + public readonly EntityUid Target; + + public EntityZombifiedEvent(EntityUid target) + { + Target = target; + } +}; + +/// +/// Event raised when a player zombifies themself using the "turn" action +/// +public sealed class ZombifySelfActionEvent : InstantActionEvent { }; diff --git a/Resources/Locale/en-US/actions/actions/zombie.ftl b/Resources/Locale/en-US/actions/actions/zombie.ftl new file mode 100644 index 0000000000..3ae2d928e9 --- /dev/null +++ b/Resources/Locale/en-US/actions/actions/zombie.ftl @@ -0,0 +1,2 @@ +turn-undead-action-name = Turn Undead +turn-undead-action-description = Succumb to your infection and become a zombie. \ No newline at end of file diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-zombies.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-zombies.ftl new file mode 100644 index 0000000000..d7e487ba52 --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-zombies.ftl @@ -0,0 +1,27 @@ +zombie-title = Zombies +zombie-description = A virus able to animate the dead has been unleashed unto the station! Work with your crewmates to contain the outbreak and survive. + +zombie-not-enough-ready-players = Not enough players readied up for the game! There were {$readyPlayersCount} players readied up out of {$minimumPlayers} needed. Can't start Zombies. +zombie-no-one-ready = No players readied up! Can't start Zombies. + +zombie-patientzero-role-greeting = You are patient 0. Hide your infection, get supplies, and be prepared to turn once you die. + +zombie-alone = You feel entirely alone. + +zombie-round-end-initial-count = {$initialCount -> + [one] There was one initial infected: + *[other] There were {$initialCount} initial infected: +} +zombie-round-end-user-was-initial = - [color=plum]{$name}[/color] ([color=gray]{$username}[/color]) was one of the initial infected. + +zombie-round-end-amount-none = [color=green]All of the zombies were eradicated![/color] +zombie-round-end-amount-low = [color=green]Almost all of the zombies were exterminated.[/color] +zombie-round-end-amount-medium = [color=yellow]{$percent}% of the crew were turned into zombies.[/color] +zombie-round-end-amount-high = [color=crimson]{$percent}% of the crew were turned into zombies.[/color] +zombie-round-end-amount-all = [color=darkred]The entire crew became zombies![/color] + +zombie-round-end-survivor-count = {$count -> + [one] There was only one survivor left: + *[other] There were only {$count} survivors left: +} +zombie-round-end-user-was-survivor = - [color=White]{$name}[/color] ([color=gray]{$username}[/color]) survived the outbreak. \ No newline at end of file diff --git a/Resources/Locale/en-US/zombies/zombie.ftl b/Resources/Locale/en-US/zombies/zombie.ftl index 606717ed35..f534a962e0 100644 --- a/Resources/Locale/en-US/zombies/zombie.ftl +++ b/Resources/Locale/en-US/zombies/zombie.ftl @@ -2,6 +2,6 @@ zombie-transform = {CAPITALIZE(THE($target))} turned into a zombie! zombie-infection-greeting = You have become a zombie. Your goal is to seek out the living and to try to infect them. Work together with your fellow zombies to overpower the remaining crewmates. zombie-generic = zombie -zombie-name-prefix = zombified {$target} +zombie-name-prefix = Zombified {$target} zombie-role-desc = A malevolent creature of the dead. zombie-role-rules = You are an antagonist. Search out the living and bite them in order to infect them and turn them into zombies. Work together with other the zombies to overtake the station. diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index c6aa383550..29a85ce3e3 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -7,6 +7,13 @@ serverEvent: !type:ScreamActionEvent checkCanInteract: false +- type: instantAction + id: TurnUndead + name: turn-undead-action-name + description: turn-undead-action-description + icon: Interface/Actions/zombie-turn.png + event: !type:ZombifySelfActionEvent + - type: instantAction id: ToggleLight name: action-name-toggle-light diff --git a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml index 51cef5a3fc..826c86dd07 100644 --- a/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml +++ b/Resources/Prototypes/Catalog/Fills/Backpacks/duffelbag.yml @@ -13,6 +13,22 @@ - id: Retractor - id: Scalpel +- type: entity + id: ClothingBackpackDuffelCBURN + parent: ClothingBackpackDuffel + name: CBURN duffel bag + description: A duffel bag containing a variety of biological containment equipment. + components: + - type: StorageFill + contents: + - id: WeaponShotgunDoubleBarreled + - id: BoxShotgunFlare + amount: 2 + - id: PillRomerol + amount: 5 + - id: GrenadeFlash + amount: 2 + - type: entity parent: ClothingBackpackDuffelSyndicateMedical id: ClothingBackpackDuffelSyndicateFilledMedical diff --git a/Resources/Prototypes/Damage/modifier_sets.yml b/Resources/Prototypes/Damage/modifier_sets.yml index c446cfe876..e24b8e9196 100644 --- a/Resources/Prototypes/Damage/modifier_sets.yml +++ b/Resources/Prototypes/Damage/modifier_sets.yml @@ -117,8 +117,11 @@ Piercing: 0.9 Shock: 1.5 Cold: 0.2 - Heat: 3.0 + Heat: 2.5 Poison: 0.0 + flatReductions: #offsets rotting damage + Blunt: 0.3 + Cellular: 0.3 # immune to everything except physical and heat damage - type: damageModifierSet diff --git a/Resources/Prototypes/Diseases/zombie.yml b/Resources/Prototypes/Diseases/zombie.yml index cbdca7bbaf..086685c02d 100644 --- a/Resources/Prototypes/Diseases/zombie.yml +++ b/Resources/Prototypes/Diseases/zombie.yml @@ -23,4 +23,13 @@ cures: - !type:DiseaseReagentCure reagent: Romerol - min: 5 \ No newline at end of file + min: 5 + +- type: disease + id: PassiveZombieVirus + name: Zombie Virus + infectious: false + cureResist: 1 #no cure. Death is your cure. + effects: + - !type:DiseaseAddComponent + comp: ZombifyOnDeath \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml index 2cea9b1e36..6945f861ac 100644 --- a/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml +++ b/Resources/Prototypes/Entities/Clothing/Head/hardsuit-helmets.yml @@ -405,6 +405,56 @@ Heat: 0.2 Radiation: 0.5 +- type: entity + parent: ClothingHeadHardsuitWithLightBase + id: ClothingHeadHelmetCBURN + noSpawn: true + name: syndicate elite helmet + description: A pressure resistant and fireproof hood worn by special cleanup units. + components: + - type: Sprite + netsync: false + sprite: Clothing/Head/Hardsuits/cburn.rsi + layers: + - state: icon + - state: icon-unshaded + shader: unshaded + - state: light-overlay + visible: false + shader: unshaded + map: [ "light" ] + - type: Clothing + clothingVisuals: + head: + - state: equipped-head + - state: equipped-head-unshaded + shader: unshaded + inhandVisuals: + left: + - state: inhand-left + - state: inhand-left-unshaded + shader: unshaded + right: + - state: inhand-right + - state: inhand-right-unshaded + shader: unshaded + - type: PointLight + color: orange + - type: PressureProtection + highPressureMultiplier: 0.08 + lowPressureMultiplier: 1000 + - type: TemperatureProtection + coefficient: 0.005 + - type: Armor + modifiers: + coefficients: + Blunt: 0.9 + Slash: 0.9 + Piercing: 0.9 + Heat: 0.1 + Shock: 0.1 + Cold: 0.2 + Radiation: 0.2 #ERT - type: entity diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml index 0977bd1a12..76c03baeaf 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/hardsuits.yml @@ -571,6 +571,38 @@ - type: ToggleableClothing clothingPrototype: ClothingHeadHelmetHardsuitSyndieElite +- type: entity + parent: ClothingOuterHardsuitBase + id: ClothingOuterHardsuitCBURN + name: CBURN exosuit + description: A lightweight yet strong exosuit used for special cleanup operations. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Hardsuits/cburn.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Hardsuits/cburn.rsi + - type: PressureProtection + highPressureMultiplier: 0.02 + lowPressureMultiplier: 1000 + - type: ClothingSpeedModifier + walkModifier: 1.0 + sprintModifier: 1.0 + - type: TemperatureProtection + coefficient: 0.001 + - type: Armor + modifiers: + coefficients: + Blunt: 0.7 + Slash: 0.7 + Piercing: 0.6 + Heat: 0.05 + Cold: 0.1 + Shock: 0.1 + Radiation: 0.1 + - type: ExplosionResistance + resistance: 0.7 + - type: ToggleableClothing + clothingPrototype: ClothingHeadHelmetCBURN #ERT - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml index 6916e21264..b7471a3a93 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/human.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/human.yml @@ -34,6 +34,20 @@ - Idle - Spirate +- type: entity + parent: MobHuman + id: MobCBURNUnit + name: CBURN Agent + description: A miserable pile of secrets + components: + - type: RandomHumanoidAppearance + - type: Loadout + prototype: CBURNGear + - type: GhostTakeoverAvailable + makeSentient: false + name: CBURN Agent + description: A highly trained Centcomm agent, capable of dealing with various threats. + - type: entity parent: MobHumanBase suffix: Dead diff --git a/Resources/Prototypes/Roles/Antags/zombie.yml b/Resources/Prototypes/Roles/Antags/zombie.yml new file mode 100644 index 0000000000..3da86c0555 --- /dev/null +++ b/Resources/Prototypes/Roles/Antags/zombie.yml @@ -0,0 +1,13 @@ +- type: antag + id: InitialInfected + name: "Initial Infected" + antagonist: true + setPreference: true + objective: "Once you turn, infect as many other crew members as possible" + +- type: antag + id: Zombie + name: "Zombie" + antagonist: true + setPreference: false + objective: "Turn as many humans as possible into zombies." \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml index 3ee81e3479..027b9ff60a 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml @@ -137,3 +137,24 @@ innerclothingskirt: ClothingUniformJumpskirtOperative satchel: ClothingBackpackDuffelSyndicateOperativeMedic duffelbag: ClothingBackpackDuffelSyndicateOperativeMedic + +#CBURN Unit Gear - Full Kit +- type: startingGear + id: CBURNGear + equipment: + jumpsuit: ClothingUniformJumpsuitColorBrown + back: ClothingBackpackDuffelCBURN + mask: ClothingMaskGas + eyes: ClothingEyesGlassesSecurity + ears: ClothingHeadsetService + gloves: ClothingHandsGlovesFingerless + outerClothing: ClothingOuterHardsuitCBURN + shoes: ClothingShoesBootsJack + id: CentcomPDA + pocket1: CombatKnife + pocket2: WeaponLaserGun + suitstorage: YellowOxygenTankFilled + belt: ClothingBeltBandolier + innerclothingskirt: ClothingUniformJumpsuitColorBrown + satchel: ClothingBackpackDuffelCBURN + duffelbag: ClothingBackpackDuffelCBURN \ No newline at end of file diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index 21c892fda3..601b14f1a8 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -80,6 +80,20 @@ rules: - Nukeops +- type: gamePreset + id: Zombie + alias: + - zombie + - zombies + - Zombies + - zz14 + - zomber + name: zombie-title + description: zombie-description + showInVote: true + rules: + - Zombie + - type: gamePreset id: Pirates alias: diff --git a/Resources/Prototypes/game_rules.yml b/Resources/Prototypes/game_rules.yml index 4334e7667d..849f5715c7 100644 --- a/Resources/Prototypes/game_rules.yml +++ b/Resources/Prototypes/game_rules.yml @@ -59,3 +59,9 @@ config: !type:GenericGameRuleConfiguration id: Secret + +- type: gameRule + id: Zombie + config: + !type:GenericGameRuleConfiguration + id: Zombie diff --git a/Resources/Prototypes/secret_weights.yml b/Resources/Prototypes/secret_weights.yml index 6866e6875c..fb64d74325 100644 --- a/Resources/Prototypes/secret_weights.yml +++ b/Resources/Prototypes/secret_weights.yml @@ -3,4 +3,5 @@ weights: Extended: 0.25 Nukeops: 0.25 - Traitor: 0.75 \ No newline at end of file + Traitor: 0.75 + Zombie: 0.05 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-unshaded.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/equipped-head-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..7fd9a0b229c3448411cd7990d6404941bbb08880 GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D$|Vmw_OLn`LH zJ!{C@V8GyVQK>2N_Kd_z48qn7m}zv;F>G0fqmKeuyvRMDGzj9|fi&lv??3hlFvDLVqPx%DM>^@RCt{2+P_O%Q4|O8ZzUZHmaZ|sge(dLDIIFW!O5jd<_!J;Zz(Q9Mn`E7 z+}yf!aILFO9RwW&8#+WN84{m)A{QbJWQ>E}@ZR$UJtr^EA>YrI`||EN_was%+yl^b zd!spNXR9&@k5y|2s<~b}zWAh_t;!&q%_CPR4#%!qO}K75ks`z$e@2spKX}yVNsBYS zr~tP5QL@>5NyJY{zHE%a^2!My6jxX*Hh-y$sx<4QsGgm08x5rie0Z|K1 zqCdc&LzuP*s@>tMRx?lp+b?kA-D;*2`Sb4Jv7CxJY=XC%8Z-w)T3Q+rT6nM>SIvWFj zA(9ea_+IV1`t_^21uWZ)U<(1^{ihbg!ik88h=_=Yh=^!9-2?hz1wgJJTsi;%002ov JPDHLkV1gY18@T`g literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/icon-flash.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/icon-flash.png new file mode 100644 index 0000000000000000000000000000000000000000..d37c1afe3e2fa59c546e099719f134de9d9d77b6 GIT binary patch literal 652 zcmV;70(1R|P)Px%MoC0LR9J=Wl}}I8a2Urw>)Ngx5VHW=VEi3n8sou(Q4b~~@#rhy75EBBd-cKoS_v6@9-U5mulSn3p zd~VleZZeJ2>Cx?U0giRu4%~=B6VP?rtki6Z%^Coi>@@J_Ex5e6B9(;N!9HfQMx|yW z(p(q-pnYnyyLU)tNY=l^dYaV=Nj^d7K6 e4T!EB1_s4CE+&a*IT}DC89ZJ6T-G@yGywqk4=nKj literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/icon.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..adfb7557f7e7d85210cbcd1dbc25d8361a2c167f GIT binary patch literal 408 zcmV;J0cZY+P)Px$Qb|NXR9J=WmAy{FP#A_EO(&yaw56a4lY=fU3=T{#-T{+WLEsG1k)qbppzDE=V+7Mdiyzt*ZZ0Z02x#6p7g#i>TWdJ$;RdU^G?n{~_ zOVtSJh`Uc7Ld2PM+jgiQ)rg`%xu-I~a5yCN)9`a=%+|ie&Or%)@yiFFZ&R*g2urEA zZ~#araND`(QUDJC?@NvemD3Y?y~n=+061$L>uxCqARNsIeGkb0+(O?=uhFb{0D!yK z7<+p}LClX=FVOh{`w2^)qAZUn zA)oC!dUqa4osz!v`OVSo?bYx9&wkSS{bA+*Juiy299-bwWa3M$JFBMFrYAGrdu-iz z{{7nPbDb;R^5yxjT3FpBD(EQiWjZk& zDCgMkDko>4&XCHreCF(0@sEcXmPk)H1}CQdV0gAQ^qS#|j0r!a55R@N3YO*aeONBR zqI2+la+^>+vqVD)^QAAg+-IV$+-`le+5J&|gCS$jZ{0+xV=Nb9Zie1U_hI-sJ$q^G zF&3a%3$)4@OQhNBR)@*`yii;exwPuzOy;^2uJmN}rib&@|7n$(E1datdFmy*WG0X0 nIUrpd(!TdvatmJE|C6c6(ue!v6Wd&195Hyh`njxgN@xNAxeCiF literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..43f27a890f29dfdbc6e08f9a339965c03d33e4e8 GIT binary patch literal 519 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%z4|=*dhE&XX zd&|)Ku!9Kuhy9D17wpKI>~><2f=JQikq&ha$;zl+y85gwH@bD))#BzL{6V_ zle?w$IMrvxoHO5R*W~9s{PW0YOKy3!aPP6)H5CtDZ+O2^R(#vzzMkm|w=f)d-zWZA z_KeQiN00x>eqFuN!Kv_(d+*9cdl(o#+{$|NfrsJNedXMQLc6j(0&y8RH&#xZ_KAt( zVPbl=X^~A{&6WLo`##HGsF1&~KdDeZk^Si2ytQ>kU#b^xJAEODL19PIuDm3ew}A8{ z!*7LOA9M@dbbs$Amaw5+!S{c|w0{lzFWfgyuwM7&b@5{^gEj3A+c=JiK6v+$*PQ=s z0DsdP?UKh_1u=0BhW9ypzD^Q9)cpQ;=d;U~M<~W0t|w)z4*} HQ$iB}%bM_> literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right-unshaded.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right-unshaded.png new file mode 100644 index 0000000000000000000000000000000000000000..118914c8d3f08f04301483e8c76c9a603d3f5d17 GIT binary patch literal 415 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=jKx9jP7LeL$-D%zBRyRlLn`LH zy=mxu*nxrdfw(8am)Wq literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..602020b276949e62f04118d9cd9c686302f6844e GIT binary patch literal 527 zcmV+q0`UEbP)Px$$w@>(RCt{2+Obcr(tn0u8;yIEIPZ0abR+Bac1BT=*B|A=tx3L+}K4v#8y_wW=t!P&_%IZjts)<41X6D_7amoJtmUo1)UXT$-1 zpk1ER_Wlli_CBcdycIZMn#ppm${s2J5z%z=M(4LgM6;if8PQ%&L%d|T?l3eKb!&tQ z;JQOPtsMO7q-hlOw2yjzMg9-)SIm@GVBM~Hm!0d#oU-2|clKAzgo_6N0000000000 z09Y^f=WcPK!PHOZu|kV$@Fw*Sh@|}itH>G>lFjCp zh2Z@Jj?<;bmsgGP{+?0++E@31*IeDMdHzC}DAp~&@n!>F2x3f6DgXdT#y12LzG@SX R$Z`Mx002ovPDHLkV1i?J>;wP+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/light-overlay.png b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/light-overlay.png new file mode 100644 index 0000000000000000000000000000000000000000..ff7347fe6ee1df2d09efdef0a797c4174822c416 GIT binary patch literal 333 zcmV-T0kZyyP)Px$2T4RhR9J=Wl)(zZFbszOWxMGPg5bfE@8I1%`n>xN17QcDutW8@>cIp;-E4Og z%nzk&mL^|ATEJv7nf_fx%AsxB58LUq-c9+Q&-YtwYntY3{3-=7lX%PXhUbC*&w|V; zSpm`WdD%@Qu6gzv1b{1wV-`dfz(n#|twoMO7C|HdRB}lS0Q)h7hxRLpRsecl3P=(n zw+Hm8>oJ6(Bsyq^F@%n7UYT7ga4x{0zFkSQA4BN016M$7KH4)YN;z;xI}Ui|;C)F{ z>4A6oJ^Ks*T+VGs0z?qt;2!VOIvyRO2F$&RSpX4)R6{_#TDv95frSr|1eON}QIjo6 fY%-Zlrk~LZL(X#79wy%000000NkvXXu0mjfQca1O literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/meta.json b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/meta.json new file mode 100644 index 0000000000..16f509fa99 --- /dev/null +++ b/Resources/Textures/Clothing/Head/Hardsuits/cburn.rsi/meta.json @@ -0,0 +1,47 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Made by EmoGarbage404", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "icon-unshaded" + }, + { + "name": "icon-flash" + }, + { + "name": "light-overlay" + }, + { + "name": "equipped-head", + "directions": 4 + }, + { + "name": "equipped-head-unshaded", + "directions": 4 + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-left-unshaded", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "inhand-right-unshaded", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ed8fa9ff56f3e7b384734c8121736a80eb526e GIT binary patch literal 2333 zcmV+&3F7vNP)EX>4Tx04R}tkv&MmKpe$iQ?()$1v`j1WN4i%h>AFB6^c+H)C#RSm|XfHG-*gu zTpR`0f`cE6RR;BL;+afkw@7zKZ37qAElt@2E_Z;zCqp)6R|?V;3I*W(jJ_!c4BP@et6p!-eVjf38R{x^0~{Oz zBSp$y^Lcl7dvE`qY4-O6ZWD5)cX)k&000M6Nklso+%PhH%ha@0>sl0>G4$OJuG+%&(9IjyO#t}ur(btGx7WRLK{fV*SKo2w z!TzY3S<3+cn#5+C(9Ijy&Ftns5;*3??K_XkZiK=9sQIzB$-2%mJ)lW!wtgS}JIkf- zDC5ZREwj5LgiJbx(Xj;le$ghH)mEXZ8V35}2(*MS`!eOaOBT~lOwAX}7w_%l*7rIQ z*R)k+(kTGIp`!$$+8`E0-=}us>Q-FBt5Pnvqk@ zmVPq12mn@NX*#{8_f=Ix#KmuRH)?7zQ59En<9n1`C>w@#z^n zm|KKl7yy8&*;k&f(|)VVmIGEF03<1hSWgEI9VLkMbU>1VR$i*_lp5a35JK?nH&<;9 z477ys^Zk3gML9xIR(4I7q#%6DimmZZp4(1Q11QQ01VKP=ce}Z?Zs5Xk37K>XftHY! zCzDR$tSI2=vVz|3c2f`p#}#k_)o1HOhaJ79P<^NDIRu2(Ftm*tXd5-~`$d}|?E#r| z%Blb%1b)A0JtMS+9aqGOK8vEPz%Yy*hUV4Zvj@-_7|2g7M_U^3bXmdr`wx&eYLJ+k zN0Zo$WaqM%Kh<|!jdY?=W>Z!r1rx#z2|&zXL|h%@0%yjU!}PA>;@$~!Er&wvnuKirR^8x)_2?kdb`_A zp*4(;wN1#Z}e2?Ka!$*@K`)vb|X;zVsDuji}T$2j)%ZRB4PdBek!vtzrds~I4u$*BO?HSp`oF& z=;!Yxz=IQa32<%W$H*f;k$V0VMl{lGyQ zf^9fkFCHT}&PW^29(rWfatC6F?G4|pPEL*Bcr!$W;+MDFVlrU!*N+U}GWXjEwDI&G zkN0+3_W~^;Bp%#DhU0jS>p?_CRai3$e-e8Jh7`S%vJR(v9Z*km2qy}8Oc-{)gps*fm z9=Hua8!rd~M#mD!q*EY-Sn&e;btuY~9cT%G5Mo8GQOAgzqj7#u#*v`SXFonYgUgXE zKRnM2%nMugpIuJsjE~&OW$<*&$WpWuHA~irb@(Bw}bk@^l1s9G>&~+WR_c2Ts z0M`EUdRN|l@I<7B&w`K-$%*xJVB+yh0D14!;Z2{VQ@9Lp`_3cJV1Lx4VQG>S1krRz zQm`W4p=2^?0zl}TXw#1Mjv&lOoGM*RGPN7HWSFlD){{(?oLc%Y27IH*si!Tz)2rL zRBi)+io^vJf@(+Qwq4_voZ#A_FG)cwE}+spdGZ9nR;)rrLu6RC$LdsybUKamZKogz zLP?DWN-zMs7dR+%T`$@Au7&{Xli%;SfZaGQ`f_Zn0vj*DOG!fj*g6D>#p3{A?x(j_ zmSxYeFLF6HR-sf}8^G?9=Zg!V@WcnOu(1ML`tJA#O_mx7Sp=$U00000NkvXXu0mjf Dvx-w9 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..8d7c6be6a656a20cbf99d19edc0e9022fb1ea8b5 GIT binary patch literal 1265 zcmVEX>4Tx04R}tkv&MmKpe$iQ?()$1&fF{WT;LSL`5963Pq?8YK2xEOfLNpnlvOS zE{=k0!NHHks)LKOt`4q(Aou~|=H{g6A|?JWDYS_3;J6>}?mh0_0Ya_BG^=e4&~)2O zCE{WxyCQ~O5k@aM(2tPJEMr!ZlJFg0_XzOyF2=L`&;2>N)ttqEfJi*U4AUlFC!X50 z4bJ<-5muB{;&b9rlP*a7$aTfzH_io@1)do()2TV)2(egbVWovx(bR}1iKD8fQ@)V# zSmnIMSu0goAMvPXS6bmWZkNfxsUB5&wg`Uwzx2Cnp`zgz>RKS{4P zwdfJhvkhEaH#KDsxZD8-o($QPT`5RY$mfCgGy0}1(02=Tt$MvR_Hp_Eq^Yaq4RCM> zj1(w)&F9^nt-bwwrqSOI3T<+IV#RYk0009hNkl(ERjs>`t5gW-Cgn=6&hmx)Sqoo^)o7qeeY^*NL$ODBJB1 zde_(H1gaKwE5(|fmz;3ShZ>x1OeZ_dQA`n2q8E;br3=j1OdzlD!Y3Lt5yx| zPJ1BdSY4nXgy4(MdIhPF&fMYa@4k)AF_HksIYZMlD%+dkTxNksAEvN7ZPJ-LQHPUX#r^5Mys;fvfBHP(x)Rrw zRCf0g_hx$zJV-4t^VTOlKgG;jp9iT00PfB9icqatfBJm#eW0?tcY#C*!SZs?TE{sf zm0ALz^Xm1$H4_QwhyC-POac4fSd6B71^CiqT|dV9E3RACrQ>)1 b1;@o-Ket`~k=83300000NkvXXu0mjf0gYo+ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/inhand-left.png b/Resources/Textures/Clothing/OuterClothing/Hardsuits/cburn.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..9071444e11964d9c0e0531f1d82376d24592a1c1 GIT binary patch literal 1247 zcmV<51R(o~P)EX>4Tx04R}tkv&MmKpe$iQ%j3f9oj*}AwzYtAS&XhRVYG*P%E_RU~>J0CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4RLxj8AiNQwVT3N2zhIPS;0dyl(!fY7Wm)$ADuRLwHd z$%L5At%|`{gb_hMLI}vr)aN8A1<&zy4NMaF7kRU=q9TikzAx^7CiitGsCp`Q^j$a~|LaquJ zITlcZ3fb|4|H1EW&En*Qn-op}ffw8U7y*L2K&xTf-^aGyIsyF8z?IhV*P6i0C+Urj z7CQn4wtDJa?(N?*?f!lM{sVG&@SkWa0009PNkl9@!kVL5ClOG1VIo4K@bE%5dJ5uID_T$ zc>o^XyWIpheC(vvzP^6*uGRj(&G`8EclCwoToXWRAKT6XaQ@O&E1%DQqv@8|4X8ZN zH9fCFsa$2i&Jt*!UBi7ij*B}ovEuzI&vVUZul#T7_iWGNj!al_8?Y+QV1?;c;~mE( zSb9gGeS)QTIF8G+SAOTouB5lq4T`fx0_{_rEn=q|q4ZIR)d1=hR=*Db=L0oIV1nxC3T}M!5f^z|nnsJ9YP;6d2pFH&h2Q zLn8no4+`6d0Y61xG=dViHtoWC|@DWy8^aafAF#>~9O zwL6cjnR$<*uJxjsK#vUY79pD+09vKDxUy#XM^gK#1GGm&ujq*Z%csMr(^sul`=aa> zu^Vu9@J30IXihG)JIIWdO;8bK@bE%5ClOGgq4VWRX9$*-b?%$ zQ5Q^mvm}-v#DG8DfIrPYVL&$-4A@z&-@F^CFNquhUw7WNvm8Hi zFw(MlA`PHkVeai4CJyZG`rJS=4FHrEX>4Tx04R}tkv&MmKpe$iQ%j3f9oj*}AwzYtAS&XhRVYG*P%E_RU~>J0CJjl7 zi=*ILaPVWX>fqw6tAnc`2!4RLxj8AiNQwVT3N2zhIPS;0dyl(!fY7Wm)$ADuRLwHd z$%L5At%|`{gb_hMLI}vr)aN8A1<&zy4NMaF7kRU=q9TikzAx^7CiitGsCp`Q^j$a~|LaquJ zITlcZ3fb|4|H1EW&En*Qn-op}ffw8U7y*L2K&xTf-^aGyIsyF8z?IhV*P6i0C+Urj z7CQn4wtDJa?(N?*?f!lM{sVG&@SkWa0009~Nkl1o^MNh?pUOWUr&{GcLVRMiy0kd6- z2VqMhVKeWtE+~ZXRu-p+^3}*E}CsW@q*fzj@|;-x=l|5JCtcgb+dqA%qY@ z93)=tN-C8C;L-g%5x@^WUGm)fn}6N&+~>~3#6%Aw?wre)UR;O(j0w>?0l<~3*S%CK z)s#8b_$TXsFGSielg$-*bpK9NFd^Tc80Oy1%MlF)+i#5r1S2&FwqkElN|Es6gqsDdwHzvc!Oy?`QTP4*J`2GC&bt{(${`G-%u@nZqjTC`urTFdycfcb|{ntt4mT>jH~h;P4CoYq6z=5Hzh zRQiuniYqtfJ*8B&9}S9(DY3Mc=f>T~-qKp0j48GKZp#AX-$25T1C?d|&E5prmru8oI%eo zgb+dqA%qY@h`rZWQ(cW(O?|b~>V{{lxxhxPZ;2fsO*2C~vE7l`%UkDr@!y|x3)o4) zIx4^*OpxqUM_piUb|$)g>l$&LKPx&6iGxuR9J=WmceTqM-;|?stxNz$SlUim`Ig4oR7^+@*Mc$;TBgFF?=28*+PUW zdMIEFsIn#Vkch(-I|AkJAIsRxBmiGce@?a#QCxckz|TdGD#VIJ>jkc88WaU@2w_ zVxv~83B0X?rTcee>HeK-orKtGeczYa*;$@Gd&HaLH(k4xw3zwiF5BDNR4Nr?-dHK{ zeP5JP0GzcPKK6k?Ghf0m?3<2Q zDTty7fSsKkfUXGr)OJ131Oo-=-S?6@j?*3Q@9$q~kD>_A^9H8EC<4u96964fy;FdR ziHYu5+x0xXt{(>0K%>#1(P)_HdFHl>TTppWYy$FT%l2A%hMz23F;3Ngi%?}XZU7>0>n z5&x-x_kj;nz`wOvlh%6?v5WAs)_m1=4Kc4|kE&Gx`10O8Y}*E4cXv0D$z%lJ=;(;= z*46;HJ3s$^FQ=7qSwuu$?Cr^u%}q(AQlcGaG8svwQu1VTQ(o-tiHJz0TsFE98LKQ5 k3YQWVi^W90Z-aRM22hJ#dwqu6A^-pY07*qoM6N<$f}vQPng9R* literal 0 HcmV?d00001