From aa8e31fa7ebe9b3ac0ef15975c1bf1f2eeb018fa Mon Sep 17 00:00:00 2001 From: rhailrake <49613070+rhailrake@users.noreply.github.com> Date: Wed, 31 Jan 2024 14:01:35 +0000 Subject: [PATCH] - add: Changeling antagonist (#2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Changeling WIP * UI * Pointers fix * Moved out abilities * Regenerate ability * Fixed Regenerate ability Prevent ghosting while regenerating * Cleanup * Base lesser form * Finished Lesser Form && Transform * Transform Sting * Blind Sting * Mute Sting Added OnExamine on absorbed human * Hallucination Sting Changeling Absorb and transfer absorbed entities to absorber * Cryogenic Sting * Adrenaline Sacs * Transform now uses Polymorph * Armblade, Shield, Armor * Tentacle Arm ability Tentacle Gun system * WIP with bugs * WiP bugs * fix implant transfer * Fixed bugs with shop transfer and actions transfer * Just in case * Vi sitter i ventrilo och spelar DotA * Fixes and proper LesserForm tracking * !!!!! * Fixed empty buttons * WIP Gamerule Ready - shop * nerf stun time cause its sucks * cleaning * just in case * Absorb DNA Objective. * Partial objectives with bugs * fix * fix pointer * Changeling objectives * Changeling objectives №2 * Admin verb, game rule * Fixed empty list check Icons for objectives * Changeling chat, changeling names etc. * fix some merge errors * - fix: Fixed all bugs with changeling --------- Co-authored-by: Y-Parvus Co-authored-by: Y-Parvus <61109031+Y-Parvus@users.noreply.github.com> Co-authored-by: HitPanda <104197232+EnefFlow@users.noreply.github.com> Co-authored-by: EnefFlow --- Content.Client/Chat/Managers/ChatManager.cs | 30 +- .../Miracle/Changeling/TentacleGun.cs | 8 + .../ListViewChangelingSelectorWindow.xaml | 9 + .../ListViewChangelingSelectorWindow.xaml.cs | 33 + .../ListViewSelector/ListViewSelectorBUI.cs | 51 + .../TransformStingSelectorWindow.xaml | 9 + .../TransformStingSelectorWindow.xaml.cs | 33 + .../TransformStingUI/TransformStingUIBUI.cs | 51 + .../Systems/Chat/ChatUIController.cs | 16 +- .../Chat/Controls/ChannelFilterPopup.xaml.cs | 1 + .../Chat/Controls/ChannelSelectorButton.cs | 1 + .../Chat/Controls/ChannelSelectorPopup.cs | 1 + .../Systems/AdminVerbSystem.Antags.cs | 21 + .../Changeling/ChangelingRoleComponent.cs | 8 + .../Changeling/ChangelingRuleComponent.cs | 35 + .../Changeling/ChangelingRuleSystem.cs | 275 ++++++ .../Changeling/ChangelingSystem.Abilities.cs | 879 ++++++++++++++++++ .../Changeling/ChangelingSystem.Shop.cs | 24 + Content.Server/Changeling/ChangelingSystem.cs | 81 ++ .../Objectives/ChangelingConditionsSystem.cs | 236 +++++ .../AbsorbChangelingConditionComponent.cs | 6 + .../Components/AbsorbDnaConditionComponent.cs | 7 + .../AbsorbMoreConditionComponent.cs | 6 + .../EscapeWithIdentityConditionComponent.cs | 7 + .../PickRandomChangelingComponent.cs | 7 + .../Components/PickRandomIdentityComponent.cs | 7 + Content.Server/Changeling/TentacleGun.cs | 8 + .../Changeling/UncloneableComponent.cs | 9 + .../Chat/Commands/ChangelingChatCommand.cs | 43 + Content.Server/Chat/SuicideSystem.cs | 6 + Content.Server/Chat/Systems/ChatSystem.cs | 36 + Content.Server/Cloning/CloningSystem.cs | 14 + Content.Server/Content.Server.csproj | 1 - .../GameTicking/GameTicker.GamePreset.cs | 6 + .../Actions/ActionContainerSystem.cs | 26 + Content.Shared/Alert/AlertType.cs | 1 + .../Changeling/AbsorbedComponent.cs | 10 + .../Changeling/ChangelingComponent.cs | 70 ++ .../Changeling/ChangelingNameGenerator.cs | 63 ++ Content.Shared/Changeling/ChemicalsSystem.cs | 88 ++ .../LesserFormRestrictedComponent.cs | 8 + Content.Shared/Changeling/SharedChangeling.cs | 105 +++ .../Changeling/SharedChangelingChat.cs | 33 + .../Changeling/SharedTentacleGun.cs | 155 +++ .../Changeling/TentacleGunComponent.cs | 8 + .../Changeling/TentacleProjectileComponent.cs | 9 + Content.Shared/Chat/ChatChannel.cs | 11 +- Content.Shared/Chat/ChatChannelExtensions.cs | 1 + Content.Shared/Chat/ChatSelectChannel.cs | 2 + Content.Shared/Chat/SharedChatSystem.cs | 1 + .../Components/BlurryVisionComponent.cs | 6 +- .../Implants/Components/ImplantedComponent.cs | 1 + .../Implants/SharedSubdermalImplantSystem.cs | 45 +- Content.Shared/Input/ContentKeyFunctions.cs | 1 + Content.Shared/IoC/SharedContentIoC.cs | 5 +- Content.Shared/Miracle/UI/ListViewBUI.cs | 31 + .../Miracle/UI/TransformStingBUI.cs | 35 + .../Weapons/Ranged/Components/GunComponent.cs | 5 + .../RechargeBasicEntityAmmoComponent.cs | 6 + .../Systems/RechargeBasicEntityAmmoSystem.cs | 3 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 4 + .../Audio/Ambience/Antag/changeling_start.ogg | Bin 0 -> 47676 bytes Resources/Locale/en-US/Miracle/changeling.ftl | 3 + .../Locale/en-US/administration/antag.ftl | 4 +- Resources/Locale/en-US/alerts/alerts.ftl | 3 + .../game-presets/preset-changeling.ftl | 13 + .../objectives/conditions/absorb-dna.ftl | 4 + .../Locale/en-US/prototypes/roles/antags.ftl | 3 + Resources/Locale/en-US/store/categories.ftl | 4 +- Resources/Locale/en-US/store/currency.ftl | 2 +- Resources/Locale/en-US/weapons/ranged/gun.ftl | 2 + Resources/Prototypes/Actions/changeling.yml | 208 +++++ Resources/Prototypes/Actions/types.yml | 1 + Resources/Prototypes/Alerts/alerts.yml | 1 + Resources/Prototypes/Alerts/changeling.yml | 23 + .../Prototypes/Catalog/changeling_catalog.yml | 158 ++++ .../Entities/Clothing/OuterClothing/armor.yml | 15 + .../Prototypes/Entities/Mobs/NPCs/animals.yml | 2 + .../Prototypes/Entities/Mobs/Species/base.yml | 4 + .../Objects/Misc/subdermal_implants.yml | 19 +- .../Entities/Objects/Shields/shields.yml | 42 + .../Weapons/Guns/Launchers/launchers.yml | 37 + .../Weapons/Guns/Projectiles/projectiles.yml | 39 + .../Objects/Weapons/Melee/armblade.yml | 3 + Resources/Prototypes/GameRules/roundstart.yml | 9 + .../Prototypes/Objectives/changeling.yml | 223 +++++ .../Prototypes/Objectives/objectiveGroups.yml | 31 +- Resources/Prototypes/Polymorphs/polymorph.yml | 90 ++ .../Prototypes/Roles/Antags/changeling.yml | 6 + Resources/Prototypes/Store/categories.yml | 15 + Resources/Prototypes/Store/currency.yml | 5 + Resources/Prototypes/Store/presets.yml | 10 + Resources/Prototypes/game_presets.yml | 11 + Resources/Prototypes/secret_weights.yml | 2 +- Resources/Prototypes/status_effects.yml | 5 +- .../Actions/changeling.rsi/absorb.png | Bin 0 -> 231 bytes .../Textures/Actions/changeling.rsi/blade.png | Bin 0 -> 753 bytes .../Textures/Actions/changeling.rsi/meta.json | 17 + .../changeling.rsi/equipped-OUTERCLOTHING.png | Bin 0 -> 2695 bytes .../Armor/changeling.rsi/icon.png | Bin 0 -> 1047 bytes .../Armor/changeling.rsi/meta.json | 18 + .../Interface/Actions/adrenaline_sacs.png | Bin 0 -> 260 bytes .../Textures/Interface/Actions/arm_blade.png | Bin 0 -> 753 bytes .../Interface/Actions/changeling_armor.png | Bin 0 -> 364 bytes .../Interface/Actions/changeling_shield.png | Bin 0 -> 241 bytes .../Textures/Interface/Actions/fleshmend.png | Bin 0 -> 150 bytes .../Interface/Actions/lesser_form.png | Bin 0 -> 263 bytes .../Interface/Actions/ling_absorb.png | Bin 0 -> 231 bytes .../Interface/Actions/reviving_stasis.png | Bin 0 -> 403 bytes .../Interface/Actions/sting_blind.png | Bin 0 -> 270 bytes .../Textures/Interface/Actions/sting_cryo.png | Bin 0 -> 245 bytes .../Interface/Actions/sting_hallucination.png | Bin 0 -> 324 bytes .../Textures/Interface/Actions/sting_mute.png | Bin 0 -> 266 bytes .../Interface/Actions/sting_transform.png | Bin 0 -> 263 bytes .../Interface/Actions/tentacle_arm.png | Bin 0 -> 1095 bytes .../Textures/Interface/Actions/transform.png | Bin 0 -> 705 bytes .../Guns/Launchers/tentacle_gun.rsi/base.png | Bin 0 -> 1078 bytes .../Guns/Launchers/tentacle_gun.rsi/frope.png | Bin 0 -> 1324 bytes .../Guns/Launchers/tentacle_gun.rsi/hook.png | Bin 0 -> 945 bytes .../tentacle_gun.rsi/inhand-left.png | Bin 0 -> 1250 bytes .../tentacle_gun.rsi/inhand-right.png | Bin 0 -> 1259 bytes .../Guns/Launchers/tentacle_gun.rsi/meta.json | 35 + .../Melee/shields.rsi/changeling-icon.png | Bin 0 -> 413 bytes .../shields.rsi/changeling-inhand-left.png | Bin 0 -> 879 bytes .../shields.rsi/changeling-inhand-right.png | Bin 0 -> 882 bytes .../Weapons/Melee/shields.rsi/meta.json | 13 +- SpaceStation14.sln.DotSettings | 2 + 127 files changed, 3747 insertions(+), 33 deletions(-) create mode 100644 Content.Client/Miracle/Changeling/TentacleGun.cs create mode 100644 Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewChangelingSelectorWindow.xaml create mode 100644 Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewChangelingSelectorWindow.xaml.cs create mode 100644 Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewSelectorBUI.cs create mode 100644 Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingSelectorWindow.xaml create mode 100644 Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingSelectorWindow.xaml.cs create mode 100644 Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingUIBUI.cs create mode 100644 Content.Server/Changeling/ChangelingRoleComponent.cs create mode 100644 Content.Server/Changeling/ChangelingRuleComponent.cs create mode 100644 Content.Server/Changeling/ChangelingRuleSystem.cs create mode 100644 Content.Server/Changeling/ChangelingSystem.Abilities.cs create mode 100644 Content.Server/Changeling/ChangelingSystem.Shop.cs create mode 100644 Content.Server/Changeling/ChangelingSystem.cs create mode 100644 Content.Server/Changeling/Objectives/ChangelingConditionsSystem.cs create mode 100644 Content.Server/Changeling/Objectives/Components/AbsorbChangelingConditionComponent.cs create mode 100644 Content.Server/Changeling/Objectives/Components/AbsorbDnaConditionComponent.cs create mode 100644 Content.Server/Changeling/Objectives/Components/AbsorbMoreConditionComponent.cs create mode 100644 Content.Server/Changeling/Objectives/Components/EscapeWithIdentityConditionComponent.cs create mode 100644 Content.Server/Changeling/Objectives/Components/PickRandomChangelingComponent.cs create mode 100644 Content.Server/Changeling/Objectives/Components/PickRandomIdentityComponent.cs create mode 100644 Content.Server/Changeling/TentacleGun.cs create mode 100644 Content.Server/Changeling/UncloneableComponent.cs create mode 100644 Content.Server/Chat/Commands/ChangelingChatCommand.cs create mode 100644 Content.Shared/Changeling/AbsorbedComponent.cs create mode 100644 Content.Shared/Changeling/ChangelingComponent.cs create mode 100644 Content.Shared/Changeling/ChangelingNameGenerator.cs create mode 100644 Content.Shared/Changeling/ChemicalsSystem.cs create mode 100644 Content.Shared/Changeling/LesserFormRestrictedComponent.cs create mode 100644 Content.Shared/Changeling/SharedChangeling.cs create mode 100644 Content.Shared/Changeling/SharedChangelingChat.cs create mode 100644 Content.Shared/Changeling/SharedTentacleGun.cs create mode 100644 Content.Shared/Changeling/TentacleGunComponent.cs create mode 100644 Content.Shared/Changeling/TentacleProjectileComponent.cs create mode 100644 Content.Shared/Miracle/UI/ListViewBUI.cs create mode 100644 Content.Shared/Miracle/UI/TransformStingBUI.cs create mode 100644 Resources/Audio/Ambience/Antag/changeling_start.ogg create mode 100644 Resources/Locale/en-US/Miracle/changeling.ftl create mode 100644 Resources/Locale/en-US/game-ticking/game-presets/preset-changeling.ftl create mode 100644 Resources/Locale/en-US/objectives/conditions/absorb-dna.ftl create mode 100644 Resources/Prototypes/Actions/changeling.yml create mode 100644 Resources/Prototypes/Alerts/changeling.yml create mode 100644 Resources/Prototypes/Catalog/changeling_catalog.yml create mode 100644 Resources/Prototypes/Objectives/changeling.yml create mode 100644 Resources/Prototypes/Roles/Antags/changeling.yml create mode 100644 Resources/Textures/Actions/changeling.rsi/absorb.png create mode 100644 Resources/Textures/Actions/changeling.rsi/blade.png create mode 100644 Resources/Textures/Actions/changeling.rsi/meta.json create mode 100644 Resources/Textures/Clothing/OuterClothing/Armor/changeling.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Armor/changeling.rsi/icon.png create mode 100644 Resources/Textures/Clothing/OuterClothing/Armor/changeling.rsi/meta.json create mode 100644 Resources/Textures/Interface/Actions/adrenaline_sacs.png create mode 100644 Resources/Textures/Interface/Actions/arm_blade.png create mode 100644 Resources/Textures/Interface/Actions/changeling_armor.png create mode 100644 Resources/Textures/Interface/Actions/changeling_shield.png create mode 100644 Resources/Textures/Interface/Actions/fleshmend.png create mode 100644 Resources/Textures/Interface/Actions/lesser_form.png create mode 100644 Resources/Textures/Interface/Actions/ling_absorb.png create mode 100644 Resources/Textures/Interface/Actions/reviving_stasis.png create mode 100644 Resources/Textures/Interface/Actions/sting_blind.png create mode 100644 Resources/Textures/Interface/Actions/sting_cryo.png create mode 100644 Resources/Textures/Interface/Actions/sting_hallucination.png create mode 100644 Resources/Textures/Interface/Actions/sting_mute.png create mode 100644 Resources/Textures/Interface/Actions/sting_transform.png create mode 100644 Resources/Textures/Interface/Actions/tentacle_arm.png create mode 100644 Resources/Textures/Interface/Actions/transform.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/base.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/frope.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/hook.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/inhand-right.png create mode 100644 Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/meta.json create mode 100644 Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-icon.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-inhand-left.png create mode 100644 Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-inhand-right.png diff --git a/Content.Client/Chat/Managers/ChatManager.cs b/Content.Client/Chat/Managers/ChatManager.cs index 4f73a9ba80..55e1b0a5c6 100644 --- a/Content.Client/Chat/Managers/ChatManager.cs +++ b/Content.Client/Chat/Managers/ChatManager.cs @@ -1,8 +1,8 @@ using Content.Client.Administration.Managers; using Content.Client.Ghost; using Content.Shared.Administration; +using Content.Shared.Changeling; using Content.Shared.Chat; -using Content.Shared._White.Cult; using Robust.Client.Console; using Robust.Client.Player; using Robust.Shared.Utility; @@ -15,7 +15,7 @@ namespace Content.Client.Chat.Managers [Dependency] private readonly IClientConsoleHost _consoleHost = default!; [Dependency] private readonly IClientAdminManager _adminMgr = default!; [Dependency] private readonly IEntitySystemManager _systems = default!; - [Dependency] private readonly IEntityManager _entities = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; [Dependency] private readonly IPlayerManager _player = default!; @@ -29,7 +29,6 @@ namespace Content.Client.Chat.Managers public void SendMessage(string text, ChatSelectChannel channel) { - var str = text.ToString(); switch (channel) { case ChatSelectChannel.Console: @@ -38,25 +37,25 @@ namespace Content.Client.Chat.Managers break; case ChatSelectChannel.LOOC: - _consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(str)}\""); + _consoleHost.ExecuteCommand($"looc \"{CommandParsing.Escape(text)}\""); break; case ChatSelectChannel.OOC: - _consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(str)}\""); + _consoleHost.ExecuteCommand($"ooc \"{CommandParsing.Escape(text)}\""); break; case ChatSelectChannel.Admin: - _consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(str)}\""); + _consoleHost.ExecuteCommand($"asay \"{CommandParsing.Escape(text)}\""); break; case ChatSelectChannel.Emotes: - _consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(str)}\""); + _consoleHost.ExecuteCommand($"me \"{CommandParsing.Escape(text)}\""); break; case ChatSelectChannel.Cult: var localEnt = _player.LocalPlayer != null ? _player.LocalPlayer.ControlledEntity : null; - if (_entities.TryGetComponent(localEnt, out CultistComponent? comp)) - _consoleHost.ExecuteCommand($"csay \"{CommandParsing.Escape(str)}\""); + if (_entityManager.TryGetComponent(localEnt, out CultistComponent? comp)) + _consoleHost.ExecuteCommand($"csay \"{CommandParsing.Escape(text)}\""); break; case ChatSelectChannel.Dead: @@ -64,7 +63,7 @@ namespace Content.Client.Chat.Managers goto case ChatSelectChannel.Local; if (_adminMgr.HasFlag(AdminFlags.Admin)) - _consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(str)}\""); + _consoleHost.ExecuteCommand($"dsay \"{CommandParsing.Escape(text)}\""); else _sawmill.Warning("Tried to speak on deadchat without being ghost or admin."); break; @@ -72,13 +71,20 @@ namespace Content.Client.Chat.Managers // TODO sepearate radio and say into separate commands. case ChatSelectChannel.Radio: case ChatSelectChannel.Local: - _consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(str)}\""); + _consoleHost.ExecuteCommand($"say \"{CommandParsing.Escape(text)}\""); break; case ChatSelectChannel.Whisper: - _consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(str)}\""); + _consoleHost.ExecuteCommand($"whisper \"{CommandParsing.Escape(text)}\""); break; + case ChatSelectChannel.Changeling: + var localEntity = _player.LocalPlayer != null ? _player.LocalPlayer.ControlledEntity : null; + if (_entityManager.HasComponent(localEntity)) + _consoleHost.ExecuteCommand($"gsay \"{CommandParsing.Escape(text)}\""); + break; + + default: throw new ArgumentOutOfRangeException(nameof(channel), channel, null); } diff --git a/Content.Client/Miracle/Changeling/TentacleGun.cs b/Content.Client/Miracle/Changeling/TentacleGun.cs new file mode 100644 index 0000000000..8e582c0efe --- /dev/null +++ b/Content.Client/Miracle/Changeling/TentacleGun.cs @@ -0,0 +1,8 @@ +using Content.Shared.Changeling; + +namespace Content.Client.Miracle.Changeling; + +public sealed class TentacleGun : SharedTentacleGun +{ + +} diff --git a/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewChangelingSelectorWindow.xaml b/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewChangelingSelectorWindow.xaml new file mode 100644 index 0000000000..4a2aa3fbb3 --- /dev/null +++ b/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewChangelingSelectorWindow.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewChangelingSelectorWindow.xaml.cs b/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewChangelingSelectorWindow.xaml.cs new file mode 100644 index 0000000000..d439a192ba --- /dev/null +++ b/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewChangelingSelectorWindow.xaml.cs @@ -0,0 +1,33 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Miracle.Changeling.UI.ListViewSelector; + +[GenerateTypedNameReferences] +public sealed partial class ListViewChangelingSelectorWindow : DefaultWindow +{ + public Action? ItemSelected; + + public ListViewChangelingSelectorWindow() + { + RobustXamlLoader.Load(this); + } + + public void PopulateList(Dictionary items) + { + ItemsContainer.RemoveAllChildren(); + + foreach (var item in items) + { + var button = new Button(); + + button.Text = item.Value; + + button.OnPressed += _ => ItemSelected?.Invoke(item.Key); + + ItemsContainer.AddChild(button); + } + } +} diff --git a/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewSelectorBUI.cs b/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewSelectorBUI.cs new file mode 100644 index 0000000000..b7de804f1c --- /dev/null +++ b/Content.Client/Miracle/Changeling/UI/ListViewSelector/ListViewSelectorBUI.cs @@ -0,0 +1,51 @@ +using Content.Shared.Miracle.UI; + +namespace Content.Client.Miracle.Changeling.UI.ListViewSelector; + +public sealed class ListViewSelectorBui : BoundUserInterface +{ + private ListViewChangelingSelectorWindow? _window; + + public ListViewSelectorBui(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _window = new ListViewChangelingSelectorWindow(); + _window.OpenCentered(); + _window.OnClose += Close; + + _window.ItemSelected += item => + { + var msg = new ListViewItemSelectedMessage(item); + SendMessage(msg); + }; + + if(State != null) + UpdateState(State); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is ListViewBuiState newState) + { + _window?.PopulateList(newState.Items); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) + return; + + _window?.Close(); + } +} diff --git a/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingSelectorWindow.xaml b/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingSelectorWindow.xaml new file mode 100644 index 0000000000..4a2aa3fbb3 --- /dev/null +++ b/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingSelectorWindow.xaml @@ -0,0 +1,9 @@ + + + + + diff --git a/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingSelectorWindow.xaml.cs b/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingSelectorWindow.xaml.cs new file mode 100644 index 0000000000..2402976248 --- /dev/null +++ b/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingSelectorWindow.xaml.cs @@ -0,0 +1,33 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.Controls; +using Robust.Client.UserInterface.CustomControls; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Miracle.Changeling.UI.TransformStingUI; + +[GenerateTypedNameReferences] +public sealed partial class TransformStingSelectorWindow : DefaultWindow +{ + public Action? ItemSelected; + + public TransformStingSelectorWindow() + { + RobustXamlLoader.Load(this); + } + + public void PopulateList(Dictionary items, NetEntity target) + { + ItemsContainer.RemoveAllChildren(); + + foreach (var item in items) + { + var button = new Button(); + + button.Text = item.Value; + + button.OnPressed += _ => ItemSelected?.Invoke(item.Key, target); + + ItemsContainer.AddChild(button); + } + } +} diff --git a/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingUIBUI.cs b/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingUIBUI.cs new file mode 100644 index 0000000000..5c77d84669 --- /dev/null +++ b/Content.Client/Miracle/Changeling/UI/TransformStingUI/TransformStingUIBUI.cs @@ -0,0 +1,51 @@ +using Content.Shared.Miracle.UI; + +namespace Content.Client.Miracle.Changeling.UI.TransformStingUI; + +public sealed class TransformStingSelectorBui : BoundUserInterface +{ + private TransformStingSelectorWindow? _window; + + public TransformStingSelectorBui(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + IoCManager.InjectDependencies(this); + } + + protected override void Open() + { + base.Open(); + + _window = new TransformStingSelectorWindow(); + _window.OpenCentered(); + _window.OnClose += Close; + + _window.ItemSelected += (item, target) => + { + var msg = new TransformStingItemSelectedMessage(item, target); + SendMessage(msg); + }; + + if(State != null) + UpdateState(State); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is TransformStingBuiState newState) + { + _window?.PopulateList(newState.Items, newState.Target); + } + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (!disposing) + return; + + _window?.Close(); + } +} diff --git a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs index 0f1fbcea60..82e38ecb35 100644 --- a/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs +++ b/Content.Client/UserInterface/Systems/Chat/ChatUIController.cs @@ -13,6 +13,7 @@ using Content.Client.UserInterface.Systems.Chat.Widgets; using Content.Client.UserInterface.Systems.Gameplay; using Content.Shared.Administration; using Content.Shared.CCVar; +using Content.Shared.Changeling; using Content.Shared.Chat; using Content.Shared.Damage.ForceSay; using Content.Shared.Examine; @@ -75,7 +76,8 @@ public sealed class ChatUIController : UIController {SharedChatSystem.AdminPrefix, ChatSelectChannel.Admin}, {SharedChatSystem.RadioCommonPrefix, ChatSelectChannel.Radio}, {SharedChatSystem.DeadPrefix, ChatSelectChannel.Dead}, - {SharedChatSystem.CultPrefix, ChatSelectChannel.Cult}, //WD EDIT + {SharedChatSystem.CultPrefix, ChatSelectChannel.Cult}, + {SharedChatSystem.ChangelingPrefix, ChatSelectChannel.Changeling} }; public static readonly Dictionary ChannelPrefixes = new() @@ -89,7 +91,8 @@ public sealed class ChatUIController : UIController {ChatSelectChannel.Admin, SharedChatSystem.AdminPrefix}, {ChatSelectChannel.Radio, SharedChatSystem.RadioCommonPrefix}, {ChatSelectChannel.Dead, SharedChatSystem.DeadPrefix}, - {ChatSelectChannel.Cult, SharedChatSystem.CultPrefix} // WD EDIT + {ChatSelectChannel.Cult, SharedChatSystem.CultPrefix}, + {ChatSelectChannel.Changeling, SharedChatSystem.ChangelingPrefix} }; @@ -203,6 +206,9 @@ public sealed class ChatUIController : UIController _input.SetInputCommand(ContentKeyFunctions.FocusAdminChat, InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Admin))); + _input.SetInputCommand(ContentKeyFunctions.FocusChangelingChat, + InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Changeling))); + _input.SetInputCommand(ContentKeyFunctions.FocusCultChat, InputCmdHandler.FromDelegate(_ => FocusChannel(ChatSelectChannel.Cult))); @@ -221,6 +227,7 @@ public sealed class ChatUIController : UIController _input.SetInputCommand(ContentKeyFunctions.CycleChatChannelBackward, InputCmdHandler.FromDelegate(_ => CycleChatChannel(false))); + SubscribeLocalEvent(OnUpdateChangelingChat); // WD EDIT SubscribeLocalEvent(OnUpdateCultState); // WD EDIT END @@ -230,6 +237,11 @@ public sealed class ChatUIController : UIController gameplayStateLoad.OnScreenUnload += OnScreenUnload; } + private void OnUpdateChangelingChat(ChangelingUserStart ev) + { + UpdateChannelPermissions(); + } + // WD EDIT private void OnUpdateCultState(EventCultistComponentState ev) { diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs index 185e48aea2..a2168ae4cb 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelFilterPopup.xaml.cs @@ -23,6 +23,7 @@ public sealed partial class ChannelFilterPopup : Popup ChatChannel.AdminAlert, ChatChannel.AdminChat, ChatChannel.Server, + ChatChannel.Changeling, ChatChannel.Cult // WD EDIT }; diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs index 455f70918b..1b1644e2d0 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorButton.cs @@ -64,6 +64,7 @@ public sealed class ChannelSelectorButton : ChatPopupButton Color.LightSkyBlue, ChatSelectChannel.Dead => Color.MediumPurple, ChatSelectChannel.Admin => Color.HotPink, + ChatSelectChannel.Changeling => Color.Purple, ChatSelectChannel.Cult => Color.DarkRed, _ => Color.DarkGray }; diff --git a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs index f9ff9f07fa..5ab5540b44 100644 --- a/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs +++ b/Content.Client/UserInterface/Systems/Chat/Controls/ChannelSelectorPopup.cs @@ -17,6 +17,7 @@ public sealed class ChannelSelectorPopup : Popup ChatSelectChannel.OOC, ChatSelectChannel.Dead, ChatSelectChannel.Admin, + ChatSelectChannel.Changeling, ChatSelectChannel.Cult // WD EDIT // NOTE: Console is not in there and it can never be permanently selected. // You can, however, still submit commands as console by prefixing with /. diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 13853eaab3..5c04970daa 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -1,3 +1,4 @@ +using Content.Server.Changeling; using Content.Server.GameTicking; using Content.Server.GameTicking.Rules; using Content.Server.StationEvents.Events; @@ -23,6 +24,7 @@ public sealed partial class AdminVerbSystem [Dependency] private readonly ZombieSystem _zombie = default!; [Dependency] private readonly ThiefRuleSystem _thief = default!; [Dependency] private readonly TraitorRuleSystem _traitorRule = default!; + [Dependency] private readonly ChangelingRuleSystem _changelingRule = default!; [Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!; [Dependency] private readonly PiratesRuleSystem _piratesRule = default!; [Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!; @@ -63,6 +65,25 @@ public sealed partial class AdminVerbSystem }; args.Verbs.Add(traitor); + Verb changeling = new() + { + Text = Loc.GetString("admin-verb-text-make-changeling"), + Category = VerbCategory.Antag, + Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Actions/ling_absorb.png")), + Act = () => + { + if (!_minds.TryGetSession(targetMindComp.Mind, out var session)) + return; + + var isHuman = HasComp(args.Target); + _changelingRule.MakeChangeling(session); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-verb-make-changeling"), + }; + + args.Verbs.Add(changeling); + Verb zombie = new() { Text = Loc.GetString("admin-verb-text-make-zombie"), diff --git a/Content.Server/Changeling/ChangelingRoleComponent.cs b/Content.Server/Changeling/ChangelingRoleComponent.cs new file mode 100644 index 0000000000..6f347a7b26 --- /dev/null +++ b/Content.Server/Changeling/ChangelingRoleComponent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; + +namespace Content.Server.Changeling; + +[RegisterComponent] +public sealed partial class ChangelingRoleComponent : AntagonistRoleComponent +{ +} diff --git a/Content.Server/Changeling/ChangelingRuleComponent.cs b/Content.Server/Changeling/ChangelingRuleComponent.cs new file mode 100644 index 0000000000..c96547e91f --- /dev/null +++ b/Content.Server/Changeling/ChangelingRuleComponent.cs @@ -0,0 +1,35 @@ +using Content.Shared.Preferences; +using Content.Shared.Roles; +using Robust.Shared.Audio; +using Robust.Shared.Player; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + +namespace Content.Server.Changeling; + +[RegisterComponent, Access(typeof(ChangelingRuleSystem))] +public sealed partial class ChangelingRuleComponent : Component +{ + public readonly List ChangelingMinds = new(); + + [DataField("changelingPrototypeId", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string ChangelingPrototypeId = "Changeling"; + + public int TotalChangelings => ChangelingMinds.Count; + + public enum SelectionState + { + WaitingForSpawn = 0, + ReadyToSelect = 1, + SelectionMade = 2, + } + + public SelectionState SelectionStatus = SelectionState.WaitingForSpawn; + public TimeSpan AnnounceAt = TimeSpan.Zero; + public Dictionary StartCandidates = new(); + + /// + /// Path to antagonist alert sound. + /// + [DataField("greetSoundNotification")] + public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/changeling_start.ogg"); +} diff --git a/Content.Server/Changeling/ChangelingRuleSystem.cs b/Content.Server/Changeling/ChangelingRuleSystem.cs new file mode 100644 index 0000000000..55b4e7ba18 --- /dev/null +++ b/Content.Server/Changeling/ChangelingRuleSystem.cs @@ -0,0 +1,275 @@ +using System.Linq; +using Content.Server.Antag; +using Content.Server.Chat.Managers; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Mind; +using Content.Server.NPC.Systems; +using Content.Server.Objectives; +using Content.Server.Roles; +using Content.Shared.Changeling; +using Content.Shared.GameTicking; +using Content.Shared.Mobs.Systems; +using Content.Shared.Objectives.Components; +using Content.Shared.Roles; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server.Changeling; + +public sealed class ChangelingRuleSystem : GameRuleSystem +{ + [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IChatManager _chatManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly ObjectivesSystem _objectives = default!; + [Dependency] private readonly ChangelingNameGenerator _nameGenerator = default!; + + private const int PlayersPerChangeling = 10; + private const int MaxChangelings = 5; + + private const float ChangelingStartDelay = 3f * 60; + private const float ChangelingStartDelayVariance = 3f * 60; + + private const int ChangelingMinPlayers = 10; + + private const int ChangelingMaxDifficulty = 5; + private const int ChangelingMaxPicks = 20; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartAttempt); + SubscribeLocalEvent(OnPlayersSpawned); + SubscribeLocalEvent(HandleLatejoin); + SubscribeLocalEvent(ClearUsedNames); + + SubscribeLocalEvent(OnObjectivesTextGetInfo); + } + + protected override void ActiveTick(EntityUid uid, ChangelingRuleComponent component, GameRuleComponent gameRule, float frameTime) + { + base.ActiveTick(uid, component, gameRule, frameTime); + + if (component.SelectionStatus == ChangelingRuleComponent.SelectionState.ReadyToSelect && _gameTiming.CurTime > component.AnnounceAt) + DoChangelingStart(component); + } + + private void OnStartAttempt(RoundStartAttemptEvent ev) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var gameRule)) + { + if (!GameTicker.IsGameRuleAdded(uid, gameRule)) + continue; + + var minPlayers = ChangelingMinPlayers; + if (!ev.Forced && ev.Players.Length < minPlayers) + { + _chatManager.SendAdminAnnouncement(Loc.GetString("changeling-not-enough-ready-players", + ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers))); + ev.Cancel(); + continue; + } + + if (ev.Players.Length == 0) + { + _chatManager.DispatchServerAnnouncement(Loc.GetString("changeling-no-one-ready")); + ev.Cancel(); + } + } + } + private void DoChangelingStart(ChangelingRuleComponent component) + { + if (!component.StartCandidates.Any()) + { + Log.Error("Tried to start Changeling mode without any candidates."); + return; + } + + var numChangelings = MathHelper.Clamp(component.StartCandidates.Count / PlayersPerChangeling, 1, MaxChangelings); + var changelingPool = _antagSelection.FindPotentialAntags(component.StartCandidates, component.ChangelingPrototypeId); + var selectedChangelings = _antagSelection.PickAntag(numChangelings, changelingPool); + + foreach (var changeling in selectedChangelings) + { + MakeChangeling(changeling); + } + + component.SelectionStatus = ChangelingRuleComponent.SelectionState.SelectionMade; + } + + private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var changeling, out var gameRule)) + { + if (!GameTicker.IsGameRuleAdded(uid, gameRule)) + continue; + foreach (var player in ev.Players) + { + if (!ev.Profiles.ContainsKey(player.UserId)) + continue; + + changeling.StartCandidates[player] = ev.Profiles[player.UserId]; + } + + var delay = TimeSpan.FromSeconds(ChangelingStartDelay + _random.NextFloat(0f, ChangelingStartDelayVariance)); + + changeling.AnnounceAt = _gameTiming.CurTime + delay; + + changeling.SelectionStatus = ChangelingRuleComponent.SelectionState.ReadyToSelect; + } + } + + public bool MakeChangeling(ICommonSession changeling, bool giveObjectives = true) + { + var changelingRule = EntityQuery().FirstOrDefault(); + if (changelingRule == null) + { + GameTicker.StartGameRule("Changeling", out var ruleEntity); + changelingRule = Comp(ruleEntity); + } + + if (!_mindSystem.TryGetMind(changeling, out var mindId, out var mind)) + { + Log.Info("Failed getting mind for picked changeling."); + return false; + } + + if (HasComp(mindId)) + { + Log.Error($"Player {changeling.Name} is already a changeling."); + return false; + } + + if (mind.OwnedEntity is not { } entity) + { + Log.Error("Mind picked for changeling did not have an attached entity."); + return false; + } + + _roleSystem.MindAddRole(mindId, new ChangelingRoleComponent + { + PrototypeId = changelingRule.ChangelingPrototypeId + }, mind); + + var briefing = Loc.GetString("changeling-role-briefing-short"); + + _roleSystem.MindAddRole(mindId, new RoleBriefingComponent + { + Briefing = briefing + }, mind, true); + + _roleSystem.MindPlaySound(mindId, changelingRule.GreetSoundNotification, mind); + SendChangelingBriefing(mindId); + changelingRule.ChangelingMinds.Add(mindId); + + // Change the faction + _npcFaction.RemoveFaction(entity, "NanoTrasen", false); + _npcFaction.AddFaction(entity, "Syndicate"); + + EnsureComp(entity, out var readyChangeling); + + readyChangeling.HiveName = _nameGenerator.GetName(); + Dirty(entity, readyChangeling); + + + if (giveObjectives) + { + var maxDifficulty = ChangelingMaxDifficulty; + var maxPicks = ChangelingMaxPicks; + var difficulty = 0f; + for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++) + { + var objective = _objectives.GetRandomObjective(mindId, mind, "ChangelingObjectiveGroups"); + if (objective == null) + continue; + + _mindSystem.AddObjective(mindId, mind, objective.Value); + difficulty += Comp(objective.Value).Difficulty; + } + } + + return true; + } + + private void SendChangelingBriefing(EntityUid mind) + { + if (!_mindSystem.TryGetSession(mind, out var session)) + return; + + _chatManager.DispatchServerMessage(session, Loc.GetString("changeling-role-greeting")); + } + + private void HandleLatejoin(PlayerSpawnCompleteEvent ev) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var changeling, out var gameRule)) + { + if (!GameTicker.IsGameRuleAdded(uid, gameRule)) + continue; + + if (changeling.TotalChangelings >= MaxChangelings) + continue; + if (!ev.LateJoin) + continue; + if (!ev.Profile.AntagPreferences.Contains(changeling.ChangelingPrototypeId)) + continue; + + if (ev.JobId == null || !_prototypeManager.TryIndex(ev.JobId, out var job)) + continue; + + if (!job.CanBeAntag) + continue; + + // Before the announcement is made, late-joiners are considered the same as players who readied. + if (changeling.SelectionStatus < ChangelingRuleComponent.SelectionState.SelectionMade) + { + changeling.StartCandidates[ev.Player] = ev.Profile; + continue; + } + + var target = PlayersPerChangeling * changeling.TotalChangelings + 1; + + var chance = 1f / PlayersPerChangeling; + + if (ev.JoinOrder < target) + { + chance /= (target - ev.JoinOrder); + } + else + { + chance *= ((ev.JoinOrder + 1) - target); + } + + if (chance > 1) + chance = 1; + + if (_random.Prob(chance)) + { + MakeChangeling(ev.Player); + } + } + } + + private void OnObjectivesTextGetInfo(EntityUid uid, ChangelingRuleComponent comp, ref ObjectivesTextGetInfoEvent args) + { + args.Minds = comp.ChangelingMinds; + args.AgentName = Loc.GetString("changeling-round-end-agent-name"); + } + + private void ClearUsedNames(RoundRestartCleanupEvent ev) + { + _nameGenerator.ClearUsed(); + } +} diff --git a/Content.Server/Changeling/ChangelingSystem.Abilities.cs b/Content.Server/Changeling/ChangelingSystem.Abilities.cs new file mode 100644 index 0000000000..e1aef75291 --- /dev/null +++ b/Content.Server/Changeling/ChangelingSystem.Abilities.cs @@ -0,0 +1,879 @@ +using System.Linq; +using Content.Server.Administration.Systems; +using Content.Server.DoAfter; +using Content.Server.Forensics; +using Content.Server.Humanoid; +using Content.Server.IdentityManagement; +using Content.Server.Mind; +using Content.Server.Polymorph.Systems; +using Content.Server.Popups; +using Content.Server.Store.Components; +using Content.Server.Temperature.Systems; +using Content.Shared.Actions; +using Content.Shared.Changeling; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Damage; +using Content.Shared.DoAfter; +using Content.Shared.Eye.Blinding.Components; +using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.FixedPoint; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Humanoid; +using Content.Shared.Humanoid.Markings; +using Content.Shared.Implants.Components; +using Content.Shared.Inventory; +using Content.Shared.Miracle.UI; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; +using Content.Shared.Pulling; +using Content.Shared.Pulling.Components; +using Content.Shared.Standing; +using Content.Shared.StatusEffect; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects.Components.Localization; +using Robust.Shared.Player; +using Robust.Shared.Random; +using Robust.Shared.Serialization.Manager; + +namespace Content.Server.Changeling; + +public sealed partial class ChangelingSystem +{ + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; + [Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly DamageableSystem _damage = default!; + [Dependency] private readonly StandingStateSystem _stateSystem = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoidAppearance = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly IdentitySystem _identity = default!; + [Dependency] private readonly UserInterfaceSystem _ui = default!; + [Dependency] private readonly ISerializationManager _serializationManager = default!; + [Dependency] private readonly RejuvenateSystem _rejuvenate = default!; + [Dependency] private readonly PolymorphSystem _polymorph = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; + [Dependency] private readonly TemperatureSystem _temperatureSystem = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solutionContainer = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly InventorySystem _inventorySystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly ActionContainerSystem _actionContainerSystem = default!; + [Dependency] private readonly SharedPullingSystem _pullingSystem = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + + + private void InitializeAbilities() + { + SubscribeLocalEvent(OnAbsorb); + SubscribeLocalEvent(OnTransform); + SubscribeLocalEvent(OnRegenerate); + SubscribeLocalEvent(OnLesserForm); + + SubscribeLocalEvent(OnTransformSting); + SubscribeLocalEvent(OnTransformStingMessage); + SubscribeLocalEvent(OnBlindSting); + SubscribeLocalEvent(OnMuteSting); + SubscribeLocalEvent(OnHallucinationSting); + SubscribeLocalEvent(OnCryoSting); + + SubscribeLocalEvent(OnAdrenalineSacs); + SubscribeLocalEvent(OnFleshMend); + SubscribeLocalEvent(OnArmBlade); + SubscribeLocalEvent(OnShield); + SubscribeLocalEvent(OnArmor); + SubscribeLocalEvent(OnTentacleArm); + + SubscribeLocalEvent(OnTransformDoAfter); + SubscribeLocalEvent(OnAbsorbDoAfter); + SubscribeLocalEvent(OnRegenerateDoAfter); + SubscribeLocalEvent(OnLesserFormDoAfter); + + SubscribeLocalEvent(OnTransformUiMessage); + } + + #region Data + + private const string ChangelingAbsorb = "ActionChangelingAbsorb"; + private const string ChangelingTransform = "ActionChangelingTransform"; + private const string ChangelingRegenerate = "ActionChangelingRegenerate"; + private const string ChangelingLesserForm = "ActionChangelingLesserForm"; + private const string ChangelingTransformSting = "ActionTransformSting"; + private const string ChangelingBlindSting = "ActionBlindSting"; + private const string ChangelingMuteSting = "ActionMuteSting"; + private const string ChangelingHallucinationSting = "ActionHallucinationSting"; + private const string ChangelingCryoSting = "ActionCryoSting"; + private const string ChangelingAdrenalineSacs = "ActionAdrenalineSacs"; + private const string ChangelingFleshMend = "ActionFleshmend"; + private const string ChangelingArmBlade = "ActionArmblade"; + private const string ChangelingShield = "ActionShield"; + private const string ChangelingArmor = "ActionArmor"; + private const string ChangelingTentacleArm = "ActionTentacleArm"; + + #endregion + + + #region Handlers + + private void OnAbsorb(EntityUid uid, ChangelingComponent component, AbsorbDnaActionEvent args) + { + if (!HasComp(args.Target)) + { + _popup.PopupEntity("You can't absorb not humans!", args.Performer); + return; + } + + if (HasComp(args.Target)) + { + _popup.PopupEntity("This person already absorbed!", args.Performer); + return; + } + + if (!TryComp(args.Target, out var dnaComponent)) + { + _popup.PopupEntity("Unknown creature!", uid); + return; + } + + if (component.AbsorbedEntities.ContainsKey(dnaComponent.DNA)) + { + _popup.PopupEntity("This DNA already absorbed!", uid); + return; + } + + if (!_stateSystem.IsDown(args.Target)) + { + _popup.PopupEntity("Target must be down!", args.Performer); + return; + } + + if (!TryComp(args.Target, out var pulled)) + { + _popup.PopupEntity("You must pull target!", args.Performer); + return; + } + + if (!pulled.BeingPulled) + { + _popup.PopupEntity("You must pull target!", args.Performer); + return; + } + + _doAfterSystem.TryStartDoAfter( + new DoAfterArgs(EntityManager, args.Performer, component.AbsorbDnaDelay, new AbsorbDnaDoAfterEvent(), uid, + args.Target, uid) + { + BreakOnTargetMove = true, + BreakOnUserMove = true + }); + } + + private void OnTransform(EntityUid uid, ChangelingComponent component, TransformActionEvent args) + { + if (!TryComp(uid, out var actorComponent)) + return; + + if (component.AbsorbedEntities.Count <= 1 && !component.IsLesserForm) + { + _popup.PopupEntity("You don't have any persons to transform!", uid); + return; + } + + if (!_ui.TryGetUi(uid, ListViewSelectorUiKeyChangeling.Key, out var bui)) + return; + + Dictionary state; + + if (TryComp(uid, out var dnaComponent)) + { + state = component.AbsorbedEntities.Where(key => key.Key != dnaComponent.DNA).ToDictionary(humanoidData + => humanoidData.Key, humanoidData + => humanoidData.Value.Name); + } + else + { + state = component.AbsorbedEntities.ToDictionary(humanoidData + => humanoidData.Key, humanoidData + => humanoidData.Value.Name); + } + + _ui.SetUiState(bui, new ListViewBuiState(state)); + _ui.OpenUi(bui, actorComponent.PlayerSession); + } + + private void OnTransformUiMessage(EntityUid uid, ChangelingComponent component, ListViewItemSelectedMessage args) + { + var selectedDna = args.SelectedItem; + var user = GetEntity(args.Entity); + + _doAfterSystem.TryStartDoAfter( + new DoAfterArgs(EntityManager, user, component.TransformDelay, + new TransformDoAfterEvent { SelectedDna = selectedDna }, user, + user, user) + { + BreakOnUserMove = true + }); + + if (!TryComp(uid, out var actorComponent)) + return; + + if (!_ui.TryGetUi(user, ListViewSelectorUiKeyChangeling.Key, out var bui)) + return; + + _ui.CloseUi(bui, actorComponent.PlayerSession); + } + + private void OnRegenerate(EntityUid uid, ChangelingComponent component, RegenerateActionEvent args) + { + if (!TryComp(uid, out var damageableComponent)) + return; + + if (damageableComponent.TotalDamage >= 0 && !_mobStateSystem.IsDead(uid)) + { + KillUser(uid, "Cellular"); + } + + _popup.PopupEntity("We beginning our regeneration.", uid); + + _doAfterSystem.TryStartDoAfter( + new DoAfterArgs(EntityManager, args.Performer, component.RegenerateDelay, + new RegenerateDoAfterEvent(), args.Performer, + args.Performer, args.Performer) + { + RequireCanInteract = false + }); + + component.IsRegenerating = true; + } + + private void OnLesserForm(EntityUid uid, ChangelingComponent component, LesserFormActionEvent args) + { + if (_mobStateSystem.IsDead(uid) || component.IsRegenerating) + { + _popup.PopupEntity("We can do this right now!", uid); + return; + } + + if (component.IsLesserForm) + { + _popup.PopupEntity("We're already in the lesser form!", uid); + return; + } + + _doAfterSystem.TryStartDoAfter(new DoAfterArgs(EntityManager, args.Performer, component.LesserFormDelay, + new LesserFormDoAfterEvent(), args.Performer, args.Performer) + { + BreakOnUserMove = true + }); + } + + private void OnTransformSting(EntityUid uid, ChangelingComponent component, TransformStingActionEvent args) + { + if (!HasComp(args.Target)) + { + _popup.PopupEntity("We can't transform that!", args.Performer); + return; + } + + if (!TryComp(uid, out var actorComponent)) + return; + + if (component.AbsorbedEntities.Count < 1) + { + _popup.PopupEntity("You don't have any persons to transform!", uid); + return; + } + + if (!_ui.TryGetUi(uid, TransformStingSelectorUiKey.Key, out var bui)) + return; + + var target = GetNetEntity(args.Target); + + var state = component.AbsorbedEntities.ToDictionary(humanoidData + => humanoidData.Key, humanoidData + => humanoidData.Value.Name); + + _ui.SetUiState(bui, new TransformStingBuiState(state, target)); + _ui.OpenUi(bui, actorComponent.PlayerSession); + } + + private void OnTransformStingMessage(EntityUid uid, ChangelingComponent component, + TransformStingItemSelectedMessage args) + { + var selectedDna = args.SelectedItem; + var humanData = component.AbsorbedEntities[selectedDna]; + var target = GetEntity(args.Target); + var user = GetEntity(args.Entity); + + if (!TryComp(uid, out var actorComponent)) + return; + + if (!_ui.TryGetUi(user, TransformStingSelectorUiKey.Key, out var bui)) + return; + + if (HasComp(target)) + { + _popup.PopupEntity("Transform virus was ineffective!", user); + return; + } + + if (!TakeChemicals(uid, component, 50)) + return; + + if (TryComp(target, out SharedPullerComponent? puller) && puller.Pulling is { } pulled && + TryComp(pulled, out SharedPullableComponent? pullable)) + _pullingSystem.TryStopPull(pullable); + + TransformPerson(target, humanData); + + _ui.CloseUi(bui, actorComponent.PlayerSession); + + StartUseDelayById(uid, ChangelingTransformSting); + } + + private void OnBlindSting(EntityUid uid, ChangelingComponent component, BlindStingActionEvent args) + { + if (!HasComp(args.Target) || + !HasComp(args.Target)) + { + _popup.PopupEntity("We cannot sting that!", uid); + return; + } + + if (!TakeChemicals(uid, component, 25)) + return; + + var statusTimeSpan = TimeSpan.FromSeconds(25); + _statusEffectsSystem.TryAddStatusEffect(args.Target, TemporaryBlindnessSystem.BlindingStatusEffect, + statusTimeSpan, false, TemporaryBlindnessSystem.BlindingStatusEffect); + + args.Handled = true; + } + + private void OnMuteSting(EntityUid uid, ChangelingComponent component, MuteStingActionEvent args) + { + if (!HasComp(args.Target)) + { + _popup.PopupEntity("We cannot sting that!", uid); + return; + } + + if (!TakeChemicals(uid, component, 20)) + return; + + var statusTimeSpan = TimeSpan.FromSeconds(30); + _statusEffectsSystem.TryAddStatusEffect(args.Target, "Muted", + statusTimeSpan, false, "Muted"); + + args.Handled = true; + } + + private void OnHallucinationSting(EntityUid uid, ChangelingComponent component, HallucinationStingActionEvent args) + { + if (!HasComp(args.Target)) + { + _popup.PopupEntity("We cannot sting that!", uid); + return; + } + + if (!TakeChemicals(uid, component, 5)) + return; + + var statusTimeSpan = TimeSpan.FromSeconds(30); + _statusEffectsSystem.TryAddStatusEffect(args.Target, "BlurryVision", + statusTimeSpan, false, "BlurryVision"); + + args.Handled = true; + } + + private void OnCryoSting(EntityUid uid, ChangelingComponent component, CryoStingActionEvent args) + { + if (!HasComp(args.Target)) + { + _popup.PopupEntity("We cannot sting that!", uid); + return; + } + + if (!TakeChemicals(uid, component, 15)) + return; + + var statusTimeSpan = TimeSpan.FromSeconds(30); + _statusEffectsSystem.TryAddStatusEffect(args.Target, "SlowedDown", + statusTimeSpan, false, "SlowedDown"); + + _temperatureSystem.ForceChangeTemperature(args.Target, 100); + + args.Handled = true; + } + + private void OnAdrenalineSacs(EntityUid uid, ChangelingComponent component, AdrenalineSacsActionEvent args) + { + if (_mobStateSystem.IsDead(uid)) + return; + + if (!_solutionContainer.TryGetInjectableSolution(uid, out var injectable, out _)) + return; + + if (!TakeChemicals(uid, component, 30)) + return; + + _solutionContainer.TryAddReagent(injectable.Value, "Stimulants", 50); + + args.Handled = true; + } + + private void OnFleshMend(EntityUid uid, ChangelingComponent component, FleshmendActionEvent args) + { + if (_mobStateSystem.IsDead(uid)) + return; + + if (!_solutionContainer.TryGetInjectableSolution(uid, out var injectable, out _)) + return; + + if (!TakeChemicals(uid, component, 20)) + return; + + _solutionContainer.TryAddReagent(injectable.Value, "Omnizine", 50); + _solutionContainer.TryAddReagent(injectable.Value, "TranexamicAcid", 10); + + args.Handled = true; + } + + private void OnArmBlade(EntityUid uid, ChangelingComponent component, ArmbladeActionEvent args) + { + SpawnOrDeleteItem(uid, "ArmBlade"); + + args.Handled = true; + } + + private void OnShield(EntityUid uid, ChangelingComponent component, OrganicShieldActionEvent args) + { + SpawnOrDeleteItem(uid, "OrganicShield"); + + args.Handled = true; + } + + private void OnArmor(EntityUid uid, ChangelingComponent component, ChitinousArmorActionEvent args) + { + const string outerName = "outerClothing"; + const string protoName = "ClothingOuterChangeling"; + + if (!_inventorySystem.TryGetSlotEntity(uid, outerName, out var outerEnt)) + { + _inventorySystem.SpawnItemInSlot(uid, outerName, protoName, silent: true); + return; + } + + if (!TryComp(outerEnt, out var meta)) + { + _inventorySystem.SpawnItemInSlot(uid, outerName, protoName, silent: true); + return; + } + + if (meta.EntityPrototype == null) + { + _inventorySystem.SpawnItemInSlot(uid, outerName, protoName, silent: true); + return; + } + + if (meta.EntityPrototype.ID == protoName) + { + _inventorySystem.TryUnequip(uid, outerName, out var removedItem); + QueueDel(removedItem); + return; + } + + _inventorySystem.TryUnequip(uid, outerName, out _); + + _inventorySystem.SpawnItemInSlot(uid, outerName, protoName, silent: true); + + args.Handled = true; + } + + private void OnTentacleArm(EntityUid uid, ChangelingComponent component, TentacleArmActionEvent args) + { + SpawnOrDeleteItem(uid, "TentacleArmGun"); + + args.Handled = true; + } + + #endregion + + #region DoAfters + + private void OnTransformDoAfter(EntityUid uid, ChangelingComponent component, TransformDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + if (!TakeChemicals(uid, component, 5)) + return; + + if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is { } pulled && + TryComp(pulled, out SharedPullableComponent? pullable)) + _pullingSystem.TryStopPull(pullable); + + TryTransformChangeling(args.User, args.SelectedDna, component); + + args.Handled = true; + } + + private void OnAbsorbDoAfter(EntityUid uid, ChangelingComponent component, AbsorbDnaDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Target == null) + { + return; + } + + if(!_mindSystem.TryGetMind(uid, out var mindId, out _)) + return; + + if (TryComp(uid, out SharedPullerComponent? puller) && puller.Pulling is { } pulled && + TryComp(pulled, out SharedPullableComponent? pullable)) + _pullingSystem.TryStopPull(pullable); + + if (TryComp(args.Target.Value, out var changelingComponent)) + { + var total = component.AbsorbedEntities + .Concat(changelingComponent.AbsorbedEntities) + .ToDictionary(pair => pair.Key, pair => pair.Value); + component.AbsorbedEntities = total; + } + else + { + CopyHumanoidData(uid, args.Target.Value, component); + } + + AddCurrency(uid, args.Target.Value); + + KillUser(args.Target.Value, "Cellular"); + + EnsureComp(args.Target.Value, out var absorbedComponent); + + absorbedComponent.AbsorberMind = mindId; + + EnsureComp(args.Target.Value); + + StartUseDelayById(uid, ChangelingAbsorb); + + args.Handled = true; + } + + private void OnRegenerateDoAfter(EntityUid uid, ChangelingComponent component, RegenerateDoAfterEvent args) + { + if (args.Handled || args.Cancelled || args.Target == null) + { + return; + } + + if (HasComp(args.Target)) + { + _popup.PopupEntity("You're lost.", args.Target.Value); + component.IsRegenerating = false; + return; + } + + if (!TakeChemicals(uid, component, 15)) + return; + + _rejuvenate.PerformRejuvenate(args.Target.Value); + + _popup.PopupEntity("We're fully regenerated!", args.Target.Value); + + component.IsRegenerating = false; + + StartUseDelayById(uid, ChangelingRegenerate); + + args.Handled = true; + } + + private void OnLesserFormDoAfter(EntityUid uid, ChangelingComponent component, LesserFormDoAfterEvent args) + { + if (args.Handled || args.Cancelled) + return; + + var polymorphEntity = _polymorph.PolymorphEntity(args.User, "MonkeyChangeling"); + + if (polymorphEntity == null) + return; + + if (!TakeChemicals(uid, component, 5)) + return; + + var toAdd = new ChangelingComponent + { + HiveName = component.HiveName, + ChemicalsBalance = component.ChemicalsBalance, + AbsorbedEntities = component.AbsorbedEntities, + IsInited = component.IsInited, + IsLesserForm = true + }; + + EntityManager.AddComponent(polymorphEntity.Value, toAdd); + + _implantSystem.TransferImplants(uid, polymorphEntity.Value); + _actionContainerSystem.TransferAllActionsFiltered(uid, polymorphEntity.Value); + _action.GrantContainedActions(polymorphEntity.Value, polymorphEntity.Value); + + RemoveLesserFormActions(polymorphEntity.Value); + + _chemicalsSystem.UpdateAlert(polymorphEntity.Value, component); + + args.Handled = true; + } + + #endregion + + #region Helpers + + private void RemoveLesserFormActions(EntityUid uid) + { + if (!TryComp(uid, out var actionsComponent)) + return; + + foreach (var action in actionsComponent.Actions.ToArray()) + { + if (!HasComp(action)) + continue; + + _action.RemoveAction(uid, action); + } + } + + private void StartUseDelayById(EntityUid performer, string actionProto) + { + if (!TryComp(performer, out var actionsComponent)) + return; + + foreach (var action in actionsComponent.Actions.ToArray()) + { + var id = MetaData(action).EntityPrototype?.ID; + + if (id != actionProto) + continue; + + _action.StartUseDelay(action); + } + } + + private void KillUser(EntityUid target, string damageType) + { + if (!_mobThresholdSystem.TryGetThresholdForState(target, MobState.Dead, out var damage)) + return; + + DamageSpecifier dmg = new(); + dmg.DamageDict.Add(damageType, damage.Value); + _damage.TryChangeDamage(target, dmg, true); + } + + private void CopyHumanoidData(EntityUid uid, EntityUid target, ChangelingComponent component) + { + if (!TryComp(target, out var targetMeta)) + return; + if (!TryComp(target, out var targetAppearance)) + return; + if (!TryComp(target, out var targetDna)) + return; + if (!TryPrototype(target, out var prototype, targetMeta)) + return; + if (component.AbsorbedEntities.ContainsKey(targetDna.DNA)) + return; + + var appearance = _serializationManager.CreateCopy(targetAppearance, notNullableOverride: true); + var meta = _serializationManager.CreateCopy(targetMeta, notNullableOverride: true); + + var name = string.IsNullOrEmpty(meta.EntityName) + ? "Unknown Creature" + : meta.EntityName; + + component.AbsorbedEntities.Add(targetDna.DNA, new HumanoidData + { + EntityPrototype = prototype, + MetaDataComponent = meta, + AppearanceComponent = appearance, + Name = name, + Dna = targetDna.DNA + }); + + Dirty(uid, component); + } + + /// + /// Transforms chosen person to another, transferring it's appearance + /// + /// Transform target + /// Transform data + /// Override first check on HumanoidAppearanceComponent + /// Id of the transformed entity + private EntityUid? TransformPerson(EntityUid target, HumanoidData transformData, bool humanoidOverride = false) + { + if (!HasComp(target) && !humanoidOverride) + return null; + + var polymorphEntity = _polymorph.PolymorphEntity(target, transformData.EntityPrototype.ID); + + if (polymorphEntity == null) + return null; + + if (!TryComp(polymorphEntity.Value, out var polyAppearance)) + return null; + + ClonePerson(polymorphEntity.Value, transformData.AppearanceComponent, polyAppearance); + TransferDna(polymorphEntity.Value, transformData.Dna); + + if (!TryComp(polymorphEntity.Value, out var meta)) + return null; + + _metaData.SetEntityName(polymorphEntity.Value, transformData.MetaDataComponent!.EntityName, meta); + _metaData.SetEntityDescription(polymorphEntity.Value, transformData.MetaDataComponent!.EntityDescription, meta); + + _identity.QueueIdentityUpdate(polymorphEntity.Value); + + return polymorphEntity; + } + + private void TransferDna(EntityUid target, string dna) + { + if (!TryComp(target, out var dnaComponent)) + return; + + dnaComponent.DNA = dna; + } + + private void TryTransformChangeling(EntityUid uid, string dna, ChangelingComponent component) + { + if (!component.AbsorbedEntities.TryGetValue(dna, out var person)) + return; + + EntityUid? reverted = uid; + + reverted = component.IsLesserForm + ? TransformPerson(reverted.Value, person, humanoidOverride: true) + : TransformPerson(reverted.Value, person); + + if (reverted == null) + return; + + var toAdd = new ChangelingComponent + { + HiveName = component.HiveName, + ChemicalsBalance = component.ChemicalsBalance, + AbsorbedEntities = component.AbsorbedEntities, + IsInited = component.IsInited + }; + + EntityManager.AddComponent(reverted.Value, toAdd); + + _implantSystem.TransferImplants(uid, reverted.Value); + _actionContainerSystem.TransferAllActionsFiltered(uid, reverted.Value); + _action.GrantContainedActions(reverted.Value,reverted.Value); + + if (component.IsLesserForm) + { + //Don't copy IsLesserForm bool, because transferred component, in fact, new. Bool default value if false. + StartUseDelayById(reverted.Value, ChangelingLesserForm); + } + + _chemicalsSystem.UpdateAlert(reverted.Value, component); + + StartUseDelayById(reverted.Value, ChangelingTransform); + } + + /// + /// Used for cloning appearance + /// + /// Acceptor + /// Source appearance + /// Acceptor appearance component + private void ClonePerson(EntityUid target, HumanoidAppearanceComponent sourceHumanoid, + HumanoidAppearanceComponent targetHumanoid) + { + targetHumanoid.Species = sourceHumanoid.Species; + targetHumanoid.SkinColor = sourceHumanoid.SkinColor; + targetHumanoid.EyeColor = sourceHumanoid.EyeColor; + targetHumanoid.Age = sourceHumanoid.Age; + _humanoidAppearance.SetSex(target, sourceHumanoid.Sex, false, targetHumanoid); + _humanoidAppearance.SetSpecies(target, sourceHumanoid.Species); + targetHumanoid.CustomBaseLayers = new Dictionary(sourceHumanoid.CustomBaseLayers); + targetHumanoid.MarkingSet = new MarkingSet(sourceHumanoid.MarkingSet); + + targetHumanoid.Gender = sourceHumanoid.Gender; + if (TryComp(target, out var grammar)) + { + grammar.Gender = sourceHumanoid.Gender; + } + + Dirty(target, targetHumanoid); + } + + private void SpawnOrDeleteItem(EntityUid target, string prototypeName) + { + foreach (var eHand in _handsSystem.EnumerateHands(target)) + { + if (eHand.HeldEntity == null || !TryComp(eHand.HeldEntity.Value, out var meta)) + continue; + + if (meta.EntityPrototype != null && meta.EntityPrototype.ID != prototypeName) + continue; + + Del(eHand.HeldEntity); + return; + } + + if (!_handsSystem.TryGetEmptyHand(target, out var hand)) + { + _popup.PopupEntity("We need to have at least one empty hand!", target); + return; + } + + var item = Spawn(prototypeName, Transform(target).Coordinates); + + if (!_handsSystem.TryPickup(target, item, hand, animate: false)) + { + Del(item); + } + } + + private bool TakeChemicals(EntityUid uid, ChangelingComponent component, int quantity) + { + if (!_chemicalsSystem.RemoveChemicals(uid, component, quantity)) + { + _popup.PopupEntity("We're lacking of chemicals!", uid); + return false; + } + + _popup.PopupEntity($"Used {quantity} of chemicals.", uid); + + return true; + } + + private void AddCurrency(EntityUid uid, EntityUid absorbed) + { + if (!TryComp(uid, out var implant)) + return; + + foreach (var entity in implant.ImplantContainer.ContainedEntities) + { + if (!TryComp(entity, out var store)) + continue; + + if (_mobStateSystem.IsDead(absorbed)) + { + var points = _random.Next(1, 3); + var toAdd = new Dictionary { { "ChangelingPoint", points } }; + _storeSystem.TryAddCurrency(toAdd, entity, store); + } + else + { + var points = _random.Next(2, 4); + var toAdd = new Dictionary { { "ChangelingPoint", points } }; + _storeSystem.TryAddCurrency(toAdd, entity, store); + } + + return; + } + } + + #endregion +} diff --git a/Content.Server/Changeling/ChangelingSystem.Shop.cs b/Content.Server/Changeling/ChangelingSystem.Shop.cs new file mode 100644 index 0000000000..b0b6eb789f --- /dev/null +++ b/Content.Server/Changeling/ChangelingSystem.Shop.cs @@ -0,0 +1,24 @@ +using Content.Server.Store.Components; +using Content.Server.Store.Systems; +using Content.Shared.Changeling; +using Content.Shared.Implants.Components; +using Robust.Server.GameStates; +using Robust.Server.Placement; + +namespace Content.Server.Changeling; + +public sealed partial class ChangelingSystem +{ + private void InitializeShop() + { + SubscribeLocalEvent(OnShop); + } + + private void OnShop(EntityUid uid, SubdermalImplantComponent component, ChangelingShopActionEvent args) + { + if(!TryComp(uid, out var store)) + return; + + _storeSystem.ToggleUi(args.Performer, uid, store); + } +} diff --git a/Content.Server/Changeling/ChangelingSystem.cs b/Content.Server/Changeling/ChangelingSystem.cs new file mode 100644 index 0000000000..049a305be5 --- /dev/null +++ b/Content.Server/Changeling/ChangelingSystem.cs @@ -0,0 +1,81 @@ +using Content.Server.Store.Components; +using Content.Server.Store.Systems; +using Content.Shared.Actions; +using Content.Shared.Changeling; +using Content.Shared.Examine; +using Content.Shared.Implants; +using Content.Shared.Implants.Components; + +namespace Content.Server.Changeling; + +public sealed partial class ChangelingSystem : EntitySystem +{ + [Dependency] private readonly SharedActionsSystem _action = default!; + [Dependency] private readonly ChemicalsSystem _chemicalsSystem = default!; + [Dependency] private readonly SharedSubdermalImplantSystem _implantSystem = default!; + [Dependency] private readonly StoreSystem _storeSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + + SubscribeLocalEvent(OnExamine); + + InitializeAbilities(); + InitializeShop(); + } + + #region Handlers + + private void OnInit(EntityUid uid, ChangelingComponent component, ComponentInit args) + { + SetupShop(uid, component); + SetupInitActions(uid, component); + CopyHumanoidData(uid, uid, component); + + _chemicalsSystem.UpdateAlert(uid, component); + component.IsInited = true; + } + + private void OnExamine(EntityUid uid, AbsorbedComponent component, ExaminedEvent args) + { + args.PushMarkup("[color=#A30000]His juices sucked up![/color]"); + } + + #endregion + + #region Helpers + + private void SetupShop(EntityUid uid, ChangelingComponent component) + { + if (component.IsInited) + return; + + var coords = Transform(uid).Coordinates; + var implant = Spawn("ChangelingShopImplant", coords); + + if(!TryComp(implant, out var implantComp)) + return; + + _implantSystem.ForceImplant(uid, implant, implantComp); + + if (!TryComp(implant, out var implantStore)) + return; + + implantStore.Balance.Add("ChangelingPoint", component.StartingPointsBalance); + } + + private void SetupInitActions(EntityUid uid, ChangelingComponent component) + { + if (component.IsInited) + return; + + _action.AddAction(uid, ChangelingAbsorb); + _action.AddAction(uid, ChangelingTransform); + _action.AddAction(uid, ChangelingRegenerate); + } + + #endregion +} diff --git a/Content.Server/Changeling/Objectives/ChangelingConditionsSystem.cs b/Content.Server/Changeling/Objectives/ChangelingConditionsSystem.cs new file mode 100644 index 0000000000..06bee9af8c --- /dev/null +++ b/Content.Server/Changeling/Objectives/ChangelingConditionsSystem.cs @@ -0,0 +1,236 @@ +using System.Linq; +using Content.Server.Changeling.Objectives.Components; +using Content.Server.Forensics; +using Content.Server.Mind; +using Content.Server.Objectives.Components; +using Content.Server.Objectives.Systems; +using Content.Server.Shuttles.Systems; +using Content.Shared.Changeling; +using Content.Shared.Mind; +using Content.Shared.Objectives.Components; +using Robust.Shared.Random; + +namespace Content.Server.Changeling.Objectives; + +public sealed class ChangelingConditionsSystem : EntitySystem +{ + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly TargetObjectiveSystem _target = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; + + public override void Initialize() + { + base.Initialize(); + + // Absorb DNA condition + SubscribeLocalEvent(OnAbsorbDnaAssigned); + SubscribeLocalEvent(OnAbsorbDnaAfterAssigned); + SubscribeLocalEvent(OnAbsorbDnaGetProgress); + + + //Absorb more genomes, than others changelings + SubscribeLocalEvent(OnAbsorbMoreGetProgress); + + //Absorb other changeling + SubscribeLocalEvent(OnAbsorbChangelingAssigned); + SubscribeLocalEvent(OnAbsorbChangelingGetProgress); + + //Escape with identity + SubscribeLocalEvent(OnEscapeWithIdentityAssigned); + SubscribeLocalEvent(OnEscapeWithIdentityGetProgress); + } + + #region AbsorbDNA + + private void OnAbsorbDnaAssigned(EntityUid uid, AbsorbDnaConditionComponent component, ref ObjectiveAssignedEvent args) + { + component.NeedToAbsorb = _random.Next(2, 6); + } + + private void OnAbsorbDnaAfterAssigned(EntityUid uid, AbsorbDnaConditionComponent component, ref ObjectiveAfterAssignEvent args) + { + var title = Loc.GetString("objective-condition-absorb-dna", ("count", component.NeedToAbsorb)); + + _metaData.SetEntityName(uid, title, args.Meta); + } + + private void OnAbsorbDnaGetProgress(EntityUid uid, AbsorbDnaConditionComponent component, ref ObjectiveGetProgressEvent args) + { + args.Progress = GetAbsorbProgress(args.Mind, component.NeedToAbsorb); + } + + private float GetAbsorbProgress(MindComponent mind, int requiredDna) + { + if (!TryComp(mind.CurrentEntity, out var changelingComponent)) + return 0f; + + var absorbed = changelingComponent.AbsorbedEntities.Count - 1; // Because first - it's the owner + + if (requiredDna == absorbed) + return 1f; + + var progress = MathF.Min(absorbed/(float)requiredDna, 1f); + + return progress; + } + + #endregion + + #region AbsorbMoreDNA + + private void OnAbsorbMoreGetProgress(EntityUid uid, AbsorbMoreConditionComponent comp, ref ObjectiveGetProgressEvent args) + { + args.Progress = GetAbsorbMoreProgress(args.Mind); + } + + private float GetAbsorbMoreProgress(MindComponent mind) + { + if (!TryComp(mind.CurrentEntity, out var changelingComponent)) + return 0f; + + var selfAbsorbed = changelingComponent.AbsorbedEntities.Count - 1; // Because first - it's the owner + + var query = EntityQueryEnumerator(); + + List otherAbsorbed = new(); + while (query.MoveNext(out var uid, out var comp)) + { + if (uid == mind.CurrentEntity) + continue; //don't include self + + var absorbed = comp.AbsorbedEntities.Count - 1; + otherAbsorbed.Add(absorbed); + } + + if (otherAbsorbed.Count == 0) + return 1f; + + var isTheMost = otherAbsorbed.Max() < selfAbsorbed; + + return isTheMost ? 1f : 0f; + } + + #endregion + + #region AbsorbChangeling + + private void OnAbsorbChangelingAssigned(EntityUid uid, PickRandomChangelingComponent comp, ref ObjectiveAssignedEvent args) + { + if (!TryComp(uid, out var target)) + { + args.Cancelled = true; + return; + } + + if (target.Target != null) + return; + + foreach (var changelingRule in EntityQuery()) + { + var changelingMinds = changelingRule.ChangelingMinds + .Except(new List { args.MindId }) + .ToList(); + + if (changelingMinds.Count == 0) + { + args.Cancelled = true; + return; + } + + _target.SetTarget(uid, _random.Pick(changelingMinds), target); + } + } + + private void OnAbsorbChangelingGetProgress(EntityUid uid, AbsorbChangelingConditionComponent comp, ref ObjectiveGetProgressEvent args) + { + if (!_target.GetTarget(uid, out var target)) + return; + + args.Progress = GetAbsorbChangelingProgress(args.Mind, target.Value); + } + + private float GetAbsorbChangelingProgress(MindComponent mind, EntityUid target) + { + if(!_mind.TryGetMind(mind.CurrentEntity!.Value, out var selfMindId, out _)) + return 0f; + + if (!TryComp(target, out var targetMind)) + return 0f; + + if (!HasComp(targetMind.CurrentEntity)) + return 0f; + + if (!TryComp(targetMind.CurrentEntity, out var absorbedComponent)) + return 0f; + + return absorbedComponent.AbsorberMind == selfMindId ? 1f : 0f; + } + + #endregion + + #region EscapeWithIdentity + + private void OnEscapeWithIdentityAssigned(EntityUid uid, PickRandomIdentityComponent component, ref ObjectiveAssignedEvent args) + { + if (!TryComp(uid, out var target)) + { + args.Cancelled = true; + return; + } + + if (target.Target != null) + return; + + var allHumans = _mind.GetAliveHumansExcept(args.MindId); + if (allHumans.Count == 0) + { + args.Cancelled = true; + return; + } + + _target.SetTarget(uid, _random.Pick(allHumans), target); + } + + private void OnEscapeWithIdentityGetProgress(EntityUid uid, EscapeWithIdentityConditionComponent component, ref ObjectiveGetProgressEvent args) + { + if (!_target.GetTarget(uid, out var target)) + return; + + args.Progress = GetEscapeWithIdentityProgress(args.Mind, target.Value); + } + + private float GetEscapeWithIdentityProgress(MindComponent mind, EntityUid target) + { + var progress = 0f; + + if (!TryComp(mind.CurrentEntity, out var selfDna)) + return 0f; + + if (!TryComp(target, out var targetMind)) + return 0f; + + if (!TryComp(targetMind.CurrentEntity, out var targetDna)) + return 0f; + + if (!TryComp(mind.CurrentEntity, out var changeling)) + return 0f; + + if (!changeling.AbsorbedEntities.ContainsKey(targetDna.DNA)) + return 0f; + + //Target absorbed by this changeling, so 50% of work is done + progress += 0.5f; + + if (_emergencyShuttle.IsTargetEscaping(mind.CurrentEntity.Value) && selfDna.DNA == targetDna.DNA) + progress += 0.5f; + + if (_emergencyShuttle.ShuttlesLeft) + return progress; + + return progress; + } + + #endregion +} diff --git a/Content.Server/Changeling/Objectives/Components/AbsorbChangelingConditionComponent.cs b/Content.Server/Changeling/Objectives/Components/AbsorbChangelingConditionComponent.cs new file mode 100644 index 0000000000..7db9c8bc3c --- /dev/null +++ b/Content.Server/Changeling/Objectives/Components/AbsorbChangelingConditionComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Server.Changeling.Objectives.Components; + +[RegisterComponent] +public sealed partial class AbsorbChangelingConditionComponent : Component +{ +} diff --git a/Content.Server/Changeling/Objectives/Components/AbsorbDnaConditionComponent.cs b/Content.Server/Changeling/Objectives/Components/AbsorbDnaConditionComponent.cs new file mode 100644 index 0000000000..163e4a9f3d --- /dev/null +++ b/Content.Server/Changeling/Objectives/Components/AbsorbDnaConditionComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Changeling.Objectives.Components; + +[RegisterComponent] +public sealed partial class AbsorbDnaConditionComponent : Component +{ + public int NeedToAbsorb; +} diff --git a/Content.Server/Changeling/Objectives/Components/AbsorbMoreConditionComponent.cs b/Content.Server/Changeling/Objectives/Components/AbsorbMoreConditionComponent.cs new file mode 100644 index 0000000000..3fd0d41f7a --- /dev/null +++ b/Content.Server/Changeling/Objectives/Components/AbsorbMoreConditionComponent.cs @@ -0,0 +1,6 @@ +namespace Content.Server.Changeling.Objectives.Components; + +[RegisterComponent] +public sealed partial class AbsorbMoreConditionComponent: Component +{ +} diff --git a/Content.Server/Changeling/Objectives/Components/EscapeWithIdentityConditionComponent.cs b/Content.Server/Changeling/Objectives/Components/EscapeWithIdentityConditionComponent.cs new file mode 100644 index 0000000000..2a774facd2 --- /dev/null +++ b/Content.Server/Changeling/Objectives/Components/EscapeWithIdentityConditionComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Changeling.Objectives.Components; + +[RegisterComponent] +public sealed partial class EscapeWithIdentityConditionComponent : Component +{ + +} diff --git a/Content.Server/Changeling/Objectives/Components/PickRandomChangelingComponent.cs b/Content.Server/Changeling/Objectives/Components/PickRandomChangelingComponent.cs new file mode 100644 index 0000000000..33338a1e28 --- /dev/null +++ b/Content.Server/Changeling/Objectives/Components/PickRandomChangelingComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Changeling.Objectives.Components; + +[RegisterComponent] +public sealed partial class PickRandomChangelingComponent : Component +{ + +} diff --git a/Content.Server/Changeling/Objectives/Components/PickRandomIdentityComponent.cs b/Content.Server/Changeling/Objectives/Components/PickRandomIdentityComponent.cs new file mode 100644 index 0000000000..af98b5acf3 --- /dev/null +++ b/Content.Server/Changeling/Objectives/Components/PickRandomIdentityComponent.cs @@ -0,0 +1,7 @@ +namespace Content.Server.Changeling.Objectives.Components; + +[RegisterComponent] +public sealed partial class PickRandomIdentityComponent : Component +{ + +} diff --git a/Content.Server/Changeling/TentacleGun.cs b/Content.Server/Changeling/TentacleGun.cs new file mode 100644 index 0000000000..faf803d2e5 --- /dev/null +++ b/Content.Server/Changeling/TentacleGun.cs @@ -0,0 +1,8 @@ +using Content.Shared.Changeling; + +namespace Content.Server.Changeling; + +public sealed class TentacleGun : SharedTentacleGun +{ + +} diff --git a/Content.Server/Changeling/UncloneableComponent.cs b/Content.Server/Changeling/UncloneableComponent.cs new file mode 100644 index 0000000000..4252f1383e --- /dev/null +++ b/Content.Server/Changeling/UncloneableComponent.cs @@ -0,0 +1,9 @@ +namespace Content.Server.Changeling; + +/// +/// This is used for the uncloneable trait. +/// +[RegisterComponent] +public sealed partial class UncloneableComponent : Component +{ +} diff --git a/Content.Server/Chat/Commands/ChangelingChatCommand.cs b/Content.Server/Chat/Commands/ChangelingChatCommand.cs new file mode 100644 index 0000000000..a6224c9779 --- /dev/null +++ b/Content.Server/Chat/Commands/ChangelingChatCommand.cs @@ -0,0 +1,43 @@ +using Content.Server.Chat.Managers; +using Content.Server.Chat.Systems; +using Content.Shared.Administration; +using Content.Shared.Changeling; +using Robust.Shared.Console; +using Robust.Shared.Enums; + +namespace Content.Server.Chat.Commands; + +[AnyCommand] +internal sealed class ChangelingChatCommand : IConsoleCommand +{ + public string Command => "gsay"; + public string Description => "Send changeling Hive message"; + public string Help => "gsay "; + public void Execute(IConsoleShell shell, string argStr, string[] args) + { + if (shell.Player is not { } player) + { + shell.WriteError("This command cannot be run from the server."); + return; + } + + if (player.AttachedEntity is not { Valid: true } entity) + return; + + if (args.Length < 1) + return; + + var entityManager = IoCManager.Resolve(); + + if (!entityManager.HasComponent(entity)) + return; + + + var message = string.Join(" ", args).Trim(); + if (string.IsNullOrEmpty(message)) + return; + + entityManager.System().TrySendInGameOOCMessage(entity, message, + InGameOOCChatType.Changeling, false, shell, player); + } +} diff --git a/Content.Server/Chat/SuicideSystem.cs b/Content.Server/Chat/SuicideSystem.cs index 131d19c523..f0bb4e5d8d 100644 --- a/Content.Server/Chat/SuicideSystem.cs +++ b/Content.Server/Chat/SuicideSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Administration.Logs; using Content.Server.Popups; +using Content.Shared.Changeling; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.Database; @@ -31,6 +32,11 @@ namespace Content.Server.Chat if (_tagSystem.HasTag(victim, "CannotSuicide")) return false; + //Miracle edit + if (TryComp(victim, out var changeling) && changeling.IsRegenerating) + return false; + //Miracle edit end + // Checks to see if the player is dead. if (!TryComp(victim, out var mobState) || _mobState.IsDead(victim, mobState)) return false; diff --git a/Content.Server/Chat/Systems/ChatSystem.cs b/Content.Server/Chat/Systems/ChatSystem.cs index 50cded7c05..8cbf052693 100644 --- a/Content.Server/Chat/Systems/ChatSystem.cs +++ b/Content.Server/Chat/Systems/ChatSystem.cs @@ -14,6 +14,7 @@ using Content.Server._White.PandaSocket.Main; using Content.Shared.ActionBlocker; using Content.Shared.Administration; using Content.Shared.CCVar; +using Content.Shared.Changeling; using Content.Shared.Chat; using Content.Shared.Database; using Content.Shared.Decals; @@ -357,6 +358,9 @@ public sealed partial class ChatSystem : SharedChatSystem case InGameOOCChatType.Looc: SendLOOC(source, player, message, hideChat); break; + case InGameOOCChatType.Changeling: + SendChangelingChat(source, player, message, hideChat); + break; case InGameOOCChatType.Cult: SendCultChat(source, player, message, hideChat); break; @@ -767,6 +771,37 @@ public sealed partial class ChatSystem : SharedChatSystem _adminLogger.Add(LogType.Chat, LogImpact.Low, $"LOOC from {player:Player}: {message}"); } + + private void SendChangelingChat(EntityUid source, ICommonSession player, string message, bool hideChat) + { + if (!TryComp(source, out var changeling)) + return; + + var clients = GetChangelingChatClients(); + + var playerName = changeling.HiveName; + + message = $"{char.ToUpper(message[0])}{message[1..]}"; + + var wrappedMessage = Loc.GetString("chat-manager-send-changeling-chat-wrap-message", + ("player", playerName), + ("message", FormattedMessage.EscapeText(message))); + + _adminLogger.Add(LogType.Chat, LogImpact.Low, $"Changeling chat from {player:Player}-({playerName}): {message}"); + + _chatManager.ChatMessageToMany(ChatChannel.Changeling, message, wrappedMessage, source, + hideChat, false, clients.ToList()); + } + + private IEnumerable GetChangelingChatClients() + { + return Filter.Empty() + .AddWhereAttachedEntity(HasComp) + .AddWhereAttachedEntity(HasComp) + .Recipients + .Select(p => p.Channel); + } + // WD EDIT private void SendCultChat(EntityUid source, ICommonSession player, string message, bool hideChat) { @@ -1243,6 +1278,7 @@ public enum InGameOOCChatType : byte { Looc, Dead, + Changeling, Cult } diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 040c88dffa..59f0b29e80 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.EntitySystems; +using Content.Server.Changeling; using Content.Server.Chat.Systems; using Content.Server.Cloning.Components; using Content.Server.DeviceLinking.Systems; @@ -177,6 +178,19 @@ namespace Content.Server.Cloning if (_configManager.GetCVar(CCVars.BiomassEasyMode)) cloningCost = (int) Math.Round(cloningCost * EasyModeCloningCost); + // Check if they have the uncloneable trait + if (TryComp(bodyToClone, out _)) + { + if (clonePod.ConnectedConsole != null) + { + _chatSystem.TrySendInGameICMessage(clonePod.ConnectedConsole.Value, + Loc.GetString("cloning-console-uncloneable-trait-error"), + InGameICChatType.Speak, false); + } + + return false; + } + // biomass checks var biomassAmount = _material.GetMaterialAmount(uid, clonePod.RequiredMaterial); diff --git a/Content.Server/Content.Server.csproj b/Content.Server/Content.Server.csproj index 30541fdb08..5b7e28e908 100644 --- a/Content.Server/Content.Server.csproj +++ b/Content.Server/Content.Server.csproj @@ -31,7 +31,6 @@ - diff --git a/Content.Server/GameTicking/GameTicker.GamePreset.cs b/Content.Server/GameTicking/GameTicker.GamePreset.cs index 8cf3b7bf01..a308e4e123 100644 --- a/Content.Server/GameTicking/GameTicker.GamePreset.cs +++ b/Content.Server/GameTicking/GameTicker.GamePreset.cs @@ -6,6 +6,7 @@ using Content.Server.GameTicking.Presets; using Content.Server.Maps; using Content.Server.Ghost; using Content.Shared.CCVar; +using Content.Shared.Changeling; using Content.Shared.Damage; using Content.Shared.Damage.Prototypes; using Content.Shared.Database; @@ -229,6 +230,11 @@ namespace Content.Server.GameTicking return false; } + //Miracle edit + if (TryComp(playerEntity, out var changeling) && changeling.IsRegenerating) + return false; + //Miracle edit end + if (HasComp(playerEntity)) return false; diff --git a/Content.Shared/Actions/ActionContainerSystem.cs b/Content.Shared/Actions/ActionContainerSystem.cs index 17bcf11bff..41decf75c9 100644 --- a/Content.Shared/Actions/ActionContainerSystem.cs +++ b/Content.Shared/Actions/ActionContainerSystem.cs @@ -174,6 +174,32 @@ public sealed class ActionContainerSystem : EntitySystem DebugTools.AssertEqual(oldContainer.Container.Count, 0); } + + //Miracle edit + public void TransferAllActionsFiltered( + EntityUid from, + EntityUid to, + ActionsContainerComponent? oldContainer = null, + ActionsContainerComponent? newContainer = null) + { + if (!Resolve(from, ref oldContainer) || !Resolve(to, ref newContainer)) + return; + + foreach (var action in oldContainer.Container.ContainedEntities.ToArray()) + { + var actions = newContainer.Container.ContainedEntities; + + var toAdd = MetaData(action).EntityPrototype?.ID; + + if (actions.Select(act => MetaData(act).EntityPrototype?.ID).Any(ext => toAdd == ext)) + continue; + + TransferAction(action, to, container: newContainer); + } + } + + //Miracle edit end + /// /// Transfers an actions from one container to another, while changing the attached entity. /// diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index 2e9133c008..76d31a2168 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -55,6 +55,7 @@ namespace Content.Shared.Alert VowOfSilence, VowBroken, Essence, + Chemicals, Corporeal, Bleed, Pacified, diff --git a/Content.Shared/Changeling/AbsorbedComponent.cs b/Content.Shared/Changeling/AbsorbedComponent.cs new file mode 100644 index 0000000000..c0239e5fec --- /dev/null +++ b/Content.Shared/Changeling/AbsorbedComponent.cs @@ -0,0 +1,10 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling; + + +[RegisterComponent, NetworkedComponent] +public sealed partial class AbsorbedComponent : Component +{ + public EntityUid AbsorberMind; +} diff --git a/Content.Shared/Changeling/ChangelingComponent.cs b/Content.Shared/Changeling/ChangelingComponent.cs new file mode 100644 index 0000000000..bd4ff0b370 --- /dev/null +++ b/Content.Shared/Changeling/ChangelingComponent.cs @@ -0,0 +1,70 @@ +using Content.Shared.Humanoid; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; + +namespace Content.Shared.Changeling; + + +[RegisterComponent, NetworkedComponent] +public sealed partial class ChangelingComponent : Component +{ + [DataField("chemRegenRate")] + public int ChemicalRegenRate = 2; + + [DataField("chemicalCap")] + public int ChemicalCapacity = 75; + + [ViewVariables(VVAccess.ReadWrite), DataField("chemicalsBalance")] + public int ChemicalsBalance = 20; + + [ViewVariables(VVAccess.ReadWrite), DataField("pointsBalance")] + public int StartingPointsBalance = 10; + + [ViewVariables(VVAccess.ReadOnly)] + public float Accumulator; + + [ViewVariables(VVAccess.ReadOnly)] + public float UpdateDelay = 6f; + + [ViewVariables(VVAccess.ReadOnly)] + public bool IsRegenerating; + + [ViewVariables(VVAccess.ReadOnly)] + public bool IsLesserForm; + + [ViewVariables(VVAccess.ReadOnly)] + public string HiveName; + + [ViewVariables(VVAccess.ReadOnly), DataField("absorbedEntities")] + public Dictionary AbsorbedEntities = new(); + + [ViewVariables(VVAccess.ReadWrite), DataField("AbsorbDNACost")] + public int AbsorbDnaCost; + + [ViewVariables(VVAccess.ReadWrite), DataField("AbsorbDNADelay")] + public float AbsorbDnaDelay = 10f; + + [ViewVariables(VVAccess.ReadWrite), DataField("TransformDelay")] + public float TransformDelay = 2f; + + [ViewVariables(VVAccess.ReadWrite), DataField("RegenerateDelay")] + public float RegenerateDelay = 20f; + + [ViewVariables(VVAccess.ReadWrite), DataField("LesserFormDelay")] + public float LesserFormDelay = 5f; + + public bool IsInited; +} + +public struct HumanoidData +{ + public EntityPrototype EntityPrototype; + + public MetaDataComponent? MetaDataComponent; + + public HumanoidAppearanceComponent AppearanceComponent; + + public string Name; + + public string Dna; +} diff --git a/Content.Shared/Changeling/ChangelingNameGenerator.cs b/Content.Shared/Changeling/ChangelingNameGenerator.cs new file mode 100644 index 0000000000..cc9f8b80ec --- /dev/null +++ b/Content.Shared/Changeling/ChangelingNameGenerator.cs @@ -0,0 +1,63 @@ +using System.Linq; +using Robust.Shared.Random; + +namespace Content.Shared.Changeling; + +public sealed class ChangelingNameGenerator +{ + [Dependency] private readonly IRobustRandom _random = default!; + + private List _used = new(); + + private readonly List _greekAlphabet = new() + { + "Alpha", + "Beta", + "Gamma", + "Delta", + "Epsilon", + "Zeta", + "Eta", + "Theta", + "Iota", + "Kappa", + "Lambda", + "Mu", + "Nu", + "Xi", + "Omicron", + "Pi", + "Rho", + "Sigma", + "Tau", + "Upsilon", + "Phi", + "Chi", + "Psi", + "Omega" + }; + + private string GenWhiteLabelName() + { + var number = _random.Next(0,10000); + return $"HiveMember-{number}"; + } + + public string GetName() + { + _random.Shuffle(_greekAlphabet); + + foreach (var selected in _greekAlphabet.Where(selected => !_used.Contains(selected))) + { + _used.Add(selected); + return selected; + } + + return GenWhiteLabelName(); + } + + public void ClearUsed() + { + _used.Clear(); + } +} diff --git a/Content.Shared/Changeling/ChemicalsSystem.cs b/Content.Shared/Changeling/ChemicalsSystem.cs new file mode 100644 index 0000000000..304892d81b --- /dev/null +++ b/Content.Shared/Changeling/ChemicalsSystem.cs @@ -0,0 +1,88 @@ +using Content.Shared.Alert; +using Content.Shared.Mobs.Systems; +using Robust.Shared.Network; + +namespace Content.Shared.Changeling; + +public sealed class ChemicalsSystem : EntitySystem +{ + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly AlertsSystem _alertsSystem = default!; + [Dependency] private readonly INetManager _net = default!; + + public bool AddChemicals(EntityUid uid, ChangelingComponent component, int quantity) + { + if (_mobStateSystem.IsDead(uid)) + return false; + + var toAdd = quantity; + + if (component.ChemicalsBalance == component.ChemicalCapacity) + return false; + + if (component.ChemicalsBalance + toAdd > component.ChemicalCapacity) + { + var overflow = component.ChemicalsBalance + toAdd - component.ChemicalCapacity; + toAdd -= overflow; + component.ChemicalsBalance += toAdd; + } + + component.ChemicalsBalance += toAdd; + Dirty(uid, component); + + UpdateAlert(uid, component); + + return true; + } + + public bool RemoveChemicals(EntityUid uid, ChangelingComponent component, int quantity) + { + if (_mobStateSystem.IsDead(uid) && !component.IsRegenerating) + return false; + + var toRemove = quantity; + + if (component.ChemicalsBalance == 0) + return false; + + if (component.ChemicalsBalance - toRemove < 0) + return false; + + component.ChemicalsBalance -= toRemove; + Dirty(uid, component); + + UpdateAlert(uid, component); + + return true; + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var uid, out var component)) + { + component.Accumulator += frameTime; + + if(component.Accumulator < component.UpdateDelay) + continue; + + if (component.IsRegenerating) + continue; + + component.Accumulator = 0; + AddChemicals(uid, component, component.ChemicalRegenRate); + } + } + + public void UpdateAlert(EntityUid uid, ChangelingComponent component) + { + if(_net.IsServer) + { + _alertsSystem.ShowAlert(uid, AlertType.Chemicals, + (short) Math.Clamp(Math.Round(component.ChemicalsBalance / 10f), 0, 7)); + } + } +} diff --git a/Content.Shared/Changeling/LesserFormRestrictedComponent.cs b/Content.Shared/Changeling/LesserFormRestrictedComponent.cs new file mode 100644 index 0000000000..80a875aea2 --- /dev/null +++ b/Content.Shared/Changeling/LesserFormRestrictedComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling; + +[RegisterComponent, NetworkedComponent] +public sealed partial class LesserFormRestrictedComponent : Component +{ +} diff --git a/Content.Shared/Changeling/SharedChangeling.cs b/Content.Shared/Changeling/SharedChangeling.cs new file mode 100644 index 0000000000..3346a3819d --- /dev/null +++ b/Content.Shared/Changeling/SharedChangeling.cs @@ -0,0 +1,105 @@ +using Content.Shared.Actions; +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Changeling; + +[Serializable, NetSerializable] +public sealed partial class AbsorbDnaDoAfterEvent : SimpleDoAfterEvent +{ +} + +public sealed partial class AbsorbDnaActionEvent : EntityTargetActionEvent +{ +} + + +[Serializable, NetSerializable] +public sealed partial class TransformDoAfterEvent : SimpleDoAfterEvent +{ + public string SelectedDna; +} + +public sealed partial class TransformActionEvent : InstantActionEvent +{ + +} + +[Serializable, NetSerializable] +public sealed partial class RegenerateDoAfterEvent : SimpleDoAfterEvent +{ +} + +public sealed partial class RegenerateActionEvent : InstantActionEvent +{ + +} + +[Serializable, NetSerializable] +public sealed partial class LesserFormDoAfterEvent : SimpleDoAfterEvent +{ +} + +public sealed partial class LesserFormActionEvent : InstantActionEvent +{ + +} + +public sealed partial class TransformStingActionEvent : EntityTargetActionEvent +{ + +} + +public sealed partial class BlindStingActionEvent : EntityTargetActionEvent +{ + +} + +public sealed partial class MuteStingActionEvent : EntityTargetActionEvent +{ + +} +public sealed partial class HallucinationStingActionEvent : EntityTargetActionEvent +{ + +} + +public sealed partial class CryoStingActionEvent : EntityTargetActionEvent +{ + +} + +public sealed partial class AdrenalineSacsActionEvent : InstantActionEvent +{ + +} + +public sealed partial class FleshmendActionEvent : InstantActionEvent +{ + +} + +public sealed partial class ArmbladeActionEvent : InstantActionEvent +{ + +} + +public sealed partial class OrganicShieldActionEvent : InstantActionEvent +{ + +} + +public sealed partial class ChitinousArmorActionEvent : InstantActionEvent +{ + +} + +public sealed partial class TentacleArmActionEvent : InstantActionEvent +{ + +} + +public sealed partial class ChangelingShopActionEvent : InstantActionEvent +{ + +} diff --git a/Content.Shared/Changeling/SharedChangelingChat.cs b/Content.Shared/Changeling/SharedChangelingChat.cs new file mode 100644 index 0000000000..eaf8520149 --- /dev/null +++ b/Content.Shared/Changeling/SharedChangelingChat.cs @@ -0,0 +1,33 @@ +namespace Content.Shared.Changeling; + +public sealed class SharedChangelingChat : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + } + + private void OnInit(EntityUid uid, ChangelingComponent component, ComponentStartup args) + { + RaiseLocalEvent(new ChangelingUserStart(true)); + } + + private void OnShutdown(EntityUid uid, ChangelingComponent component, ComponentShutdown args) + { + RaiseLocalEvent(new ChangelingUserStart(false)); + } +} + + +public sealed class ChangelingUserStart +{ + public bool Created { get; } + + public ChangelingUserStart(bool state) + { + Created = state; + } +} diff --git a/Content.Shared/Changeling/SharedTentacleGun.cs b/Content.Shared/Changeling/SharedTentacleGun.cs new file mode 100644 index 0000000000..191fb1da7b --- /dev/null +++ b/Content.Shared/Changeling/SharedTentacleGun.cs @@ -0,0 +1,155 @@ +using System.Numerics; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Humanoid; +using Content.Shared.Physics; +using Content.Shared.Projectiles; +using Content.Shared.Stunnable; +using Content.Shared.Throwing; +using Content.Shared.Weapons.Misc; +using Content.Shared.Weapons.Ranged.Components; +using Content.Shared.Weapons.Ranged.Systems; +using Robust.Shared.Input; +using Robust.Shared.Network; +using Robust.Shared.Physics.Components; +using Robust.Shared.Random; +using Robust.Shared.Serialization; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Shared.Changeling; + +public abstract class SharedTentacleGun : EntitySystem +{ + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly INetManager _netManager = default!; + [Dependency] private readonly SharedAppearanceSystem _appearance = default!; + [Dependency] private readonly ThrowingSystem _throwingSystem = default!; + [Dependency] private readonly ITimerManager _timerManager = default!; + [Dependency] private readonly SharedStunSystem _stunSystem = default!; + [Dependency] private readonly SharedHandsSystem _handsSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnTentacleShot); + SubscribeLocalEvent(OnTentacleCollide); + } + + private void OnTentacleShot(EntityUid uid, TentacleGunComponent component, ref GunShotEvent args) + { + foreach (var (shotUid, _) in args.Ammo) + { + if (!HasComp(shotUid)) + continue; + + Dirty(uid, component); + var visuals = EnsureComp(shotUid.Value); + visuals.Sprite = + new SpriteSpecifier.Rsi(new ResPath("Objects/Weapons/Guns/Launchers/tentacle_gun.rsi"), "frope"); + visuals.OffsetA = new Vector2(0f, 0.5f); + visuals.Target = uid; + Dirty(shotUid.Value, visuals); + } + + TryComp(uid, out var appearance); + _appearance.SetData(uid, SharedTetherGunSystem.TetherVisualsStatus.Key, false, appearance); + } + + private void OnTentacleCollide(EntityUid uid, TentacleProjectileComponent component, ref ProjectileEmbedEvent args) + { + if (!_timing.IsFirstTimePredicted) + return; + + if (!HasComp(args.Weapon)) + { + QueueDel(uid); + return; + } + + if (!TryComp(args.Weapon, out var gun)) + { + QueueDel(uid); + return; + } + + if (!HasComp(args.Embedded)) + { + DeleteProjectile(uid); + return; + } + + switch (gun.SelectedMode) + { + case SelectiveFire.PullMob when !PullMob(args): + DeleteProjectile(uid); + return; + case SelectiveFire.PullMob: + _timerManager.AddTimer(new Timer(1500, false, () => + { + DeleteProjectile(uid); + })); + break; + case SelectiveFire.PullItem: + PullItem(args); + DeleteProjectile(uid); + break; + } + } + + private void PullItem(ProjectileEmbedEvent args) + { + foreach (var activeItem in _handsSystem.EnumerateHeld(args.Embedded)) + { + if(!TryComp(activeItem, out var physicsComponent)) + return; + + var coords = Transform(args.Embedded).Coordinates; + _handsSystem.TryDrop(args.Embedded, coords); + + var force = physicsComponent.Mass * 2.5f / 2; + + _throwingSystem.TryThrow(activeItem, Transform(args.Shooter!.Value).Coordinates, force); + break; + } + } + + private bool PullMob(ProjectileEmbedEvent args) + { + var stunTime = _random.Next(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(8)); + + if (!_stunSystem.TryParalyze(args.Embedded, stunTime, true)) + return false; + + _throwingSystem.TryThrow(args.Embedded, Transform(args.Shooter!.Value).Coordinates, 5f); + + return true; + } + + private void DeleteProjectile(EntityUid projUid) + { + TryComp(projUid, out var appearance); + + if (!Deleted(projUid)) + { + if (_netManager.IsServer) + { + QueueDel(projUid); + } + } + + _appearance.SetData(projUid, SharedTetherGunSystem.TetherVisualsStatus.Key, true, appearance); + } + + [Serializable, NetSerializable] + protected sealed class RequestTentacleMessage : EntityEventArgs + { + public BoundKeyFunction Key; + + public RequestTentacleMessage(BoundKeyFunction key) + { + Key = key; + } + } +} diff --git a/Content.Shared/Changeling/TentacleGunComponent.cs b/Content.Shared/Changeling/TentacleGunComponent.cs new file mode 100644 index 0000000000..efa8f4c50b --- /dev/null +++ b/Content.Shared/Changeling/TentacleGunComponent.cs @@ -0,0 +1,8 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling; + +[RegisterComponent, NetworkedComponent] +public sealed partial class TentacleGunComponent : Component +{ +} diff --git a/Content.Shared/Changeling/TentacleProjectileComponent.cs b/Content.Shared/Changeling/TentacleProjectileComponent.cs new file mode 100644 index 0000000000..2088ad332e --- /dev/null +++ b/Content.Shared/Changeling/TentacleProjectileComponent.cs @@ -0,0 +1,9 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling; + + +[RegisterComponent, NetworkedComponent] +public sealed partial class TentacleProjectileComponent : Component +{ +} diff --git a/Content.Shared/Chat/ChatChannel.cs b/Content.Shared/Chat/ChatChannel.cs index 67f6b64a48..66fedea558 100644 --- a/Content.Shared/Chat/ChatChannel.cs +++ b/Content.Shared/Chat/ChatChannel.cs @@ -75,17 +75,22 @@ namespace Content.Shared.Chat AdminChat = 1 << 12, /// - /// Unspecified. + /// Changeling /// - Unspecified = 1 << 13, + Changeling = 1 << 13, //WD EDIT Cult = 1 << 14, + /// + /// Unspecified. + /// + Unspecified = 1 << 15, + /// /// Channels considered to be IC. /// - IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual, + IC = Local | Whisper | Radio | Dead | Emotes | Damage | Visual | Changeling, AdminRelated = Admin | AdminAlert | AdminChat, } diff --git a/Content.Shared/Chat/ChatChannelExtensions.cs b/Content.Shared/Chat/ChatChannelExtensions.cs index 4b6267559f..374a9437b2 100644 --- a/Content.Shared/Chat/ChatChannelExtensions.cs +++ b/Content.Shared/Chat/ChatChannelExtensions.cs @@ -15,6 +15,7 @@ public static class ChatChannelExtensions ChatChannel.AdminAlert => Color.Red, ChatChannel.AdminChat => Color.HotPink, ChatChannel.Whisper => Color.DarkGray, + ChatChannel.Changeling => Color.Purple, ChatChannel.Cult => Color.DarkRed, // WD EDIT _ => Color.LightGray }; diff --git a/Content.Shared/Chat/ChatSelectChannel.cs b/Content.Shared/Chat/ChatSelectChannel.cs index 4a7ca5aec8..1871ed0b71 100644 --- a/Content.Shared/Chat/ChatSelectChannel.cs +++ b/Content.Shared/Chat/ChatSelectChannel.cs @@ -53,6 +53,8 @@ /// Admin = ChatChannel.AdminChat, + Changeling = ChatChannel.Changeling, + Console = ChatChannel.Unspecified } } diff --git a/Content.Shared/Chat/SharedChatSystem.cs b/Content.Shared/Chat/SharedChatSystem.cs index d86053c43d..0338f36e8e 100644 --- a/Content.Shared/Chat/SharedChatSystem.cs +++ b/Content.Shared/Chat/SharedChatSystem.cs @@ -21,6 +21,7 @@ public abstract class SharedChatSystem : EntitySystem public const char AdminPrefix = ']'; public const char WhisperPrefix = ','; public const char DefaultChannelKey = 'h'; + public const char ChangelingPrefix = '<'; public const char CultPrefix = '^'; // WD EDIT [ValidatePrototypeId] diff --git a/Content.Shared/Eye/Blinding/Components/BlurryVisionComponent.cs b/Content.Shared/Eye/Blinding/Components/BlurryVisionComponent.cs index faff4b9e52..5e90f0cc74 100644 --- a/Content.Shared/Eye/Blinding/Components/BlurryVisionComponent.cs +++ b/Content.Shared/Eye/Blinding/Components/BlurryVisionComponent.cs @@ -8,20 +8,20 @@ namespace Content.Shared.Eye.Blinding.Components; /// [RegisterComponent] [NetworkedComponent, AutoGenerateComponentState] -[Access(typeof(BlurryVisionSystem))] +// [Access(typeof(BlurryVisionSystem))] public sealed partial class BlurryVisionComponent : Component { /// /// Amount of "blurring". Also modifies examine ranges. /// [ViewVariables(VVAccess.ReadWrite), DataField("magnitude"), AutoNetworkedField] - public float Magnitude; + public float Magnitude = 4f; /// /// Exponent that controls the magnitude of the effect. /// [ViewVariables(VVAccess.ReadWrite), DataField("correctionPower"), AutoNetworkedField] - public float CorrectionPower; + public float CorrectionPower = 2f; public const float MaxMagnitude = 6; public const float DefaultCorrectionPower = 2f; diff --git a/Content.Shared/Implants/Components/ImplantedComponent.cs b/Content.Shared/Implants/Components/ImplantedComponent.cs index 727213907a..2744d0291b 100644 --- a/Content.Shared/Implants/Components/ImplantedComponent.cs +++ b/Content.Shared/Implants/Components/ImplantedComponent.cs @@ -10,5 +10,6 @@ namespace Content.Shared.Implants.Components; [RegisterComponent, NetworkedComponent] public sealed partial class ImplantedComponent : Component { + [ViewVariables(VVAccess.ReadOnly)] public Container ImplantContainer = default!; } diff --git a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs index 8c3aa00561..7b1893b7a9 100644 --- a/Content.Shared/Implants/SharedSubdermalImplantSystem.cs +++ b/Content.Shared/Implants/SharedSubdermalImplantSystem.cs @@ -117,7 +117,8 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem /// The entity to be implanted /// The implant /// The implant component - public void ForceImplant(EntityUid target, EntityUid implant, SubdermalImplantComponent component) + /// Should we force inserting in container + public void ForceImplant(EntityUid target, EntityUid implant, SubdermalImplantComponent component, bool containerForce = false) { //If the target doesn't have the implanted component, add it. var implantedComp = EnsureComp(target); @@ -175,6 +176,48 @@ public abstract class SharedSubdermalImplantSystem : EntitySystem RaiseLocalEvent(implant, relayEv); } } + + //Miracle edit + + /// + /// Transfers all implants from one entity to another. + /// + /// + /// This method transfers all implants from a donor entity to a recipient entity. + /// Implants are moved from the donor's implant container to the recipient's implant container. + /// + /// The entity from which implants will be transferred. + /// The entity to which implants will be transferred. + public void TransferImplants(EntityUid donor, EntityUid recipient) + { + // Check if the donor has an ImplantedComponent, indicating the presence of implants + if (!TryComp(donor, out var donorImplanted)) + return; + + // Get the implant containers for both the donor and recipient entities + var donorImplantContainer = donorImplanted.ImplantContainer; + + // Get all implants from the donor's implant container + var donorImplants = donorImplantContainer.ContainedEntities.ToArray(); + + // Transfer each implant from the donor to the recipient + foreach (var donorImplant in donorImplants) + { + // Check for any conditions or filters before transferring (if needed) + // For instance, verifying if the recipient can receive specific implants, etc. + + // Remove the implant from the donor's implant container + _container.Remove(donorImplant, donorImplantContainer, force: true); + + if(!TryComp(donorImplant, out var subdermal)) + return; + + // Insert the implant into the recipient's implant container + ForceImplant(recipient, donorImplant, subdermal, true); + } + } + + //Miracle edit end } public sealed class ImplantRelayEvent where T : notnull diff --git a/Content.Shared/Input/ContentKeyFunctions.cs b/Content.Shared/Input/ContentKeyFunctions.cs index 02ab921ed7..37876fd629 100644 --- a/Content.Shared/Input/ContentKeyFunctions.cs +++ b/Content.Shared/Input/ContentKeyFunctions.cs @@ -15,6 +15,7 @@ namespace Content.Shared.Input public static readonly BoundKeyFunction FocusLocalChat = "FocusLocalChatWindow"; public static readonly BoundKeyFunction FocusEmote = "FocusEmote"; public static readonly BoundKeyFunction FocusWhisperChat = "FocusWhisperChatWindow"; + public static readonly BoundKeyFunction FocusChangelingChat = "FocusChangelingChatWindow"; public static readonly BoundKeyFunction FocusCultChat = "FocusCultChatWindow"; public static readonly BoundKeyFunction FocusRadio = "FocusRadioWindow"; public static readonly BoundKeyFunction FocusLOOC = "FocusLOOCWindow"; diff --git a/Content.Shared/IoC/SharedContentIoC.cs b/Content.Shared/IoC/SharedContentIoC.cs index c08410cab9..f5ae9106c1 100644 --- a/Content.Shared/IoC/SharedContentIoC.cs +++ b/Content.Shared/IoC/SharedContentIoC.cs @@ -1,4 +1,5 @@ -using Content.Shared.Humanoid.Markings; +using Content.Shared.Changeling; +using Content.Shared.Humanoid.Markings; using Content.Shared.Localizations; using Content.Shared._White.Cult.Systems; @@ -10,7 +11,7 @@ namespace Content.Shared.IoC { IoCManager.Register(); IoCManager.Register(); - + IoCManager.Register(); // WD EDIT IoCManager.Register(); // WD EDIT END diff --git a/Content.Shared/Miracle/UI/ListViewBUI.cs b/Content.Shared/Miracle/UI/ListViewBUI.cs new file mode 100644 index 0000000000..2039093510 --- /dev/null +++ b/Content.Shared/Miracle/UI/ListViewBUI.cs @@ -0,0 +1,31 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Miracle.UI; + +[Serializable, NetSerializable] +public enum ListViewSelectorUiKeyChangeling +{ + Key +} + +[Serializable, NetSerializable] +public sealed class ListViewBuiState : BoundUserInterfaceState +{ + public Dictionary Items { get; set; } + + public ListViewBuiState(Dictionary items) + { + Items = items; + } +} + +[Serializable, NetSerializable] +public sealed class ListViewItemSelectedMessage : BoundUserInterfaceMessage +{ + public string SelectedItem { get; private set; } + + public ListViewItemSelectedMessage(string selectedItem) + { + SelectedItem = selectedItem; + } +} diff --git a/Content.Shared/Miracle/UI/TransformStingBUI.cs b/Content.Shared/Miracle/UI/TransformStingBUI.cs new file mode 100644 index 0000000000..f247fe4fb3 --- /dev/null +++ b/Content.Shared/Miracle/UI/TransformStingBUI.cs @@ -0,0 +1,35 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Miracle.UI; + +[Serializable, NetSerializable] +public enum TransformStingSelectorUiKey +{ + Key +} + +[Serializable, NetSerializable] +public sealed class TransformStingBuiState : BoundUserInterfaceState +{ + public Dictionary Items { get; set; } + public NetEntity Target { get; set; } + + public TransformStingBuiState(Dictionary items, NetEntity target) + { + Items = items; + Target = target; + } +} + +[Serializable, NetSerializable] +public sealed class TransformStingItemSelectedMessage : BoundUserInterfaceMessage +{ + public string SelectedItem { get; private set; } + public NetEntity Target { get; private set; } + + public TransformStingItemSelectedMessage(string selectedItem, NetEntity target) + { + SelectedItem = selectedItem; + Target = target; + } +} diff --git a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs index 7035f17978..305d5a8df4 100644 --- a/Content.Shared/Weapons/Ranged/Components/GunComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/GunComponent.cs @@ -186,4 +186,9 @@ public enum SelectiveFire : byte SemiAuto = 1 << 0, Burst = 1 << 1, FullAuto = 1 << 2, // Not in the building! + + //Miracle edit + PullItem = 1 << 3, + PullMob = 1 << 4 + //Miracle edit end } diff --git a/Content.Shared/Weapons/Ranged/Components/RechargeBasicEntityAmmoComponent.cs b/Content.Shared/Weapons/Ranged/Components/RechargeBasicEntityAmmoComponent.cs index 923f95e207..a1f8b4e96e 100644 --- a/Content.Shared/Weapons/Ranged/Components/RechargeBasicEntityAmmoComponent.cs +++ b/Content.Shared/Weapons/Ranged/Components/RechargeBasicEntityAmmoComponent.cs @@ -22,6 +22,12 @@ public sealed partial class RechargeBasicEntityAmmoComponent : Component Params = AudioParams.Default.WithVolume(-5f) }; + //Miracle edit + [DataField("playRechargeSound")] + [AutoNetworkedField] + public bool PlayRechargeSound = true; + //Miracle edit end + [ViewVariables(VVAccess.ReadWrite), DataField("nextCharge", customTypeSerializer:typeof(TimeOffsetSerializer)), AutoNetworkedField] diff --git a/Content.Shared/Weapons/Ranged/Systems/RechargeBasicEntityAmmoSystem.cs b/Content.Shared/Weapons/Ranged/Systems/RechargeBasicEntityAmmoSystem.cs index 536f3da811..e796fb99c5 100644 --- a/Content.Shared/Weapons/Ranged/Systems/RechargeBasicEntityAmmoSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/RechargeBasicEntityAmmoSystem.cs @@ -51,7 +51,8 @@ public sealed class RechargeBasicEntityAmmoSystem : EntitySystem { // We don't predict this because occasionally on client it may not play. // PlayPredicted will still be predicted on the client. - if (_netManager.IsServer) + //Miracle edit start/end + if (_netManager.IsServer && recharge.PlayRechargeSound) _audio.PlayPvs(recharge.RechargeSound, uid); } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 199b90ab7f..e09d8a4e5f 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -285,6 +285,10 @@ public abstract partial class SharedGunSystem : EntitySystem // Don't do this in the loop so we still reset NextFire. switch (gun.SelectedMode) { + //Miracle edit + case SelectiveFire.PullItem: + case SelectiveFire.PullMob: + //Miracle edit end case SelectiveFire.SemiAuto: shots = Math.Min(shots, 1 - gun.ShotCounter); break; diff --git a/Resources/Audio/Ambience/Antag/changeling_start.ogg b/Resources/Audio/Ambience/Antag/changeling_start.ogg new file mode 100644 index 0000000000000000000000000000000000000000..05c86a6dcd71c30a1fc2ba7af0442e5d3b26aa4d GIT binary patch literal 47676 zcmce7by$?o_vpLi(v5UUEhR18xkxP`EZr@Qgv!!Lx3qK$(hY*Nbc3`?gCIx<0(bd* ze&64H?tSiapZnJxW`}wA%$zwj=bU#KH0|tk02JWA3MBfO#Y0y#*^wAT2l8?CuypWz z_yWYF4*>Vb0=)kBcR5J&Vdj6AhnXPc6K|KkF@EvG$p4gn)PK?7BB9SLd~Ahzc?5X) zczIEg&$7smj;pQLOA8MhNm*-GOB)7RZ)*oH2iF%2f;_y)A3hO2AqI8>bu9%~D@`{~ zT^koSR}TwkF@9cN5gtB19$p~`9TJIxtgfOYKQEskHy*nrR=g_Uxvb(Gw2&}4!h@Gzl>=DC)KD)&ydXCcM&QTJU2WN(YX zZqyx%1=))e-AVF@=n@IW>yN1A`XV-U0^0}=or zFP}Z?8#_KBRUI}k1OS9G?xf0zq-wP^BU8+HU_3)~q_P1pa-2$dkxEptL7c5aE~|e5 zdu9PgWu3#9I8!6^L^Q-{?Y|b84C=&O>AgS0e7c(^c0}6lu z05s${T;*C)^Fe4SEd=8A*7@J&g;>S#4N&~g=J7vbKr&2-L1~?UBrufpAEX{g zd^G>Eg#F}rYCpR-xNuvwn7 zF-fyoZFDi2H!)f8{$J}qtRv6>MBERg)R9#su1E#@ALS%~073v!C=&Hu1*wlX7Q-Y~ zhcr9Cv{Jv!vbS{q3_&UeDK%t46#Sz$X2Y}+huqQ!Wmi;0m6TUhIA)eO{NF1|K4T>S zU;zN_pgWDSJI$c`16Th@m;n`F004Py8buGT!6ZR7B#$!+6TgkMg}rs2yZlE9$RQ79 z&{G0ghmoQ{c9JDjX9U&%AM1l)Q2?az5hf=cmolR4ED#^1~I1qy-Jo z27pLD6-W^&49ZuUE)RtardX*!wz54mLkLy};s<^4&pm{d=g7#75v z7{&qVH-JTP7wK9#a4$^5Vz~#hjbb{(Q!Kg5=iuC^BIU>}U?ukT2YBd2F$aVVAI+z{8TQ!#r z9}(?O4;Zc8N%tg3@=QUZ@`l0Hs^&7`l|sqWaEGda1bFnr;?aC|FgRCvzX3douRq;0 zntNdqUX5HsCt7q69+FghVG6H&KowPmM8zvSm@5u#O`G@UsX90HI}G^RL|9WvQ^RT99fH1{=Rv=N-)Nhr3< zdJ$c1;EOo1T zkPId?lII6Jf$lJ*R5W5{IBD@OAW`-2=0!gN0P}7Tz|bAauVp_J1u$p;2-zXgVv0~G z1e_n5BF7HVi%}g)4@u%zZiQyb94a87~@|0;fdvRkLJO%9GU6?~v(KHNFj5GjeNWZR8bYu8~sQdI`j(m;CN4|wfJ_sdp#tzb>!5-ESWh)Me zgKp;)CBdAEi%12wi;&j$g`p6JG?`*z9VAsqRK-Q}@R-JuSvWUKBNA0@3h@Ifay50N z$v`>ykZU;cZD+!x`H%nzWFQIZT-Bdx{+ba-?v(k$6on*Ldw0y(3J+=?K-!2pS>h}a6b8ii3@ zc_SPND4DgaYP_H^dLda%?i5|SjkIu~g*7X6Q8Hw@v4Pz8=5mzmg4QemAu@JR^io(6 zQWa+-w`fR;=eX){Hq!A!k)|Q10`b(4gL2!9%c_(&YC)k@{p3*9@@Wxpu=hVGdmuYm3xYIfEqB zM1|b2C=VN$T9Abt6G@QbzXVxj3yd>j=M;~_n9B?2wVl|DsnlX}2J{0CT4$&NTw0%| zKx512% zn@mUrWRUrhIj-Eesza5^17J?LIX2`hYoc@Cg&9?l2{LT}(VP5utfWvO3#4#+T;QwX<{2;t>%3=!7 zQPG_kSPUOhf>C?}$RtI!pTcTf6qC^k31A3KcmNQ}?Peq0BAC(Z0T4L-n3D~I4bC^Hn9^RfPvUK+k$29gPUN>KlXr3?D9xG7by&z5w z0x5z)v5Bn4RrhI-p$O@nzKDX0i&Eq`7`?}3F%dDNqrcc3WKV!q!L20bBc7K*yTmKFmR4nf*c!5Q4;boIR!blI0Xu2 z@q)>5K#Gw&w>*Uqssbt2K*qcxQaN_UFeJMy#YxCLm#Qm=g`@z+o{z+WK3Oeu>D{4B72h14kMP&V}JB;LMZp&sUzRSPK79nr~ z0ur9I(i01Bnkf( z85x;>It<8GM&`d>!;r6M59Q%C>>Q*1!p$;n|@dYTy(BcH| zdt4rhF|^$FTK~evD3L-3rras(A;d3)g&*hny#ecQ54e?=Ds)CeQEIKxm9#g=-3e}Q z*8P0rD6wiPh_~=Ug2JQ#+}3X)6Ev;{bGCMb-^g$`R3W~FMT_jP)A$Wy^wWzkZFc5|3e;IE)) zgQ1L5#fLbwpt3d+-Rx>fS7{u2v+H!*;Q`?;_}{y9Jcs$|IFA4-f5jq+%P&$~n6Ft^ z>x|eAV9J>-2gxD^^&w;61nE3Zj4D(vQQysq&D%_PytuO?CGYoX0Y!bqn~ZB{%oc zk_KoSk|!=Y#oqW#y+r40k~FJ=8#9WjBX7p@RI_A5yR2|Ges+ZRFPqYiBK4~peuXbu zeG`ue`l3)mKJ}vP3XT5m_`sPGlX8M?YGfy@$=DpH8S>`KE$dctc=Z>Ju4($&`7ani z*Y0;*Tt_Iw>u!JaysV5cZf)9r1F07M_u0Zm1ehp6fWwMB(}BQKy78j2`6C+9!NC>U zZ#OW!T|!)wCVYcg@6R0uXn#-tY}+BIM_RiQquR7NQw1w-Q}n%?y0l5tZG9p(8(%1; zsK!14(#i_KB6B36^d72+i|)Oo^l@RviaA%M;82rXufMM$I}oU}rqRBNz4li@_029K zKj$BqL8mLqFIzF{$3B(R=o9B14)_<&be@BZYo6iJelXRL@_J#09%`jiXTW?A=2IW7 zK!FO-h$rpZ&=!Gsh;8bx4RgY>PKFw=8cWCylo;nj$nw9b_BLk_4_xFrkDY$4(g`NP z(nURn!buti5&=?i)gLxbWXpJp>W}Z2ats{3J7%0ylc}Yoq$I#9L{&FfKcx+WUEQaZU5{>tU7F&=y?xuI;yMU-B2T&&MVnRm@m2K38+g+PPtXeJZ1u#O>Xx zX=d|uZ;qU4C&2*V6e%>MQY_}{z$?TBki;B2Z#snAeu)~5`BIFU6V&-MnsV~0Vw;9R zF|u`yvPiJYq&28)L*r*3d?Qs;UeLe~OHpo(dF?&Q7M)`A>)cnl+rNW!f}Z}#M3=p( zDg^*xiXtk2rs(bj6^#zaQ?rZ;JoMVM;b~g9GpQEedb}KvvvuU)o2g!Y&0s?+LzPpR z!4(9id=A1JRPl;EoRKK|4PsqpPT!{}^U!#jSMO2TARgG}3D9MzPF6`IC0yzDg0OI9 zXM%00Me^;{a7teitS8AU*(7sQzLsk8D{x+1tdVG}f6v+V^9eYOpLsW+C7Ey(?r zm2vkQI&%fqIiimpeNZe{^G&0ya73%g>^-@h>h%Mw)7PXe8a%TR)lG_pC%T zIPh=NjXA_?S8{mR^I`}YIl?3M2i+Z&e#q_^oeaDSFp6ugdpGD?FR`?#*n%UicG@te zV;Z63W)4CV?fb@!Bl4oRM^o#A-rSixXHLwat9U_3t|9~4N*ErE zdr*hb_c8Lus8L8QYG1#Mf}-rvtx@vlULX!;dYS}X|cOEm=(4kmV1lJ*YOFN&XR zH*&)+U26J@zYNH{IgS?+;Pp#>c}*AlHTz7`>vl!vd9w0PIBEPZ)NzTCzgN}kk3nlY zdybgQ(p%=#+**zPlX^{0UW5%qq$xcUexKs;_WS@VhR|qOb6Q0jdlv#(KT3XYDVxL0 z8B>>nzKu(t^k}JUjq}k+GwYeB;qgZt`L^9;14|E#lN$gmA%pURx#Y%ZPmts58{d4g z-fw#fAEv$?Hdmz#=3|~Z4O7wFu+pRi;HO|jz#_l&iVEnM=DU=Ls`z|t+sfVo=We?| zB1rl(G_WF>bYm#>s1W?k=X4S>pxmoUFnc#*%K_63X!A_ETfhH+6&kedpd+i270rf~ zS0)}byZ&hK&Brrm;g&RFe~laC`HWTS&W`??B&zF2*90KKgWn5KLBXSB;?Tp@kfD{( za2>y7v232zCV><9AfyI(GcV8>~fNw!w%M+*123Td(A+D;|Mf`b<9Fyv= zUW3*aIkSU@DZ3j}YYd1dbC_xG>~trorRJad2$96#VIw?BnF~rKtb@jK@`ukBPV`YN z#OcTvDfj6E^7`@(j3f03xSFoTFmh0TEPp;iuT0WnUE<(nA`tBq+r4&|0Nz|hQvksH zWeV^Nr5j~ZK1CHvVh0`z_LGb|@j>Pp1vj_LgkLB%PGt~Du=GyRPf*)%a*r|fX#f2k zdiViuG#Q}Y$>|{Eg$b*LVVl@L7NJ=dDbff@+@@f5 zI7FlIId+Azd$%HwUb@|di|v1lwL=MBrM>{Qdt-WIxY3nY#D5VG+Q8fY7}51kId$8> zsaZd+ZTnI)Huo^2CSyY83D;Ij;1jd>70fgUsOc&im)$B8_=Fb=`~{Gb+7w(%?A;d` zi&kaRd7O?vy-9s}bZaHs-!s@$lZjj8E@(HLE*@F1gfrJ2bk^Zf!D)v$U!gAF)>69n z2X1omcW6Xf91Z+0@3B0nMl896C2 z-M4-s*F+a)O-JdUfSHh7Y{{zH3EZNwoJt$KSZCq-`m3?7;oVVB4+dk4*sytLGTw*$rNTzlJ}n81$p=z%-$Z+NFgP zp(?81INr5n}#k*wP~&xk3LLCFMjNf1)ncB7x5v&$?RPn+Gx{9YO& zJR%^*&FUXdCLRvX3>}XCbn~I7p`!;SlAy%Ou>Murf`W{X>ezl~Unj`?bJ|u5j~->L z3`6HV!Cal(Q=Q@b<~+)J?>e!p4=7-BBCjsYKQ_Fk6vm9*v2PGR8Zmtl0DDPM>Cw^S zT0+|+j+D)vuZRsY5e6|1G|F-YV4ngUkiXY;6o#ZY*go}bsk&*N9LV!(s}XyyHMgE0?%KX zd~hhDUdM0W*#F@AIR$`2%{;z;;-bM~YP#&eGVX+Mp!JkYN4e@a8ahuUn&HVJ&oHy; z$GGtx%?7VW(tnDE{r`z4XK2GNN6wZHrAPs$1@HsVq;XQ7oWOhXSAkk%E zde3xVyd{W@kR+R;oj*K;)OvJILYVc!*fguY!dvC3tN}6d1iu2662Qj-6liN*N{qzQ zGO1|!^n%~CSf(oscJ-l#80o1y9fvzBbSNqG49+!O5eS z>3ZBMo;E-4*zsPfh;>6z9a8%N!x?qHKtPBPlHnNku8I~+GHiXp zl4YCWzy4RqOZc)L$dh)uPx^o=kibsxv}c_t4A=&9n6jHDOl`EEuzMc-rAK{T_?{A! zd9!6-?3q`2iDpF4mc;b(kfHdzX5J+qD6DA03nc;sb?8?X@yi6ydUXp$#hX`j8_?j< z^&`G*{@$}6>ZyY^Mb7^6?ho!v)ZV#yr;M}2hL*pIO})1^!znH0^UT~DWaLP!KtHa! z@?0nS)Os)x!j4Cy&XnJV=9`zYU(2J3+s;+IDknm7@Oxv`?1QRWlTs;vb?bs)?3&-b zCl%UAMT_Sl&(opmCrY_*3|AX4)0OA&hkpuOl676du}Bl%J*juk!=O>cM3<)P2BJZt zpFa6K)|oA7<6h;dT;it4xOtZqp3rk~D(3;!S@*7g^0Nn(l0Y5nFl}N+BV!;oQz$+u zGoJD5Gvyna+2xEm*zCwBqY45VWl|Kj6@kP_0*1i!XNS_;1E)@xQsnGG*KS_FGL(yE zjdP{Z@*_DTQ8G5Y&99CzxI&t{VvNOkT_k-8`9m&>H5l3cB%j}|AZ84d0hHNxV$@&l!HjdSUpGIlf0}ab3?fxvSEtDiVFIrhk!Y@> zqEWd3buDjU5zNDRw&A4SaZVD#uU^HSTcpH!#z6MRd(Olu08%wftS4?qs07iWeFh|j zJYm-xQn^CV5^-R4XML0vc;ZQ39k)~l(ff-2+|^o^bnLB!~Av&T<0N^K^QDudy6Yc^BH zU<#+gBnEZUg0vtXhrt^M{7=*SQ5C#S3vox=-W>QneT`1~VVT|^KDRK_JBG?Ek=Y>~ z>oECuOk=<5`{(*99{^3B9-+4A|G7s-7E^|pr0BFYwBvkwUgm_K8pxI4007%9PYr;q zu_5;?7YjY7u?y1m^!$lP`JTGqHR&*EQD0mt4Q<;ASRJHSZ0gW z=qyZMoBE>=?bkr&PJ>#-UfU6@^jG@5{}j|I4wJI#`M5=3^9%n)eWKDdhtpBw`~Zn| z8`p`fg9@IK97#fTze<3nHjBGXeDNQV^mipy4$Q#a?jcU}>?&gA zC4fn14#-cgZK)it?!}dFTR*18KT|i*Mf)W_p`x(Vg%ZU^xOSC}%qLK_#*_diolVpX=B#UYfvkgRgP5k@bwGB5n37W`? z+kJm|d5MQh(?jG|706=F0$`vvh^?TFOk#?j=Ux$1C~$@qp3gICu{_I{$`Z-3O(ZVD z#T(x2!A@s;Pyb}38x0h5v$Yjw#^sm01L;8fGH?A<3Lxzvdi#o7l9(BTR>cqxy{gAa zpqbn(kj)TFxPWqhJ_@BeEJ>;X0~9IFCXb#g>@S1+?Aqe}=<0!|`%{R&$$V?L#>|Q$ zr8<2fE-E8Bm*iU71@l*g0>R|a<8TTrx#wX%H7T>ND>Kr-4MGXRn; zuBd$7xNtka0-r+t_h1!K^>D2E@4@QBjTPsYPA<}#8Y=oaFl%Eg5p7NFXLfoHYPx#r zS}HoqnlMc*HES&`TYCdzGc#i|D+>#lx`vje7!-LHYievMqOD?LZN)Xn68#D!DPvxP zn0XS$+7_X%^!NI6bxWYLc6TRP-emB$bMSNYGJM_fC5HX|7xp0)LYaw8oc(6^nw>;8 z_fN;_C4jI+?fW`i zs4kd&b-6Hp;Y)2n2f?g9J2Uf!PGJ+bP+jyg{e!(tYuAyvY0@DLa|t#sR(FYMpks-jofXFW%NLSB@^&^a z>(-d7E%K1aJXrV@Rn$Q{aMeeQ_*pc$`?biMK4K%tC=xz1nA1cn`0f=Z@?A6;E|Xz1A6_7C$M!0UFye|I)*4uzWe)Q0>uQ?*nkWO5ScEb!tYBh zMa3`p=-3iHh9y|N_+x4d0l+TP*zm0prjQFFR|8&0xj;iYbb!4410i8SQ113JFd145 zm?V+<|8=>m2M^Jp=sn%;Ki&OfWQI|J_{)M1Fd&`}X~SQvTD|Q!W~N>Gw5A{KoZCF# zJ#aRuqc$)ovd-w9Stzt)A^P!Y_msznCsRyg$r@VMD5RR0$saA;&{06&CZ)%{8WXOa zCZzeAiHVFW%_z`Qnjh!d`SkVJhF=$e=a<*GWPdKc$|3;h@dQU|RK;rWWHNXEo_o~S z=;K*`{^Ue$GKzijdrGR;oM_jXxS0q)Lyrf-A<;ubOw%_E?z78D<}r@u z4Fw$PHjN6oEAz$J6;g~Y#v&ND(y?@d+H9d)iO0-4`7=A^_vAD(@{=H`@P}1ZoFY0R@1GFgmtz6v&U_kUMDi^u|s*1aR*U2lacZ zbcPhi5J6d@-w3pj%f@{Xvx<|mw$GSCVsVj;7DI;g}x@bQ(8nMIBcW@(Efs9cpu_&`iBkjn)T>P;ivb>4|8IF+Dg zIynQJ6fa<^+gpFO2xF!&%M`MwvaXlHsHlZIrZD8G`mTmn?TDF|`0S^~Dr=I(A9h~W ztocWz4fL-BZ7>UxgbOdaPq|Imgp(h#e9r}RmqMwmi8kynJe=|u4!b%G&T4Tfs}}AT z`mKgF=zbZgY#X{57Z5ZhMrd3=XUJbN`)eh6=G7ag*UFX$3i>`9cHt(H-W5RM?LnfK z*2?E~KG{4ERnQ|T5(f}<8`t&8wp3Bt%0`+W?X%O(p1qGcc2R8|KqTH4dhcJLg4~EJ zt>{z2o$%7L5byHcuMeLMy~%A~&7i06>(&S#FIes#0wi=?9A2{I?q8`dbkD~y-7+}D zoA}eoluz8#*v-i>cn$DvA0XmAn}bPXv*brxIUaG|9c)gl64bZ3hL@HdrLpW`%K9w~ z+DMP~DYiLYaY{_os2L+F&h<}0MzNs&Hwzy@qxlhzzVFm65tSD~TOBACrBbU!C9w%Y z4Grb+CUCAuLU`@)`(%u@#?7DVymQYwm<=i;1(@|ni|Gm7w8WaX*JorF<}JO%uN+G( z@-;NC2bE%~@H!{a)cu69dYqXnsfg_A#|h6QMoFf{(r7+!*Wzm3B3@`(O)eh2psN=4 z`PxiN$yb4kBB9;_GI9%r-{P$&63SOubk9z+RN#m|iQ0XAtRdss8(%FpK+E?uTe`6d z|J}|T>HyF3NyfAasW(A|ShQz6yHX2{I}MfuXh2R+wpySgM)UN3p_cubqnlxV1O3^ zeslOr-1*X0QCC&<^9(jt-gaB~)0_B1T^;_rLvk`j0mVffg%m$=1(%VwWQ#V|6T=^) zz6IC~GuCbA%Fn>8YSxwMO#Ld)Lwfd&v9lV+7UWx=R0%9Jq8V#OVLo&5t(f-~Gp<`P zbExfn*};AJee6`&M@LQDRJG1v(&%U}-)BK~L+!4G=6Tnz(%%wH18twV&a*$AQXX7A zo;Zc;j9~J`QS-xR$yB1iuPtu@t+G^)q=rw+4KXI9AE0d+kN9)^5t=WL16R6P1~&Bi z6loc^_e!le=9EchY9lIl`ckMx#ZCL9KDrp?d`%vf z^V5jyoUEawQ}U}1Ip4>)EfUU`C2NxzBp>rh?gN5^3;+dfEAwQ}1<7^_Tx{-Q;dzk#|KYlMnpu&Px&$q}%opyx)E^LE~~W zHk80}0s9&t>VlFf$}Q+-p0L>igrpWQJ2({|RLnh64{Wc^F~vvuHr|j*7w{aw=m~?p z#miHAcTidPNn>!!Xay}nU4Y8feu<@=kv$i`3lXeHs+o)3FDm=3_=}IP4{v1Fh0M+5 zZw#ET%jZYv23<=ZdQgy2f?<;^FZZqLF;3dM7ftbx&+?{53wjEFgq|3@jRar3E!3t? zPXn}Z4uY&Z4_d{uP)ogYR99}rc~874USnGFVP47Q*w9%CbeUo0S7UB?1pm@@(wEM? z57dJ^gN3*gAkQmv5Fnr%6)0jH-t+zxb(WElfT>1;Uco+AXDfQHMX1d^eDaL`iS{k; zY!4k1j7Rq}IJM(%zaOdZYg7Ky^*!?+Im;N%S50@cS~6nZ_fvoA@}y73QQwrUkaruR zfu=6l%c?-ks!KVJUGzt3q{wUw+y=Umv<--tCL=o|Qdi+bVM;=;zFM%#KhoHc5Hj)R zF<;uZFxOi)`GA@GvmT`a$FSFE-kTljQmaz+QMJe!Sff}Wri#@=C7Yu&SCx6kT&_H) z#knhoS~l5#ghEOQM2f!;d`0}r>ubcZs^BYlFclrbEBhX8QeGRQu_)rw#pkPlzDC-S z#E_}@u2?I!beb>+F0NNfuGvVRa%OI0%Z*F}JUm zi4;k?GhRKZ`JL$^0no8axaDmC{QRF-0OERa)wZ_dim9o6JIPvaJR7uko;S^D_+;Im z=EhLIUEYU%AChb%#*=yNnUb+VV5e90o)=s@q_k^ZTi1F4Zye*EN|DLu7P-W&(bpj) z2i~YixMARtb^B3FzfglY)f5p+M%@k`tag7GRZ%{VudI(eq8-Ug8z}u7{bDQ%gcEuA zZ6bQCsbd@qGfLb^uX@w^=MDgQ4ukY7AHvIGr+3l+)3(qbH@VZ2d-LcWh zhEB{8-AK8pFsssDaCTlQ#WtBPtAB~H7&csX8889ins@*lgdl7H`#hQoF`@%Oi7r#v z`;+tG)pVJ;`T5*;37WZsH95e`W7Hh=9)G=q z%x7kvzkmm{x_!_zlDj$L*yN7I!3f?;Se4tX7DNxqxZ(DX#68tG^46x|&C{6R zg;@=nft@n&iv?4?4Os+ru>ZKyqU63~sZWIZxGU-NU%HOx0?p!EDs2}BlLXErV$VO8 zVD=#~hRS7rH+10bt{qvLD#Ka8+vm~*ydq-^LYgwZLYk? zTVn$!crDL0Q0xye&EruWZRzf>EWlPj}}Ufy?pTult;BYu(t zlf>W}Mq7t@nm+-!P>Y&sV;x>ws)(30B^Rnx0=hbSyf?cgE%;xD3No?U$EWRok%b9@ zM7fPGg_2tL;D>enifeVHa#=*`hQoQc%cZN!vmlt?Atg>Jsqaz$F`yM>`%+_in#=^#{^7?Y*8L0^V4r4=B}~AUR_bs3^ARZZ`Lt_ zZc_~Ub-EL&N@;)zalSo2d$H%Q4qf!9xW)+CXb2f5Er4%Tlf1H*m0ai7&)Y~cF%Ws; zKqh6?s11%`J`n0{Plm%$IV1L~L%{8pr}6*}1!%_lxGm&M%TSu>acx{0qc-=~LTnXg z-r`*gFpm{Js%CoEdAt3$6tjN-FtECh(ycKEbcTGMobtlQXNk* zH7k-l3aYm_{OQFVZ3uFg{sYgQ?Tm68B|cFTMQ83ud)F|AOs3s_Mhn+c(8dbpkKCeF zv6EqrU@8)xD&tLprmT{Vjw>K2Gr#8t@uq`@Q1KJ=RRSabN0 zU=X)+R4$H2yCTavbKdbc$s!R&O@*)}8s!T2AAc0FjKjw`$$(_mG4QI#36LW$G`eV7 zmXBdwD&1T(vg<9c68>~Yj$ZLB3WWxxKubgp#vn5{9cwT*sDQtI)yBLBKYGDY@9r-P z5NM`o41qZP!vq3M+IM95%w`rqYbwTM7+AQ15&&8nnwRRROR4i)P?h4~=Fqb|nR+Za zf(zja(iuB+bYG?2XnFw>L{}&&CC_@$4vtry&XKP_Jcr`EFF+tRW7|*}4kxBTbeLS( zY{5;b%%?!}zS$fhOiYu>8LsFu<{;$z+Hs>1IrCtUdh*P6oxM^MJY@NuJi`Bs&bLHn z@&oa9a_cRM7_mt#@-`H5-)TwQZ`_~h9tRM=G1U5gNtjiw1Qb*wpzZTqV z4APN4gA3xX1ZZ-7dxAfh)nCDxSB*SsW1_yRwYR#`ynM~hj#@^(U}KNSSvwwq@7TXB z!RJ2fL17+Ic<~2?8892Ka!rkU3HL}QSo@`5h_46gxF=x(PZ1RIe|AEQHu%x(oY~L_ zyiCj(D3AO@+|{mRXG=Nn%k%gwgy#|vT{#Nmfdl-*Pf@r=`l|Zvf>P{PxYi0zAKF_{7!fS#!T z0J%MN_z0#Wn}k{G+938-(=-=7xQ z6y1dx=P&B$NX{q4)v+(0k{!l1Gw^)UCV#dUIpIo*b9q{I zNy5zBMo0^)VX1E^tOiw6d8ValBc`RUq-JYnZf<61Euo>Vt||HqYHDL{CZevUW$$b$ zuB8DtGO;o=(w7>}roaIZfdKBb+o-BtbFc&95JOa{viwp}I(~M~PM%73ma@Cg-o$%v zlBMMx7jCV0uZ+}wU26cd1V(ybQ_5QG`{(ShVz&Nr5^HqZUjE@&?ft2?&U-Ss!7?oe zl6srXj)N%^elZy-2edgGp>W8C|2iN9TwZEJWh(hU@0Aa#n`CE~T*Y>D5$d3Ef*LMB zQuyh=J#%+nyZr{+1j@3H$fpkvC*TM<-SLL|dhtjYon$VbxcI%tZP^o0nE2W|R1Yxs$8t}TfdSO+ zUmtfKzn~zHLcVe>b40?v!3R~*f8dT@Z~iG6uGVqqcooB;@N)FVUFnGYm-II7ybfno zi=~aNINfPBo{oG-JtrR-fgu?16O1@ZE`sBy!ecxJ#n-s=efC6(_tWS3gS0>A* zPu7U0;r!xbm(hyTZH6JWLIDJ#&17YrFhs!*YB-X(kCY?*^9Mu<66_6qcMY@v31Z__ zkxE;&9CfE=@+wm(zLJsyX;G0<*ZZ%M#GZ;V#fudlpK>>?p{A-AHF1rMzgE~br9WuY zb4rRhe=qQ>rT^t~A9vu|*I1Tn+I-lwG7!2Glm~+~=UnAvrJvv_>C*%1R zuEt`AH6koADZ|53@?gNkY=(|F3?+r}Ee9RyBPbdo@Prs3WiKK5sMh=qWu&XZN#W;I zv*@Q6fYdGhS8_QELpJ#D1kS|qFV!+k&l97&yCE3*Y4hX$sKj2ROdxW@Y&2@jjTxB$ z2`vjL0zD~e4b)i08(mGKk?0*C1v(?XJD;1@m^L* zXIjyYnrkmFrNk#{5cwFIa>4oCfWAPkh?dFzx%LTP&H8iyxR=cRo8Nu~T47~H80cL} zSV+GrEU-}D(5<8sQu9%A&m!7T%Z*RM^VSSwYb&?n>PJJhMWxZGVP*M2=2$mqeKtb) zn)T5oO{H6HhP#*7-X7OeJa-+1EOolG^*A01X)J%D ziK<5B>Q|7$9}A^^oA>@}qh(Fxrc%<`2)VuG)9Sm$AA}jAmxGksCA}_jrz+aH1Fw@Q z_(VSlW}{*5zg8eC{i;xEGH1Ri6S|)R5}d#b3-duO+P|tQO8C)0q!N47%<bl)qv}Qz5H^Ag)0?j7EiRNH~2|TPxiUyBrqY!{0?mH<1HsJcsI|Tng zz^4zGwkQJJdMG=T#@=b4^$R~~W=*}!4BGL+BdH)JPF&N_Y@evptzR}RDh{LuxcXx` zl_xda)Uo@1=jp(E+7M%7mz7}~jNLlT`Y#Ewjc(6I`_O9a&{uJDG}Oa?#%7wD&2z|b zlmj&K$_n%%42-FZ7IyDRZ+>c-X!^Q|7gW6^z>w|QU2#;C1Z#9v3+DY=T9@{oxo@vv z@hfVa`TfJwx~ygJ+U1SqtTg?L@(Z}K_pa#X2Voqp9)%a_s0(Psdi8g;nv=Gr>dRw$ zI~dj;<8$S%1=zWO?w! zV*L#d4U#R#X|#8&*dpS_)WXIZ^>@q?e&ix_IorST?TA+G;nqyIzEy&YMEy)Y9r#%9 z?QCvG4b_7P9H%FwUp0*l9a|nBX<#Es;z^g8j_X`Hmt}9Mn$EAUR93v)&F1Hf@vf^M z|8d-`_cm_Nm)l?T)l5yKP1n1*fVeynv+8i^%CSF6FOyQlif4Vs^wTi40jjsg?n)2@ zsh1D|E&>xk9=sFNwx*&wIrn}WYaQ9P-}vg-n7A{HLONiXlMWT_0}{ucM}q;_MvFo` zVsPzuA8822JT;9?^j0D_!mnGjAGmdnQeL;sd+)WdebD&l%TA}UnAjUgwg`a+yO-or z5Iu+xc_EZVwEXAPmfnpYm2a?x2KpTTTznx15TnR|$|R@tiXaP;_}j0>&z>SqnhdVo zYbO@}Nb}(1U1j7q&hf|5D93wOO~34(XHNJ6x!Va%$_tMbE;w>TTR~@%LNYJdwN8v%FdM z7e}F*Q)gp;JvQp0=0vfuHBu8Q#h<~?7dCgtxd>DeF4q} zXBD}%(VYQ=_5_piNw^~tZjy6WoE<-T`62~Ejktb(dPNSLxXy@>f(avOc2?4{_RuaC zycdYSn$o;)VjEDf`681@w^6O-|ErB01lKXj>L)r7?>_9hU>aiuWFVvf^CyKTn+Sj~ z$(O%Cc;u|nO|0u!F~0zz!qN0{``0U_j#qpTfy3u#qjY2Hy-u+NamhCsdF@9U1rt{@3BEVcD%%!_utKxgDbzk zJ6HP4K!)jkXOs7V@-wMun`|C8)r) zopGn<=*gef|_$`&AN54zeG@NWcv@h zv?lZzi(&e!Hvyf0vfrRu0HRY62v`YN1ZWk4zPPEivw6-xS-6}&i_si5TO=INhbbw=EbkSsej_uulQ<{GXn#7 zV#*N;M^Q-qb&LhDVeS)pjt@MJ3__{lpS+gU3NTA_GK|P%bPC?i#TbAbq1R5oYPaAE z;j#^ow_jN6C!~}E5 z8972>w1Q)9a zBSVc)0h{8a*DI5AZ3w!`m+yWp^NM{R40%UK%YkYT+gL=t+a?hVo?*)Uwk42Re}?)t z(;N!VzIo4+c-GoN@&ZX)>V5mUf}AWNuNrd6&SKgKNfd z2+e^z!Oprz)_XeaZ@uR_^;w@C=zGo9mM&1x<-M^kWa6#Hy->FX+Y`yKMJa>~avO74 zaEoW-+`aVxA%!Du$Tr3mJs$iy8wBGu*@|LEZDpPaVZgS>ThywBjD`^ZTwNf@M7tv+ zjF^qA@5I$5`N8keU&< z#*|0R+k*5z9~OwkZr!OhTyr3}by2ilyAX8O_4twIFC@4x6N{wgkR-S1PjiwlGx?4`3=*`f$~>8{r6N zI1N!l)&E4u4nXJzq>ojiuRw&5{tmp`63ja8A3u)PSO8d-g&eeLSq)S;mki62al+?G z@3lXm!@5*Q|&Q*YfH8Iz6 z9I$-K3+&E+*kNx1ZpyJt28NRAZyxGkJ!5B2yhq>O8Sz*7z7zG&&97Rglb^pznhvTE zvq2^D2r)G*wPWU`Tt-k}hYN~5-+2Q6SE-;$irGomBoZLNlkoNXLaRz0f7rIF1#Bv5 z0Nu>ydLhbjQfiB$K>X?(c-v~}MuAZ;iW)>Ezfe%r?Mnk6%bp+uzlX!_6bCfr(Do;;6FHo zvB6B7ZI+l(@=txy1HLqKCAe$DLun2N3R-V6nN?~jSJgmCa z?>+QK??(WDu#&Lfsz4c%$4NOlB%8;P=dC3T72fPKR0!8E?O^Y%G_reZ@YjH;T#Ts} zy(?Xeb75|F1Zx_xuBs073gj^KGozN2^g_e?}}an zSH&aG0G6Sx)OR1`Tv6fH#6TSxZwcv%f}9td)599s34ip4!cV0)OrmOe-GtKwj&7xp z4U8N*G}VKM4{O*=`1*ig>PB#f!+@hFcZbnU3@bNY#{q@P~|LxR!!7l)syXtsk_467o`G2pq z39^yn;DnpdtGJBRAu14K1#{N(G60{;?C{nQTN3LjeK({Ffc^=|+Lm|(3{vN}z4`%c zLZKPD*iBdfWlQE%vKx;*MF0P%_e}mby@!fS?-`A}@8-6&GB7i>Qq*v=bF^>~)mC$` zHMTObw-V4&vUOC{v@^0WF>(-4)zWYP5PDT-Q ziWtw^M;es==|(tQoA^y2S=caIwFk&_TWG(37q;9n;(Yr2Ky7>Tb%#GBn@9M(pHG># zd}nkCPzD9J8xi#Dvte8SqyKS!@{&m|FBEVrSuwzGx~$N^hCfQDywJ$397>4$Gg&wm z0HEldm=4niF+su%%I*FZM~7>_XYeE+p$xlEVY@$q;NMI}glyQ~zpGj$jR25quRWVG zl8z|h7Yu-@R~dABNxS@pX{hr@mTtk!pQWXAW8i_2Rq?Ay0Rb^E6DgYErcq9Z zI#eE&mHOJU+gF}&s3nV9SrUg^K#8L+_vLgFL0q@9zx+p8Ghnej8_kO64X7j-UAd?r zhzR*w9Bnl5*+H>$BNkf`LmgDJKP}32U_W}dO@SDm&0)^;#xfLE_?%P!U+qW+W@$m; zTH>&g^S zkOI=;Y1p_&jPNm_`~_n(J7>m9{o000@;OGqWTipIx{-u80QjizmJbOTV|ECQ+ypf@E^wLmVj?P&fgB)R-|BI)a@ut3E2Z?-G+M#Hc zXMJ``TsAwzj9yh!;lspM;hyf%a$n2z%0mV%B?^LcWorZh?Suy zWPb|IHfTtPanY$g-a7qo1ctN=U3pkNb@Mw<{@yNYnKjd{aDAGq5XA97S5huCPuQO! z(h*DcL7`%UqjO#z9a#npXBSh=-jBJDHXgL#KrGP!)4K^m0Sve}K!=5Vb)h~A6Cv@> z@$im&bVP3f7Mub0LEB6ZcQqWt-}0yt%rQK$N<3QJSRncOSZ3@ZRFLp!&_inI@ zuOtP;Oa?G*#jAbuLj2+2?fG`sYhp&9?bskccZtpwG#bk|7Y~_!N>_tZkfGoes=SB0 zJbj){A>fPZW}GyiW51y&xZEp;I4wwzO5mH|-!`*L+23tzNA#O`5>&oD{Q2j1=@FsY zNRWmV$%IRFYP&ulBak)>?f>rzIJ1SkBROzvD^Jb)S`jN z@}l{-qET6(!YSO!zPGD2XEBRHwGMU`+!J&1a!kl4K)BDqz_|TXPQV`BN-3}M)kMB* znC}lPekK4WG5q;x?U&oV@1dwi{!?jXBJ)E2D{xFCr8U_EL8=@(kC`gjciOyVAYMuo zErYp~j7I;3k=NMSS=zQupl@W0fjE=^py@sT)YHE zWQuY$gMKfLjq4>^v)D~?@viyCxN<~@%ND~N=gnn<%!YM~Ed*;z*6YU4L;Qj!&X@H2 zE%&I%*7&XJ3f*d}Av9H&!~*eXUu&g^zJceJl&d6aL=OVrxr;Y0+v(qL%Sbr@peht+ z(+&Hq>oQI1=d^pTsjL@qGSz~6K1el(=R3}Z5kE8KcYW1g>4~-eMn12Cp-=C#=oO}c z-?pjo&m$gx5-*KzB!zw$N0spoLP3YHHy z3I{WB$A+9}0A=xbYX(d(>F2eTuJ%4r>)B0CgaIy@PXc$qJk6V9_Y$edIE$vJ9~0e+ zT<81MAhgtN(A-lFSX)M5iUTzz7u6x?o;B|-l<$RLdrmt^1Ci7x;)TPr=$?$!Gse%S zoMpM=QHSAfwo0vEYLW$cyYHGCPgMW-M>mr&@_j{Q_c0dw4japca2wcyv}MS@Y)V|u z+Z^P(oGNz<2c6F;1TK)O)%?p$`!p0kMixx0t}!dyFRl7M`K5|VgLw@g9 z``r(&daeF#n_T^+pr}LQtmSHEEdY!z7vkJLWl|R`?sOCwNPeJR2ar64`AGLeI4FD)+nLa)nEE>qy~jw;1bmD%^Z-?f{VfWyR+bQ-h!c$sq*|BcYzPQn|*y`I#R3q!s{myo{h zc>r3q>s-KA{;euUBcJ+xwNW>3SK~n6{c>v9(_Lf=;-3-PRVhe&p%?fLvf1Binevge zw`+YXbW!AREEbm)O+Rs;1ydFapDyH0=n*U~0pUwxc}v4yQKH{OGSF;76ad zE!ezWZS%u;e&^Uh5;(8Hty;HAuHl^?V%ahd{5jbeFEHAtRQ&AI)>f9fgqxPeJPIWW znCWz2y?w4lh@-rOCi~>*{PBo_?jokD}u>YpDfl0%E1RRWIJWv3@EYYFf9+oT) zW%*g9tCk&(8;;i`hm2CBUzk4ZU#GB$PRq^O9-4X{uWG)Cz!xbWwx?Lu7$TiOYrB(` ze~l4YyvF+`6)d8>QfV??xwQ{gismF8Y9wy0aP@G>+3CWCa%t+eha=Zq^-J6hLeh)Z_|n> zN=n##vLuCPKAs!-X+)nK#SzNfb&|<$WIg}E>-mXXlhz|jfG9IAv38N>hmt7Ve(TQ& z3dt=UG!b4QRjj3`P7|CZ@XmaZ@5i=`z*cbqNWC{<=E%dxg2?J`)*rt{)%=n zzrOM?j^OH_bmtK45nXjAkrB9KdszbQZCoofOqnAdcYCP2O?vfy)gj~hse4i`aK%*PNjOYt_6^V@7CZ^JmU=VdsX{&aH4RE z(n(ObhGC|*{W;_!_7zX2z3S$RaoK860lGzPpygy_4P<`@Ihq^K*);w%IU)zR&D~#@2rq@@R z7Y==rONJ5qV*8XqJGoZu*Kb58y(lMW-q|`SBD(DYpXJmmuD!(;{?A+7B0YpQ&wuRip{-6xiBpu_N;D9&JrZAs*s#($J zq|IaS910$XsVOUkoc`sa(vlBT8J=?8u-z2Dzel19sP7DH^}*#QUt_I$oK@+ zMv1HmCa6hu^1whF6e62YdT^0*4@Lo#T;|azAtn<6d!lc|n~j~n8nSHG&cPhQM*QyM z(@JO~6d1K@^18!_NR4obF~v9P*+C)HI&gv{=CKoAv`#S32yf47Zkzv93Z0e?$EjTt4qM;Cn32GRhj1OqswFrtv_3C|WE@ zIJQPvN<$y#eewFu#VP)HW7K3`lQElRYNv}}o$|`Njc?=;5-$1Tayr`3FZWe$??%!l zyl@%2K5>6a7f#;)Y$Gm&hSk03>d!o_CR4IWEHqw#4$%C~_Sl4lZ@!lxY`LBmcw!G& z6U#~2s=-RyC6T(UF7CR%6@TErZIlZorsXvaCN>S{UWZjXO|WL2wChL)syKG{%xAWa zWr0bkXsA6-h61c7KTD%8`Y0eR8x2E|{~NpCE2(Damy;x}_uo_toHU!PhSS-`P+C30 zRB}7!N3M>LB2VhWnO`&YA2x&GHcrz1Y4h^7v$4+^Lh z+lJmN2?=BME~_$Y`NBmsv&loFTP0#4=0O+zu2E<-hepFlPk>>el~8$WB_faGr}$?Z zIww<(rzg35O||_G>q=Pg2mXt?ESB;Esvj#u+m(8spMN@T>|ta4WRLTlYOV9C;qWo% zgx8{>`K^k$E31+();c8mez7T zu)*che#-wW)|;FI-QLmw8UCym0qP(!((eU#vfg5WmOUy3Xa8wIwrOMn7wWOJuwmb;jdfa=O7fUKjvg%MK$2^gXKnnu47!8 z>zgl_n()wV5)fVI_eC+?$W+6z5K=?#?(N*dBzySf*6)9QtBn~f)mS@tmZ$*DVYVhP z1IIQmN!osIG|~0)e2~;)V%$D*P>0RIg|fai^sWPv&^orr>$RTr#e_C0ue}I1a!&o$ zDTN%rGYv-3(?+{P6|uCC&JcMs0~v`ZeDfx+ey@KWiMi%=AP;VON;(dtH?3qj9v7(Z zl3^0Qfe}r#9=!N95vz!zGHL4AR~LG#lJk+so6-=vwRqYqy1GR;n4+n-(8YPgY9Jqt zct6r8`?}s=ZM@#cA{bmh9wx?#b9pWKjWz+_(FiMVdG!l`AL@q=El;xADsM&W6eSgk z`;I*l;hWoa(J9qL=Dj^+puBKz)v|1{xyDf!JE-)Lw#|}Z>W!SuYYO)2BG;V(=j74` zu+UUv1I(TwT_xW{PNTHHe^w_&!c`d97&A&O0O3p?d&o>GXubb##w?zjf~U7{D9~EP z`3YAlW=|}Sk^C3Clhr-fTSY%6EZMTv77{@*S3mgR-DazU97jP4RphL$mrL-oxo!qs>`;S56)+*gmm@P8vCkGYy-ph zY$S6*W4FX!-D>vF*E!VGRx-yuIX8Z@;6@+7Ou24>PDMfIhvSKkj&^lA7#l!SDLfdVU2t~Vo~Np50hV_c%X9LJuV>xsue4%i%t~I7TU%tNK+OXD zJlVACYD4XNTG)?uy!PnoRn7s3&Z~!gSy|NOA$t!L_8k~1O`A<*g5^hQ&9y%y3{dT~SbF3$jrpr<( zPSYz%u&T*Iao^~#KZ$1?n`!;Nf!!fO71Jg#@K0eWJYyY zx{N0k_74ijj*jpmjUP$N;!K#!w`rFsmv+akt<-_aW=L(Ox$eKi<1{Ow z=`h`8d(S^biv1N=0>2eAC^x>f?wo$XifwB`Xh?YAC%hfC?m;7&|T> z{c!d(-8j5Ys+Ddb0&xzIWHQ$&Ql0v!n6AtSTM6kBA+D{LGgx7D(=(xIROh9UmqNw3 zxI*bAFgq_(;blZV{^9q9o))Fr>ZwJg&muzbcfivRgoAk!jsQd-U@6V;vhix8Q&3$+ z3k@{li@hNRaD26I_5IwhxHLb#dc4+X#TDYCm{;iTpsE8u?Z^USHHzf%WV@fvh(Bhf z%VNe*Kgw7PQeq4Ieb=q`Ez5{bj8jAe$Xnhf0QffF^nXYw5qUE3U&~OGQ&3Hw@$7ZQ zoUW_ONHn}Oi70j74`O-cuv%;PZVFw`llD#CkG4#9MYEp1%i(GKz=%fkx(WNa@2V|T z{O&X|f;DYA8&SkwIb{&tyJSN9T37tXw)%c#FSsP3*1hfhaL~J<>y$)uD}0;GPz`4@ zSSdqIahzGcHne=CvC=Qwq$2_x4btAIVtJ z_2}LduI=*hZ4i${>^?R=lmL42@7faa_G&4VfO4&|R;aavck9h< z)7h<_aCACw=2@Qo_`vcZ1}GE3jGF;_`_anBkt^TBeqqvP;bVptzpaJ5SROh0P7_h8 zd+KP367##bgWl-0 z+b1Q90-lFIdWIw(&kS*5I4S9>72%RPR8M$zc<|tqrH#GQAq_s1_aoN@>m^F~46Xt% zJ_WP0>T&29{4sc??Qb0MBVO)pdJ+&UkfW&HkJ%2CZ7XZCa@aM=*H_C>HObbjE`~t=EqTkI@#()FM+H{r4)4{&mR_ONfc9r(Qgb63Bi| zM-0{KK8td33T`z!5ZsOkMFBQx<>Adr+J+lVSy2_g(oh~%MyFte+J|=D1yxwi?LsRP z1{bUlCXCz$2|S2Ln`L=uOF;k+Ob$ZGGjZY>g~K$CS=X=Y%gE&Hgadw8QH|lYNM`*< z3fZfRlmSwIszN8VJT1OExDvo{tK*f6;iyk3q4%8b9Z&9nM ziBC?P9!D=4U+8arJyTqCNdUY@?wlD-`XcGo6qF^<=gublYgmM5V{t6I(7jxA%Q1tkG_9j{a5$- zMl>_YBypYDW%ue(GqQfaegW4a%_F_3h)F)thq501AXG*p7J5GG!acw9E$(`S z%PE&{_oep~2~zvnb0G1TCiHc}=GFY>a_4a4+n&~cC!3)?*tkXrc!ms_;l`i5bYl~q zE*7re?Chk_+^$lAQ>_^7duYMOz--kgEZ@*={Jgb9a&qZrP-!3PG) zoXl4<>|m%wD{24u)w6{@R!$YVf3E4~*{=`@1+R{8>ERW1(uFY!4%4|8de;Rtr_4NZ z0Hyj6{3A+=6Tnx2;Fi^#C74~p!^NONn!Rww=ABq{47mJl8dl&uThk0xBaNsUnBH6c zNZJ4l{i|yS@3of?qcq-8!lm-ToXs`R@q#0_>@X5%83-njYZBC*?VAL@VF#!>=p({C zH@`@Swx&yo%{8ZW8(0Or__pxu{nzLBu=6r|9!G02<5F?&A?VKH<*~YsJz2ZlVK@`Q zmT|zR#C)YneBOJ5HaNaald}$wil1mc2^Rv%%6CnFdXOU8S~W9@aup<@OMfIp-Tcc* z;M||h#?1q@9<6|8HMP~A(;6()QjkM0XIR`ZDypuZ)!_$v!XO}&4gzVB{N_5~${yI> zd+W56M#t^j_V`5fXQ6^wGi8POH*;{(L3o#QVxF>A1F`oCPS=O;k#96oDldh|HHb** zBflRfj3Il75`)D5dqfHRI!-RG0&nw)-R0U24#Sflg6Rv`UNS~BEhS}L?qM1!tAnwH ztf(-$)XFZ>aD5DvX?Lg{F6Psl3kF3vhycKAgr_ zGlbK^A@6ErFYt86)gw805vCfWUZ7=(xhd^-?){0|1|eHe@}cfj3izV80Ce9*VHa&M z6V?Q@k*LZ!hy~fAQRbBFy97VRtNf&1bzv@LMd}A5C{V*$3c#Gko4}QuWs<6t;^kf9 z4yD6ihCdXsRrCd5)28>2O2nTwJChJB|e*xzV}&IYYGc@T>}Nf zMXflqP>TN)aXci8U-;aYxZzr1$AvxmJyovU-sk?M;9T#u9=UsAV7aH%pAG5pI08(6sZpq z0K|L|+|V~X1;zgWz!ZW+qkoqQvr*tuO4|P@m#HA0LA0iTzZHoHUz z_&z?D{L=Y00nPFKfv$a2zzIG!s)+d~JW`(wOr|^;VVt~*2{6-V>@e?3Y#h|cHU=B(bNG@bDhw-85(3%90RrueV)O!rgY@!V zx3lr_=uu7^z#B4cbLvkHu-A3;UCdZ#VqP58a#9TyVPPS-{?tt|mJ>HbECa!enrH!JqDSEpgJ&=p zDrxSA;G0!eTsM1V3m#hgWoM8kS|;}{y8c4C_bCTz!85PEK2Crr?^&HnPx>|G8J6w7j`e;heRP~yxheAsi z#rL>%0O-K^r87wVLx9LL_~+yx2&x_3YXf+2nEJRoOXF?JTz24l$#4c$5&(rpz*Czb zuWgkOM$C(!A`CMC&+I4tc0}{s&8Ydle$A?7$cqx;kw{5={Ohl|c;6|3z|*N;;Brc{ z5M2Vgm@zX1IDPlEtU;ghCq?)*iQP|-&5T zR=I3NI3_44p_*SV9pu0PNWOM{YZpNit|#Q)#V1z(NlY`%_JUPxE{e4}u{Az|xb4mSFSq!#fL2 z=XYGiERjkRLOLkFr^r2-Nr1r1>xBRvvK$O7$~DAiCZZ>mkh)&ebvm%OQJN}W6{_ZD z<@19XYShjzn5UDRf7a)iM3B3_ngS5mgl)Y5a(JmEliuT6zV_ckG5KqEvb%Vnqz+)Q zeehz1g(hV_#A)Hq;Pt7nNS2uF{SwcdzwPDyUL)4|5#zL76`#|C0uc>7wFajJ*;iB~8LT@NOXHDddzP}b$fz$|ouhO`J2Jj$99Re&o* zWe>8A7~(OZ(7|$d8nx}Jov@mHyiLwl`1|)+0|7i|gwvGgt(;Jw|DZLQk53DEe{gOc zmcUyyS%HSMKlrJ0emU#iM{+&)_WmQSq;w9)`m}~kn1I+mf*D$Z+dnV>+BdP>lCso3 zZ1coX(RY@zdZa5vezO60$y7Dn2-g6}bJ0?eA*RCM-R#sAsf&nYmw*&@Ui!+mr zP0#oOtQh-3?ON+ArP)5D^Sh~Vp@`XMW5iwxhRz6zodW=Y_op7H`tN)d#{NYFM~4rAXL8>%3#f2qZ{=?Y1+P@keJA$~@z&6V3*lR%BG)6t z_0zOB9D3^6e>j;ah%g2_bb%+S(}NtxNcS#wP+QIfDnvWuUXBti=^TYD+#e3-=7L3% zh7SN5c?5UtxjMdxtq2NK!UG3Cso9wTtTG~Tzr7RhW3xF;nW~lsK!-5T0AVFZKN1GPRs(OJuO?7V2`e=DEuU?{Z+V4hZ(GPKiGg(wB%2 z2H^Cd9wp7wa(vd1hMegSf@oC>dc(dN0A>P@CnAk!b!gs88AxCvZt0ieh8we!tSeI3v+!SuTz zkm48{Rj5k{UMMpQbJ|3H62jfxYd4^zga)1b$vH+I_M-HeeNyD2R!W)LP9xT&v4~G= z+GRVcR=*|!Rc-AYXSNW?E6>IP?E4S=sbECNF&|z@Uf1^89W>=c>4y%i_T91V;|u8v z9t9g^g3sWPuz!4D*f5nPnk>BVDaXI)6iXk|h5uKfiTjGi%!khJo$TKlSHD~W!;;b4 z*8vEIx_vM*gknvR)pX=;=tV-r01|L%Dv{qQsMXTtRHRdM4MqW=f44rPfsj+sNGQLU z%-?@|x!IwFe-S{gD`hEn!55w5ef*r&kLH;n_uztOp0>=BeX*k}x>$v3mhr#qLvti) ztC!7Wo+32RNP}#p04H?)ZuV2cAKFawPCm0*oykbdaB1y~QctLT2{0wH{6g#{D+4cw z@8;9879`&H{fkF!1YJ=s727`Sogd(O&w$Rd8Wd4J>ebE3xbEl@1p@Cf;-Y?qxp;At z!R}G6I21Z_prMD4y)VzVp}zXSK<)R3 zv`t6+{F--}8^(q@LkQk6ZD*9kD#GmRX=|;Qbo3lES7>)OEv zC}$g?9o)-J02B8m>N7#qEf5ZLGbbK>V0DY#g4wz?E2KC-=G}K#rJQj#({27r$Qr4{ zRA&Sg$1d3=Tf*5In#D^wrn=>VDw-eXubbrY`W(cnRl&~$oo#UA;Nb(H@_e2BvX;&s zhN!PMD7FY`ctgzk-B$3IkMc1)}vc-ufsX^Z9k6EVnTxwBd(vaVv? zBN$ed6medy{>1_wnkB}?-a23-MMT|}`7qXcIiB=Bt1ZTFPHCqQ2APq8oBh1L?t$p85~8r z=eM>(Xak)`U|G-?QKze4MbnfyFYN)*j=NyO=HLg9D=lnPya{NJ#QC6zQQqfWXLDZ{eTI z72%bVZq#DQoaoOApa4!;Rt#i;2FRi`Fsb=lXu7{wGUsCaRB$njE+&MB!TVeeSiq`+~Y$JAqQ9*2Qh|L2j#Xrhl)g!4%^> z25fpjSGJ7;&%^+;r}D6VR+vxHdF3=#S*@3Y@K!qBYYZqd=$BcvBvzrlS*IV>JqA ze*M>EoSwrYFRxVKC*V#hhDK2h+7!U8Xd=75&G3V1KV9^V(}k9WCft_0NQCOK`r^og zDK>bMzcx$tmSFz1+Cn7mu$5Pp>)`St1cEM*(?j$>Oi9YS*{UDXB7JCiWCyd{(0wxs zWvw7N-(W+1Sh701!8XO&Kpvhg3g9M^_Xz|f0_q{a;fK>~JuscWu0>dQ*E-^slr(L- z`%-iV=&!sSHD#S^KM(?fW(9bS3hLBCTA@yv--XZ{w6Los%zW_hOL+eq+|R)7O^5g8 z2742m2eAE&VNrJfwJ-qaWjreH$0tMy0Rf~)xo$d#CrhT#IYVK!G0a>;iqnOv^;q!! zREQQa+_)Y&KG2~K4}zK({2jo_xizW#b^8=rD%i1+aowH!l-P}eJ|B0cx@ki}cUbq( z!cv|4Xuq6-3YhA%eUPE`Zi+GWm8AvIGM05-bTIq%4LTjHyU^qR{lHXEL@CRzW%Z=b zJHYH-`}(!%+_hUPklyG#)CRxG zr5$V<=Ww&J-mgdWxwi0e+&2TLK zHTo+m@|*Co79vYL6h%u=9$4;*?fU0=j-u+ug>0S<*3J1wwE^n4ITey;!XK1{+yL$4 zw>O4=+Z&y>ggM`#icJFZW!UNtDdu8nLv57b0;V^4ZAU-+EHY&5wK*bssaesp)#~_w zxt8Z+6t!F%%hG0tDTNL^<2BXW?Wc$H%pX@DKkuq<{V;qp9jE#D3^*lIrYsd1Mp$4B zhNF1jxX+93E~js(JwL6`o?&JnDKVM~Lfw}8BbQ5upcr0gVHgDwOVB)DLy7}7TO)jq z28o4RD34BDM^i;nv8)(OwNkCk&3Nqu0qDNi+-nGBtIt0+KUc8H`bx`9o1=~K-xn=$ zv@f5ZVFLbtl9h}1JtMlKqEYD5zyg9}ce(DjugwEA<+zw$%(Io!SW)L-{ox8;XFvn1 zQst8?l9FjiOnETl&Z1@`c-G+VJXa>~-sVY^ zvxLctJf7ne2P2dUzW)(|QLc@pF>*NbSB+f3mz-uD=Y4I{MjC$%u6t zd%Oxor=GuzJOdxR4JnH~gjbGv;jI>r4Tx}hVYGUty)eEUK06HF@vyXTb`T6lH}%Oj zhcl_cqCRe{>+*>5HzC+EkEWdsi%P;|Kw0C~<% zU40nNuImTn**6V*sTeds7Um^^FAA-EYEJS@a_hK3FPmSEZDU8sx|=Xzy)FnDKBhE` zeY7A07X_3YZ=3z}z)caNUyj;zKK!kbwo%+TVywnlo2T!)CL}-=OadRwA4*`mFUwH; zpKOZt|7KG#klB>)F$7;<8|vtrn#n3yn40UF=o#thS(+-VD9c-_C|a7B85>yXni}h= z85kH@YwI8je^zn-AK*f34U>K$~T}uD_gG1RdtTXyxs^5tg}Y=TXO(8 z|8AKq_Ze^?VA{bAU2^3WhT%>_Ov)TQh4SapRqC=6D|5d?%rPc>((??-HF5FG9MY^27= z!q&g6v)%9C@M#?ZD7mIZrQx{vGpZVJ0d;6l?w{0-@)BbTP zhoW6!Ac%DT{lHi6^#ax%8UPPZ9XTmSe(v$Hk=cFa;l&3vqXP*{RJ?0jHmZ@!i8v^r zhp#Sp&tC%gg1}5b|8q^i`2I4S!Vgx9sqH*nId?SEDst{?G($YxhuMQ!@S_4iY3U$6 zN2>V`fUCOJuMX_jl5u`l5@uinbf@4@2p6Y!X{jP80`1ku&o95bvt(9%p$0*U`3-hS zyAfHTS89_y^b+!$5R|qKbjmsyyuSdV_PBVL(D2=soBS_j9hM-#k3h9QgTLHI?l|AM zF2&EeS@TJg*1=E3$$d?cBe%T?Qg%u5z-V}$fiFnhiIVB^i^6M%eO{(>^Wsf&2Q;q{ zy4N+8{DP{7r%Uo5K+gTT|Mqx*?peGEKywr8>^8sdl|}MYb$lt}$piz?3ib;JbnUs+ z-P2*GP;|GbLBRKrNmlgXG{-b$iKqV+K;t{{B_5kxZA$_aD+yvD9Ef0h&E@Fz`KQn7 zM>dy&P{r@FFjT>x!Y~3R@V8De^D?Ks$* zQ?tj3`r9B#`<2-|iQUTU)SK-8C-5{6%L#~(q@HCL(uYmV;q?0mvkCxc1Hh4e7QWu2 zPCKMuR{*d>A1*J~4xFxXgeLrx?DTFB?L%jAfI0X*ce_x)?|%0?9KeY|r~_Vjlfq4k zx`Ni^e9Q2glj*5CuRX0gZ5Cs#zATqUn@B=OHOP6`4{_ zYRiP#7wMP*a0Gx)>w~#VHo*>g&ZES2O%&D`H^13l4gy32AW`#bxMsf=r6Vs*QKHCp zVq)s$0AA?Ii8qB-MLwcmzIG(LBqsHRy&aD;4cN~Woa=UfdKc|w`mq6u@aPvakT^22 z4NS7~My&y6I-uy=r$3gtvfaB5e+dJypp1_9`>$e@&M^`1?hZHsK1lP48>e)N0hiYv zV1^`7Rf4q0Vafn)9EVIkf8C6Rt8Jjoc|6ZtEbN|t1kmb*9+dfJ9$>+(%76FLitOI< z;lERW9*yHb3oQC--xuAHh5KpiWho0@__D@{k)XIhfA#@GApu=Zw&w9^A2#vf{Ms0z zIw=+au3i*mo_E#H@7uMt`yVH1)NeLuy<)TkIHO*;-1)e&fL7l{_L2_?MTLVJr)q)x zoZTe*raPCt#L&dPUg%f{0CpI|%p0fGk}62%!8SNp0D6}sDU&wI`B_u{KDtpg!luG{ zzh5+=;@PAU#33RorS-n^wvZZoIRPCtuk6Nw(*+R#WDo9Icg|BL8>jroR|;0ga{}36 znI{kp{#O9}Fy7t>+XHE0KZla_vbk2AocULc7HXC?bYG; z#WA~wWci}2vGL!v{fI1dP=uMCXTCL?B`Y1VjCC3 z5nOKo1hE;*#GT-oDB!HMO^yR&ZNHq)UB07t+@rIy80wsfT8Bq+u%qf_$N#KF-B#b# z7d@rlA<-kP-_oaWS)7YYGeA5Jam#gO-Yus91huKcO8my1q8eTbV*KLq0q57hV+`o*fuH4iDTwy(gAS0qZb*%kwfWth14Pn2LJ@MIn^0-0009E zxNDsx=V^BiwJoL}6B@-1kgVGa1*Ljakc7SG!=bqe0{8uftu;|FeXGhT+W_85K-$SR z`~QUg>Oh7O>?%3C=MgXjHy$o?005QHr{<8;3GE4N4PQS~sbGj!(c3E`(G7*( zt-b%Z(4Av)0w&oet2Z@h;;p-TMYMR6OWli*Mc}>m--A6EcFIpbFjUYOI^O#zK(jWZ>?B-Z;hL(5XcX%uC`O-ZLU9Iq@4m*Tz{ zy?^h^d&1H}r2_zdSfk(s01F;0AduEO5l&nfn#R&}&|WP|s6SLPOH#P)9*F30_(n^Z zoGYNah50IMJ6dYi#oLvmP8NuhnJzaLHn=+^RDq-;OLZ$#e?D)Z)EMh9xQ#<;P!s?@ zn3K{8pfI7Grzj`UmtD#+%j;hrHxUdB?w7OfU;ipd+J4;JE!E#|ILWyDlt6JVKF35R zGblzicWURfodYy<=tcJ`Hp9%P0~7%k*0X*8k*c4q^rAczKFH(PiIL0V(IXEwfW&l? zq-v9#59jGChv{baW;+J~PT$dp;%;0UtJJ{G-5k~2mK{3tEdYO*f87)Lav%Ib^D#h~ zq}}$jv{hie{VLpY+gmtvw%qnry4$m&w*Z(*eQN<;SktwKH2Q~!3ls$aCrN6W(LiUW zGWv5_3HhChfaB#=Qveo4*5AjO9IF0iUk(m~pGLWBHL=oDzB$3cIsAKUm#23oFKF!m zD4>HZoox+|cNGGX1U`t9+KqFZXYU`-nOTx_G?vF|&OycD^1xe0=5ZG8qg{-#E-!zK z0BAehML?wWN+FG&xU0==M&s5zRMWi<`}N=bMuHybPX@slJB5zYUU7%yO^Oem?%A(* z2G9UL$fMK=VDPB+0U%&@Nz#173eKRB&QFF6AXW~!4_3>NQshS54DLrZ00#%#x(>RJ zxq*p=Pqj>Kq^PH3{wLCe6LPO`WW)d$F=BA7%hPD3jPj0v8D$ zFwBxm(!4F{VF0qU{a0g@Z=W9`y*+FVG4@am0JFLVn)FaJB9<)3!~x9$27rFMoZLcJ zbx+y`Cgq+45CF`TqO5abEw)NO@3ccWDFHr6NGXJ=k2zgB{BWMDbN8FfQR$YdPyqxMuUI@lxpiG z06v(L#3DiAFj^UjkeHHJBh@{hNk-#Xr($?D@alL3A#wXt(Gk#KE7cg77B`lw-=;C~ z{en=s;cMXZp;PhldHEWR_tuW$cne6Ec$@v+SSa=pse(rbnE+lmlhjQzr8tvc`#}Lf zl1q~4jn?w4wEUnck}l2Zp}OQrM?+BL(xRmZ-Dr_f2OJ2cu|@{w?w)T@%m#RqTG=ox z&0<)Fd~7GZL&sUrJgGL)c@8x=MGXKxIMdcig2=PI4k!*lmL&D#0j_HJ;B24NCiKKU z3xJ{knsF;^J=&~S_de;_o6#;}}8i+go+)H_-h z(w%4=H1t5XSO7Lyus|dIZxVYOrY3)k4zdHP~>L;FTAbKVmtZ)x3 zMxLfM8UO^ZIVLn5KmyW%9gqaRq+@pZad*per%^Sj6rd6s9i}CS+SQ(~FHZlrcPXfF znvi=AXZ3jO{4Gc#8_WMjI)s3;ox`P4;SGtiIwr5;*UwwoE&v3#nFf=ZEHv{2=wN|% z8SXK=`u%it`S3uLBQy>G&Ejx*U7=10A79n@n^8UdZE>jWcG6{Pqyo835y5Osd8YmNVp!qeXX;)tmh)*`p(7Lq_>+ggI}1mC`b?k(BFUcQ&hYUISK;=Agqj zZf;Xvxko)XC4k-lUYNr@3&4d%qxJwzEAL`Ds+Oz1Jv3fGr6~P08l>2`5uU~Q`+r%- zjwk4gnk63D1;Bvuy`<3Shk46Uas8(d$#9p}5t2W8?DPpye z3`uiHAx?;0F*h7vt)a!4{hhaXYpqH5RkNmH!Aup@xb}j}6?*VyqnX9hDG}@f0O;qh z<29)QS=MczOed}y90kZiaqi2)F;gaWU2`)50svlk;|CH4i%GCO1nQF;fJG#Qpi=bh zrCL$}001;NeLYXagR z2_U>7j=ioTTo^(Iq-{KBSNjp&(`XV;XJ=CY0D!y#0000004a-1VF>^L0Kp`^Ofh3% zVPafkU1wliWMN%iUR`5iUSDBjOif)_TTxa|SXfe2SWs46H%UxJQA|%!T322^sN;tW z3b;8|ypq!_!gia#Z;0fADO!dwy1W!NFz9w|j8xv9;;v{uG3(G1S1#v+g;q*w|DbzN zI-eZ5COxzSycsvEQ%BvY9KH0G0R+&F)BlTr1$RI}n{`ga0=rl}698W5M&;*gbN{wS-?7l7ga`X_gShuc!PmQH`8cHZ;7((&Hz4;a0*2P9v(?!n$ z)nPRMsS*hQeKPpbJDuanTLR%&V(k|yI_3FKI4>ZxivfPv!-q8z7?Z_%0Dwx)PLBE~ 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 IQI8j80c;2fRsaA1 literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/Miracle/changeling.ftl b/Resources/Locale/en-US/Miracle/changeling.ftl new file mode 100644 index 0000000000..9193eea5cc --- /dev/null +++ b/Resources/Locale/en-US/Miracle/changeling.ftl @@ -0,0 +1,3 @@ +chat-manager-changeling-channel-name = HIVE +hud-chatbox-select-channel-Changeling = Hive +chat-manager-send-changeling-chat-wrap-message = [bold]\[HIVE\][/bold] [font size=13][italic][bold]{ $player }[/bold] hisses, "{ $message }"[/italic][/font] diff --git a/Resources/Locale/en-US/administration/antag.ftl b/Resources/Locale/en-US/administration/antag.ftl index 535659f27e..1375969e79 100644 --- a/Resources/Locale/en-US/administration/antag.ftl +++ b/Resources/Locale/en-US/administration/antag.ftl @@ -1,5 +1,6 @@ verb-categories-antag = Antag ctrl admin-verb-make-traitor = Make the target into a traitor. +admin-verb-make-changeling = Make the target into a changeling. admin-verb-make-zombie = Zombifies the target immediately. admin-verb-make-nuclear-operative = Make target into a lone Nuclear Operative. admin-verb-make-pirate = Make the target into a pirate. Note this doesn't configure the game rule. @@ -7,8 +8,9 @@ admin-verb-make-head-rev = Make the target into a Head Revolutionary. admin-verb-make-thief = Make the target into a thief. admin-verb-text-make-traitor = Make Traitor +admin-verb-text-make-changeling = Make Changeling admin-verb-text-make-zombie = Make Zombie admin-verb-text-make-nuclear-operative = Make Nuclear Operative admin-verb-text-make-pirate = Make Pirate admin-verb-text-make-head-rev = Make Head Rev -admin-verb-text-make-thief = Make Thief \ No newline at end of file +admin-verb-text-make-thief = Make Thief diff --git a/Resources/Locale/en-US/alerts/alerts.ftl b/Resources/Locale/en-US/alerts/alerts.ftl index 795d740141..db4a58275f 100644 --- a/Resources/Locale/en-US/alerts/alerts.ftl +++ b/Resources/Locale/en-US/alerts/alerts.ftl @@ -110,3 +110,6 @@ alerts-revenant-essence-desc = The power of souls. It sustains you and is used f alerts-revenant-corporeal-name = Corporeal alerts-revenant-corporeal-desc = You have manifested physically. People around you can see and hurt you. + +alerts-changeling-chemicals-name = Chemicals +alerts-changeling-chemicals-desc = Our chemicals. diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-changeling.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-changeling.ftl new file mode 100644 index 0000000000..be664bc249 --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-changeling.ftl @@ -0,0 +1,13 @@ +#Changeling +changeling-title = Changeling +changeling-description = A changeling has boarded the station! +changeling-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 Changeling. +changeling-no-one-ready = No players readied up! Can't start Changeling. +changeling-round-end-agent-name = changeling +changeling-role-greeting = + You are a changeling! + Your objectives are listed in the character menu. + Use the evolution shop to gain new abilities. + Death to Nanotrasen! +changeling-role-briefing-short = Use '<' to communicate with other members of the Hive. + diff --git a/Resources/Locale/en-US/objectives/conditions/absorb-dna.ftl b/Resources/Locale/en-US/objectives/conditions/absorb-dna.ftl new file mode 100644 index 0000000000..c7df4e2666 --- /dev/null +++ b/Resources/Locale/en-US/objectives/conditions/absorb-dna.ftl @@ -0,0 +1,4 @@ +objective-condition-absorb-dna = Absorb {$count} humans. +objective-condition-absorb-more-dna = Absorb more humans, that others in the Hive. +objective-condition-absorb-changeling-title = Absorb {$targetName}, {CAPITALIZE($job)}. +objective-condition-escape-with-identity-title = Escape with {$targetName}, {CAPITALIZE($job)} identity. diff --git a/Resources/Locale/en-US/prototypes/roles/antags.ftl b/Resources/Locale/en-US/prototypes/roles/antags.ftl index d12f70cda2..7ccfc78ee4 100644 --- a/Resources/Locale/en-US/prototypes/roles/antags.ftl +++ b/Resources/Locale/en-US/prototypes/roles/antags.ftl @@ -1,6 +1,9 @@ roles-antag-syndicate-agent-name = Syndicate agent roles-antag-syndicate-agent-objective = Complete your objectives without being caught. +roles-antag-changeling-name = Changeling +roles-antag-changeling-objective = Complete your objectives without being caught. + roles-antag-initial-infected-name = Initial Infected roles-antag-initial-infected-objective = Once you turn, infect as many other crew members as possible. diff --git a/Resources/Locale/en-US/store/categories.ftl b/Resources/Locale/en-US/store/categories.ftl index 437fc03ae0..95e9f24fe1 100644 --- a/Resources/Locale/en-US/store/categories.ftl +++ b/Resources/Locale/en-US/store/categories.ftl @@ -13,5 +13,7 @@ store-category-job = Job store-category-armor = Armor store-category-pointless = Pointless -# Revenant +# Revenant && Changeling store-category-abilities = Abilities +store-category-stings = Stings +store-category-boosters = Boosters diff --git a/Resources/Locale/en-US/store/currency.ftl b/Resources/Locale/en-US/store/currency.ftl index ed21ab4792..af2efc249e 100644 --- a/Resources/Locale/en-US/store/currency.ftl +++ b/Resources/Locale/en-US/store/currency.ftl @@ -9,7 +9,7 @@ store-currency-display-debugdollar = {$amount -> } store-currency-display-telecrystal = TC store-currency-display-stolen-essence = Stolen Essence - +store-currency-display-changeling-point = DNA Points store-currency-display-space-cash = {$amount -> [one] One Dollar *[other] Dollars diff --git a/Resources/Locale/en-US/weapons/ranged/gun.ftl b/Resources/Locale/en-US/weapons/ranged/gun.ftl index 3fbf5f77e7..0a9d5705cb 100644 --- a/Resources/Locale/en-US/weapons/ranged/gun.ftl +++ b/Resources/Locale/en-US/weapons/ranged/gun.ftl @@ -12,6 +12,8 @@ gun-component-wrong-ammo = Wrong ammo! gun-SemiAuto = semi-auto gun-Burst = burst gun-FullAuto = full-auto +gun-PullItem = grab item +gun-PullMob = grab human # BallisticAmmoProvider gun-ballistic-cycle = Cycle diff --git a/Resources/Prototypes/Actions/changeling.yml b/Resources/Prototypes/Actions/changeling.yml new file mode 100644 index 0000000000..0618871bb3 --- /dev/null +++ b/Resources/Prototypes/Actions/changeling.yml @@ -0,0 +1,208 @@ +- type: entity + id: ActionChangelingShop + name: Shop + description: Abilities shop. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/shop.png + event: !type:ChangelingShopActionEvent + - type: LesserFormRestricted + +- type: entity + id: ActionChangelingAbsorb + name: Absorb + description: Absorbs the human. + noSpawn: true + components: + - type: EntityTargetAction + itemIconStyle: NoItem + icon: Interface/Actions/ling_absorb.png + event: !type:AbsorbDnaActionEvent + canTargetSelf: false + useDelay: 10 + - type: LesserFormRestricted + +- type: entity + id: ActionChangelingTransform + name: Transform + description: Transform to the chosen person. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/transform.png + event: !type:TransformActionEvent + useDelay: 30 + +- type: entity + id: ActionChangelingRegenerate + name: Regenerate + description: Enter in a regenerative stasis to heal self. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/reviving_stasis.png + event: !type:RegenerateActionEvent + checkCanInteract: false + useDelay: 120 + - type: LesserFormRestricted + +- type: entity + id: ActionChangelingLesserForm + name: Lesser Form + description: Become a lesser form of self. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/lesser_form.png + event: !type:LesserFormActionEvent + useDelay: 90 + - type: LesserFormRestricted + +- type: entity + id: ActionTransformSting + name: Transform Sting + description: Injects a retrovirus that forces human victim to transform into another. + noSpawn: true + components: + - type: EntityTargetAction + itemIconStyle: NoItem + icon: Interface/Actions/sting_transform.png + event: !type:TransformStingActionEvent + canTargetSelf: false + useDelay: 120 + - type: LesserFormRestricted + +- type: entity + id: ActionBlindSting + name: Blind Sting + description: Stings a human, completely blinding them for a short time. + noSpawn: true + components: + - type: EntityTargetAction + itemIconStyle: NoItem + icon: Interface/Actions/sting_blind.png + event: !type:BlindStingActionEvent + canTargetSelf: false + useDelay: 30 + - type: LesserFormRestricted + +- type: entity + id: ActionMuteSting + name: Mute Sting + description: Silently stings a human, temporarily silencing them. + noSpawn: true + components: + - type: EntityTargetAction + itemIconStyle: NoItem + icon: Interface/Actions/sting_mute.png + event: !type:MuteStingActionEvent + canTargetSelf: false + useDelay: 30 + - type: LesserFormRestricted + +- type: entity + id: ActionHallucinationSting + name: Hallucination Sting + description: Injects large doses of hallucinogenic chemicals into their victim. + noSpawn: true + components: + - type: EntityTargetAction + itemIconStyle: NoItem + icon: Interface/Actions/sting_hallucination.png + event: !type:HallucinationStingActionEvent + canTargetSelf: false + useDelay: 30 + - type: LesserFormRestricted + +- type: entity + id: ActionCryoSting + name: Cryogenic Sting + description: Injects the target with a blend of chemicals that begins to turn their blood to ice. + noSpawn: true + components: + - type: EntityTargetAction + itemIconStyle: NoItem + icon: Interface/Actions/sting_cryo.png + event: !type:CryoStingActionEvent + canTargetSelf: false + useDelay: 30 + - type: LesserFormRestricted + +- type: entity + id: ActionAdrenalineSacs + name: Adrenaline Sacs + description: Allows to make use of additional adrenaline to instantly recover from knockdown. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/adrenaline_sacs.png + event: !type:AdrenalineSacsActionEvent + useDelay: 60 + - type: LesserFormRestricted + +- type: entity + id: ActionFleshmend + name: Fleshmend + description: Rapidly heal all type of damage. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/fleshmend.png + event: !type:FleshmendActionEvent + useDelay: 60 + - type: LesserFormRestricted + +- type: entity + id: ActionArmblade + name: Arm Blade + description: Reforms one of the changeling's arms into a grotesque blade made out of bone and flesh. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/arm_blade.png + event: !type:ArmbladeActionEvent + - type: LesserFormRestricted + +- type: entity + id: ActionShield + name: Organic Shield + description: Reforms one of the changeling's arms into a large, fleshy shield. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/changeling_shield.png + event: !type:OrganicShieldActionEvent + - type: LesserFormRestricted + +- type: entity + id: ActionArmor + name: Chitinous Armor + description: Inflates the changeling's body into an all-consuming chitinous mass of armor. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/changeling_armor.png + event: !type:ChitinousArmorActionEvent + - type: LesserFormRestricted + +- type: entity + id: ActionTentacleArm + name: Tentacle Arm + description: Reforms one of the arms into a tentacle. Can grab items or humans depending on mode. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: Interface/Actions/tentacle_arm.png + event: !type:TentacleArmActionEvent + - type: LesserFormRestricted diff --git a/Resources/Prototypes/Actions/types.yml b/Resources/Prototypes/Actions/types.yml index ed3029e43a..5abcaf9bc3 100644 --- a/Resources/Prototypes/Actions/types.yml +++ b/Resources/Prototypes/Actions/types.yml @@ -9,6 +9,7 @@ icon: Interface/Actions/scream.png event: !type:ScreamActionEvent checkCanInteract: false + - type: LesserFormRestricted - type: entity id: ActionTurnUndead diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 11b9beec3b..57040cc9be 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -10,6 +10,7 @@ - category: Stamina - alertType: SuitPower - category: Internals + - alertType: Chemicals - alertType: Fire - alertType: Handcuffed - alertType: Ensnared diff --git a/Resources/Prototypes/Alerts/changeling.yml b/Resources/Prototypes/Alerts/changeling.yml new file mode 100644 index 0000000000..f007cd0b01 --- /dev/null +++ b/Resources/Prototypes/Alerts/changeling.yml @@ -0,0 +1,23 @@ +- type: alert + id: Chemicals + icons: + - sprite: /Textures/Interface/Alerts/essence_counter.rsi + state: essence0 + - sprite: /Textures/Interface/Alerts/essence_counter.rsi + state: essence1 + - sprite: /Textures/Interface/Alerts/essence_counter.rsi + state: essence2 + - sprite: /Textures/Interface/Alerts/essence_counter.rsi + state: essence3 + - sprite: /Textures/Interface/Alerts/essence_counter.rsi + state: essence4 + - sprite: /Textures/Interface/Alerts/essence_counter.rsi + state: essence5 + - sprite: /Textures/Interface/Alerts/essence_counter.rsi + state: essence6 + - sprite: /Textures/Interface/Alerts/essence_counter.rsi + state: essence7 + name: alerts-changeling-chemicals-name + description: alerts-changeling-chemicals-desc + minSeverity: 0 + maxSeverity: 7 diff --git a/Resources/Prototypes/Catalog/changeling_catalog.yml b/Resources/Prototypes/Catalog/changeling_catalog.yml new file mode 100644 index 0000000000..475d3a93af --- /dev/null +++ b/Resources/Prototypes/Catalog/changeling_catalog.yml @@ -0,0 +1,158 @@ +# Body Transforms +- type: listing + id: ChangelingLesserForm + name: Lesser Form + description: Become a lesser form of self. + productAction: ActionChangelingLesserForm + cost: + ChangelingPoint: 1 + categories: + - ChangelingAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingArmblade + name: Arm Blade + description: Reforms one of the changeling's arms into a grotesque blade made out of bone and flesh. + productAction: ActionArmblade + cost: + ChangelingPoint: 3 + categories: + - ChangelingAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingShield + name: Organic Shield + description: Reforms one of the changeling's arms into a large, fleshy shield. + productAction: ActionShield + cost: + ChangelingPoint: 3 + categories: + - ChangelingAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingArmor + name: Chitinous Armor + description: Inflates the changeling's body into an all-consuming chitinous mass of armor. + productAction: ActionArmor + cost: + ChangelingPoint: 4 + categories: + - ChangelingAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingTentacleArm + name: Tentacle Arm + description: Reforms one of the arms into a tentacle. Can grab items or humans depending on mode. + productAction: ActionTentacleArm + cost: + ChangelingPoint: 4 + categories: + - ChangelingAbilities + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +# Stings +- type: listing + id: ChangelingTransformSting + name: Transform Sting + description: Injects a retrovirus that forces human victim to transform into another. + productAction: ActionTransformSting + cost: + ChangelingPoint: 3 + categories: + - ChangelingStings + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingBlindSting + name: Blind Sting + description: Stings a human, completely blinding them for a short time. + productAction: ActionBlindSting + cost: + ChangelingPoint: 2 + categories: + - ChangelingStings + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingMuteSting + name: Mute Sting + description: Silently stings a human, temporarily silencing them. + productAction: ActionMuteSting + cost: + ChangelingPoint: 3 + categories: + - ChangelingStings + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingHallucinationSting + name: Hallucination Sting + description: Injects large doses of hallucinogenic chemicals into their victim. + productAction: ActionHallucinationSting + cost: + ChangelingPoint: 1 + categories: + - ChangelingStings + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingCryogenicSting + name: Cryogenic Sting + description: Injects the target with a blend of chemicals that begins to turn their blood to ice. + productAction: ActionCryoSting + cost: + ChangelingPoint: 3 + categories: + - ChangelingStings + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +# Boosters +- type: listing + id: ChangelingAdrenalineSacs + name: Adrenaline Sacs + description: Allows to make use of additional adrenaline to instantly recover from knockdown. + productAction: ActionAdrenalineSacs + cost: + ChangelingPoint: 3 + categories: + - ChangelingBoosters + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: ChangelingFleshmend + name: Fleshmend + description: Rapidly heal all type of damage. + productAction: ActionFleshmend + cost: + ChangelingPoint: 2 + categories: + - ChangelingBoosters + conditions: + - !type:ListingLimitedStockCondition + stock: 1 diff --git a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml index 2bc864e88e..aba1c7399a 100644 --- a/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml +++ b/Resources/Prototypes/Entities/Clothing/OuterClothing/armor.yml @@ -152,6 +152,19 @@ - type: GroupExamine - type: GiftIgnore +- type: entity + parent: ClothingOuterArmorHeavy + id: ClothingOuterChangeling + name: chitinous armor + description: Chitinous and flesh mass of armor. + components: + - type: Sprite + sprite: Clothing/OuterClothing/Armor/changeling.rsi + - type: Clothing + sprite: Clothing/OuterClothing/Armor/changeling.rsi + - type: Unremoveable + deleteOnDrop: true + - type: entity parent: ClothingOuterArmorHeavy id: ClothingOuterArmorHeavyGreen @@ -196,6 +209,8 @@ - type: Clothing sprite: Clothing/OuterClothing/Armor/magusred.rsi + + - type: entity parent: ClothingOuterBaseLarge id: ClothingOuterArmorScaf diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index abf57a405f..50544837fb 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -1175,6 +1175,8 @@ interfaces: - key: enum.StrippingUiKey.Key type: StrippableBoundUserInterface + - key: enum.ListViewSelectorUiKey.Key + type: ListViewSelectorBui - type: Sprite drawdepth: Mobs layers: diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 2aa85a86a6..3b8cf5a912 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -201,6 +201,10 @@ type: HumanoidMarkingModifierBoundUserInterface - key: enum.StrippingUiKey.Key type: StrippableBoundUserInterface + - key: enum.ListViewSelectorUiKeyChangeling.Key + type: ListViewSelectorBui + - key: enum.TransformStingSelectorUiKey.Key + type: TransformStingSelectorBui # WD-EDIT - key: enum.NameSelectorUIKey.Key type: NameSelectorBUI diff --git a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml index dd58bcabc9..003ad3feab 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/subdermal_implants.yml @@ -163,6 +163,23 @@ - key: enum.StoreUiKey.Key type: StoreBoundUserInterface +- type: entity + parent: BaseSubdermalImplant + id: ChangelingShopImplant + name: changeling shop implant + description: Opens evolution shop. + noSpawn: true + components: + - type: SubdermalImplant + permanent: true + implantAction: ActionChangelingShop + - type: Store + preset: StorePresetChangeling + - type: UserInterface + interfaces: + - key: enum.StoreUiKey.Key + type: StoreBoundUserInterface + - type: entity parent: BaseSubdermalImplant id: EmpImplant @@ -177,7 +194,7 @@ range: 1.75 energyConsumption: 50000 disableDuration: 10 - + - type: entity parent: BaseSubdermalImplant id: ScramImplant diff --git a/Resources/Prototypes/Entities/Objects/Shields/shields.yml b/Resources/Prototypes/Entities/Objects/Shields/shields.yml index 7bc600e87f..aef48e42d6 100644 --- a/Resources/Prototypes/Entities/Objects/Shields/shields.yml +++ b/Resources/Prototypes/Entities/Objects/Shields/shields.yml @@ -118,6 +118,48 @@ Blunt: 1.5 Piercing: 1.5 +#Changeling shield +- type: entity + name: organic shield + parent: BaseShield + id: OrganicShield + description: A large, fleshy shield. + components: + - type: Sprite + state: changeling-icon + - type: Item + heldPrefix: changeling + - type: Blocking + passiveBlockModifier: + coefficients: + Blunt: 0.8 + Piercing: 0.8 + activeBlockModifier: + coefficients: + Blunt: 0.7 + Piercing: 0.7 + flatReductions: + Blunt: 1.5 + Piercing: 1.5 + - type: Unremoveable + deleteOnDrop: true + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 80 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - trigger: + !type:DamageTrigger + damage: 50 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - !type:PlaySoundBehavior + sound: /Audio/Effects/gib2.ogg + #Craftable shields - type: entity diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml index ecabe2a4ab..11accf2b36 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Launchers/launchers.yml @@ -261,6 +261,43 @@ True: { state: base-unshaded } False: { state: base-unshaded-off } +- type: entity + name: Tentacle Arm + parent: BaseItem + id: TentacleArmGun + components: + - type: TentacleGun + - type: Gun + soundGunshot: /Audio/Effects/gib1.ogg + fireRate: 0.3 + selectedMode: PullItem + availableModes: + - PullItem + - PullMob + - type: BasicEntityAmmoProvider + proto: TentacleProjectile + capacity: 1 + count: 1 + - type: RechargeBasicEntityAmmo + rechargeCooldown: 0.75 + playRechargeSound: false + - type: Sprite + sprite: Objects/Weapons/Guns/Launchers/tentacle_gun.rsi + layers: + - state: base + - type: UseDelay + delay: 1.5 + - type: Appearance + - type: EmitSoundOnCollide + sound: + path: /Audio/Effects/gib3.ogg + params: + variation: 0.65 + volume: -10 + - type: Unremoveable + deleteOnDrop: true + + # Admeme - type: entity name: tether gun diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml index d8e3176731..9f195302bf 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Guns/Projectiles/projectiles.yml @@ -883,6 +883,45 @@ - HighImpassable - type: GrapplingProjectile +- type: entity + id: TentacleProjectile + name: tentacle + noSpawn: true + components: + - type: EmbeddableProjectile + deleteOnRemove: true + - type: Clickable + - type: InteractionOutline + - type: Ammo + muzzleFlash: null + - type: Sprite + noRot: false + sprite: Objects/Weapons/Guns/Launchers/tentacle_gun.rsi + layers: + - state: hook + - type: Physics + bodyType: Dynamic + linearDamping: 0 + angularDamping: 0 + - type: Projectile + deleteOnCollide: false + damage: + types: + Blunt: 0 + soundHit: + path: /Audio/Effects/gib3.ogg + - type: Fixtures + fixtures: + projectile: + shape: + !type:PhysShapeAabb + bounds: "-0.1,-0.1,0.1,0.1" + hard: false + mask: + - Impassable + - BulletImpassable + - type: TentacleProjectile + - type: entity name : disabler bolt smg id: BulletDisablerSmg diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml index 497876f359..3bc3b9e765 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/armblade.yml @@ -22,3 +22,6 @@ qualities: - Prying - type: Prying + - type: Unremoveable + deleteOnDrop: true + - type: ToolForcePowered diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index b35615b4cf..3a494bb955 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -79,6 +79,15 @@ - type: ThiefRule #the thieves come as an extension of another gamemode ruleChance: 0.5 +- type: entity + id: Changeling + parent: BaseGameRule + noSpawn: true + components: + - type: ChangelingRule +# - type: TraitorRule +# ruleChance: 0.6 + - type: entity id: Revolutionary parent: BaseGameRule diff --git a/Resources/Prototypes/Objectives/changeling.yml b/Resources/Prototypes/Objectives/changeling.yml new file mode 100644 index 0000000000..17c9b02ef4 --- /dev/null +++ b/Resources/Prototypes/Objectives/changeling.yml @@ -0,0 +1,223 @@ +- type: entity + abstract: true + parent: BaseObjective + id: BaseChangelingObjective + components: + - type: Objective + difficulty: 1.5 + issuer: syndicate + - type: RoleRequirement + roles: + components: + - ChangelingRole + +- type: entity + abstract: true + parent: [BaseChangelingObjective, BaseStealObjective] + id: BaseChangelingStealObjective + components: + - type: StealCondition + verifyMapExistance: false + - type: Objective + difficulty: 2.75 + - type: ObjectiveLimit + limit: 1 + +- type: entity + noSpawn: true + parent: BaseChangelingObjective + id: AbsorbDnaObjective + components: + - type: Objective + icon: + sprite: /Textures/Actions/changeling.rsi # TODO: Change icon + state: absorb + - type: AbsorbDnaCondition + +- type: entity + noSpawn: true + parent: BaseChangelingObjective + id: AbsorbMoreDnaObjective + name: Absorb more humans, that others in the Hive. + description: Absorb as many you can. + components: + - type: Objective + difficulty: 2.0 + icon: + sprite: /Textures/Actions/changeling.rsi # TODO: Change icon + state: absorb + - type: AbsorbMoreCondition + +- type: entity + noSpawn: true + parent: BaseChangelingObjective + id: AbsorbChangelingObjective + components: + - type: Objective + difficulty: 3.0 + icon: + sprite: /Textures/Actions/changeling.rsi # TODO: Change icon + state: blade + - type: TargetObjective + title: objective-condition-absorb-changeling-title + - type: PickRandomChangeling + - type: AbsorbChangelingCondition + +- type: entity + noSpawn: true + parent: BaseChangelingObjective + id: EscapeWithIdentityObjective + components: + - type: Objective + icon: + sprite: /Textures/Mobs/Species/Human/parts.rsi + state: full + - type: TargetObjective + title: objective-condition-escape-with-identity-title + - type: PickRandomIdentity + - type: EscapeWithIdentityCondition + +# steal + +## cmo + +- type: entity + noSpawn: true + parent: BaseChangelingStealObjective + id: CMOHyposprayStealObjectiveCh + components: + - type: NotJobRequirement + job: ChiefMedicalOfficer + - type: StealCondition + stealGroup: Hypospray + owner: job-name-cmo + +## rd + +- type: entity + noSpawn: true + parent: BaseChangelingStealObjective + id: RDHardsuitStealObjectiveCh + components: + - type: StealCondition + stealGroup: ClothingOuterHardsuitRd + owner: job-name-rd + +- type: entity + noSpawn: true + parent: BaseChangelingStealObjective + id: HandTeleporterStealObjectiveCh + components: + - type: StealCondition + stealGroup: HandTeleporter + owner: job-name-rd + +## hos + +- type: entity + noSpawn: true + parent: BaseChangelingStealObjective + id: SecretDocumentsStealObjectiveCh + components: + - type: Objective + # hos has a gun ce does not, higher difficulty than most + difficulty: 3 + - type: NotJobRequirement + job: HeadOfSecurity + - type: StealCondition + stealGroup: BookSecretDocuments + owner: job-name-hos + +## ce + +- type: entity + noSpawn: true + parent: BaseChangelingStealObjective + id: MagbootsStealObjectiveCh + components: + - type: NotJobRequirement + job: ChiefEngineer + - type: StealCondition + stealGroup: ClothingShoesBootsMagAdv + owner: job-name-ce + +## qm + +- type: entity + noSpawn: true + parent: BaseChangelingStealObjective + id: ClipboardStealObjectiveCh + components: + - type: NotJobRequirement + job: Quartermaster + - type: StealCondition + stealGroup: BoxFolderQmClipboard + owner: job-name-qm + +## hop + +- type: entity + noSpawn: true + parent: BaseChangelingStealObjective + id: CorgiMeatStealObjectiveCh + components: + - type: NotJobRequirement + job: HeadOfPersonnel + - type: ObjectiveLimit + limit: 3 # ian only has 2 slices, 3 obj for drama + - type: StealCondition + stealGroup: FoodMeatCorgi + owner: objective-condition-steal-Ian + +## cap + +- type: entity + abstract: true + parent: BaseChangelingStealObjective + id: BaseCaptainObjectiveCh + components: + - type: Objective + # sorry ce but your jordans are not as high security as the caps gear + difficulty: 3.5 + - type: NotJobRequirement + job: Captain + +- type: entity + noSpawn: true + parent: BaseCaptainObjectiveCh + id: CaptainIDStealObjectiveCh + components: + - type: StealCondition + stealGroup: CaptainIDCard + +- type: entity + noSpawn: true + parent: BaseCaptainObjectiveCh + id: CaptainJetpackStealObjectiveCh + components: + - type: StealCondition + stealGroup: JetpackCaptainFilled + +- type: entity + noSpawn: true + parent: BaseCaptainObjectiveCh + id: CaptainGunStealObjectiveCh + components: + - type: StealCondition + stealGroup: WeaponAntiqueLaser + owner: job-name-captain + +- type: entity + noSpawn: true + parent: BaseCaptainObjectiveCh + id: NukeDiskStealObjectiveCh + components: + - type: Objective + # high difficulty since the hardest item both to steal, and to not get caught down the road, + # since anyone with a pinpointer can track you down and kill you + # it's close to being a stealth loneop + difficulty: 4 + - type: NotCommandRequirement + - type: StealCondition + stealGroup: NukeDisk + owner: objective-condition-steal-station diff --git a/Resources/Prototypes/Objectives/objectiveGroups.yml b/Resources/Prototypes/Objectives/objectiveGroups.yml index f2d900afe6..82d7561102 100644 --- a/Resources/Prototypes/Objectives/objectiveGroups.yml +++ b/Resources/Prototypes/Objectives/objectiveGroups.yml @@ -126,4 +126,33 @@ id: ThiefObjectiveGroupEscape weights: EscapeThiefShuttleObjective: 1 -#Changeling, crew, wizard, when you code it... + +# Changeling +- type: weightedRandom + id: ChangelingObjectiveGroups + weights: + ChangelingObjectiveGroupSteal: 1 + ChangelingObjectiveGroupKill: 1 + +- type: weightedRandom + id: ChangelingObjectiveGroupSteal + weights: + CaptainIDStealObjectiveCh: 0.5 + CMOHyposprayStealObjectiveCh: 0.5 + RDHardsuitStealObjectiveCh: 0.5 + NukeDiskStealObjectiveCh: 0.5 + MagbootsStealObjectiveCh: 0.5 + CorgiMeatStealObjectiveCh: 0.5 + ClipboardStealObjectiveCh: 0.5 + CaptainGunStealObjectiveCh: 0.5 + CaptainJetpackStealObjectiveCh: 0.5 + HandTeleporterStealObjectiveCh: 0.5 + SecretDocumentsStealObjectiveCh: 0.5 + +- type: weightedRandom + id: ChangelingObjectiveGroupKill + weights: + AbsorbDnaObjective: 1 + AbsorbMoreDnaObjective: 0.75 + AbsorbChangelingObjective: 0.25 + EscapeWithIdentityObjective: 0.75 diff --git a/Resources/Prototypes/Polymorphs/polymorph.yml b/Resources/Prototypes/Polymorphs/polymorph.yml index 46590248ae..bdbd273873 100644 --- a/Resources/Prototypes/Polymorphs/polymorph.yml +++ b/Resources/Prototypes/Polymorphs/polymorph.yml @@ -148,3 +148,93 @@ revertOnDeath: true revertOnCrit: true duration: 20 + +- type: polymorph + id: MonkeyChangeling + entity: MobMonkey + forced: true + inventory: Drop + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + +- type: polymorph + id: MobHuman + entity: MobHuman + forced: true + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + inventory: Transfer + +- type: polymorph + id: MobArachnid + entity: MobArachnid + forced: true + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + inventory: Transfer + +- type: polymorph + id: MobDwarf + entity: MobDwarf + forced: true + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + inventory: Transfer + +- type: polymorph + id: MobDiona + entity: MobDiona + forced: true + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + inventory: Transfer + +- type: polymorph + id: MobMoth + entity: MobMoth + forced: true + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + inventory: Transfer + +- type: polymorph + id: MobReptilian + entity: MobReptilian + forced: true + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + inventory: Transfer + +- type: polymorph + id: MobVox + entity: MobVox + forced: true + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + inventory: Transfer + +- type: polymorph + id: MobSlimePerson + entity: MobSlimePerson + forced: true + revertOnCrit: false + revertOnDeath: false + transferDamage: true + allowRepeatedMorphs: true + inventory: Transfer diff --git a/Resources/Prototypes/Roles/Antags/changeling.yml b/Resources/Prototypes/Roles/Antags/changeling.yml new file mode 100644 index 0000000000..3b347a452c --- /dev/null +++ b/Resources/Prototypes/Roles/Antags/changeling.yml @@ -0,0 +1,6 @@ +- type: antag + id: Changeling + name: roles-antag-changeling-name + antagonist: true + setPreference: true + objective: roles-antag-changeling-objective diff --git a/Resources/Prototypes/Store/categories.yml b/Resources/Prototypes/Store/categories.yml index c16972c8a3..89f4973528 100644 --- a/Resources/Prototypes/Store/categories.yml +++ b/Resources/Prototypes/Store/categories.yml @@ -68,3 +68,18 @@ id: RevenantAbilities name: store-category-abilities +# changeling +- type: storeCategory + id: ChangelingStings + name: store-category-stings + priority: 0 + +- type: storeCategory + id: ChangelingAbilities + name: store-category-abilities + priority: 1 + +- type: storeCategory + id: ChangelingBoosters + name: store-category-boosters + priority: 2 diff --git a/Resources/Prototypes/Store/currency.yml b/Resources/Prototypes/Store/currency.yml index e26d0c39ec..f61daa685d 100644 --- a/Resources/Prototypes/Store/currency.yml +++ b/Resources/Prototypes/Store/currency.yml @@ -10,6 +10,11 @@ displayName: store-currency-display-stolen-essence canWithdraw: false +- type: currency + id: ChangelingPoint + displayName: store-currency-display-changeling-point + canWithdraw: false + - type: currency id: MeatyOreCoin displayName: Meaty Ore Coin diff --git a/Resources/Prototypes/Store/presets.yml b/Resources/Prototypes/Store/presets.yml index 3e370a4fab..3f18fb70d3 100644 --- a/Resources/Prototypes/Store/presets.yml +++ b/Resources/Prototypes/Store/presets.yml @@ -23,3 +23,13 @@ minItems: 3 maxItems: 10 salesCategory: UplinkSales + +- type: storePreset + id: StorePresetChangeling + storeName: Evolution Shop + categories: + - ChangelingAbilities + - ChangelingStings + - ChangelingBoosters + currencyWhitelist: + - ChangelingPoint diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index dbdeebb627..bbd30e541f 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -61,6 +61,17 @@ - Traitor - BasicStationEventScheduler +- type: gamePreset + id: Changeling + alias: + - changeling + name: changeling-title + description: changeling-description + showInVote: false + rules: + - Changeling + - BasicStationEventScheduler + - type: gamePreset id: Deathmatch alias: diff --git a/Resources/Prototypes/secret_weights.yml b/Resources/Prototypes/secret_weights.yml index 9fc1d576be..d994c26b95 100644 --- a/Resources/Prototypes/secret_weights.yml +++ b/Resources/Prototypes/secret_weights.yml @@ -7,4 +7,4 @@ Revolutionary: 0.10 Zombie: 0.05 Survival: 0.05 - + Changeling: 0.30 diff --git a/Resources/Prototypes/status_effects.yml b/Resources/Prototypes/status_effects.yml index bc84dfbaf1..1c2bd7fbf8 100644 --- a/Resources/Prototypes/status_effects.yml +++ b/Resources/Prototypes/status_effects.yml @@ -60,7 +60,10 @@ - type: statusEffect id: StaminaModifier +- type: statusEffect + id: BlurryVision + alwaysAllowed: true + #WD EDIT - type: statusEffect id: Incorporeal - diff --git a/Resources/Textures/Actions/changeling.rsi/absorb.png b/Resources/Textures/Actions/changeling.rsi/absorb.png new file mode 100644 index 0000000000000000000000000000000000000000..1eb4a0bb0b28f47bae2ebf83ded44d0837b913cd GIT binary patch literal 231 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Clothing/OuterClothing/Armor/changeling.rsi/icon.png b/Resources/Textures/Clothing/OuterClothing/Armor/changeling.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9c0c95f12822056d3c767f78d05a9a754495248b GIT binary patch literal 1047 zcmV+y1nB#TP)(RCt{2Ry{~tQ560pRTDv-nkOiUL$G3q=%Sq}Sc`+!&45Fl z#3^Y(bSZUd+9^=55elJG-5gt;%#wDnaZwD2!-EeLkPj+uMt#rX~pCUlMgpfUlwQy}dmYi$%4T z3q`0P=qTdx zI9}hr4SfBIIU&JwcC27&fxDkR;aMnzt*tGkVC>^ZOioTJtLS3x=dwSyvZ7>jOG}6e z$?4-`ydNBd9b8I)W+kY+xw%;>aNoR%a5${iAMmx+)#}?MN4c2DPcJMWBeGW}K=Ze@ zwqh)uM!ryhCTsqC_b|&%QT%LwA35=HPOS5!p`lFVyrp`&yHQ_Xf2kWTB*51n9v-U8 zG`Xx$C{#>rY#5!Loyg^K$dt?;k+Hk03x2;Jb#-;nb-hBA8$9U38o{v9~%&>XGEGtoSvS-eQuAvfM7G42n5vktEwuvU=NlAtX|>7+*cy-xAD&jdL*{5xZ^&qDM6f8$F| zPRC4iw-{FVdQ I&MBb@07c(s_5c6? literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/arm_blade.png b/Resources/Textures/Interface/Actions/arm_blade.png new file mode 100644 index 0000000000000000000000000000000000000000..792dde45000070e0055ef2958bfacb43538d3a9d GIT binary patch literal 753 zcmV_AODZZVIVL7SBO^~RFj6!$EiElIH8oBmB3B(9JtHGeE-p|rGf5&M zmrqY=KR;0$8(A0_IXO8uH#adcF+3|PM9Y-W2NG2v!DJe=PCzD=Yp<7$0 zU|_ONPH{m&WJ*e07#LF=98De`Q63&cBqT;5A$1uUb{iW?BqUZH9ZoSZO*lAOT3UKw zV3S>4lwDn%U|^1pj$~wHUS3{R92{31991A7TpS!%ARtmCBvT|LOerZ)Dk@zgBU&XT zS0f`>ARt66EK4LLVPRo>YiosSYK4V`b8~ZOXlPFz9aS3}QawFSGc!#vFj6WiSs)-+ zA0Jp99cU91M=mZ!Dk@-LV31#5iDhMlczBm(WqWC9epy*bA0JL8CUkUkqgh#-V`G+P zW}Rqgm3VlNetvy zu>b%70d!JMQvg8b*k%9#0RBltK~y-6V_+Bs^Z|?nmm7bK5E1s^{Hmw+H{c@`m{yf7mZGcFrMfbybZKp$XpqPV!Ygd~@Ml(aBN z9;QN^K?VxNW#!}*goPEAl$oFkR8)o4)HO7lLNvD*6_=EXl!=rIgFtyjWfg|+s%vVQ7=fU!zM-)RNANVa jw6?W(bavs6;Xw-keHSW+u}5}500000NkvXXu0mjfr+Oj!UIJUyHs5M7Lo;n#7+y>L4?NS;wBPzAT4JM#=T)izSG+q4|On^zLV&}pY zGm4bpPG^!RYiye#gzy|kQ~Hc_wGJfzD^F@@N>)G;P@lSBX*#k1_q2w#oean-QZHPc zj;umCv7s>;VJicZYIj3tYXCw(ds;?A$Lwr`9=$W60iiMHzybh2n?_!@p)9dNt7@{%p=-Gp!%?bi60R|uE`99Ed)?v8F6u`y7 z^>_OuOE}`9=5dOhNc__-wcNR&w=d3=k!9CRfdtN1jlLX5_WVi9G?MZV_+>OL@|I<3 kz)|Jq#NBK>ujc<`DmN2#;kGmB13HYs)78&qol`;+0AwFjvH$=8 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/fleshmend.png b/Resources/Textures/Interface/Actions/fleshmend.png new file mode 100644 index 0000000000000000000000000000000000000000..74c021241197a981265ce75ec43f6c830316e453 GIT binary patch literal 150 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnH3?%tPCZz)@&H$ef*8>L*{AV!!zhdXZea9yQ z1sO|%{DK)Ap4~_Ta;!aF9780gCMPHe8??1qIY_JybCe7|TwoC4yu7WIjX^SJ^|H3L uvlx6I3%qZK@SfS%uBynbC3IjvSOhx9L*7&9>ZKa)1m11L1} z|4ieV#taW0JV;DTY-nh(Jo)YsP?)hK$S;_|;n|HeAZNO#i(`n!#H(i=xeht-uwFRA zcztc7+=54T$r=0?w0R!iS~y|vYB`4`+0*ZZbw>$(V5&Qj6nChmVL$)1t=0J}?!Lcs zuux)A^Ez9L2DSNG>|4dySFAe9G_fzlQeb0))7}Xy56(-;(V4RL)zLpvpKN@$rzq@w z@QYQDL9hS!O#5iQJy+JKHby%2G-P)c2yEG~#9L=3FUS7p?(?e6B<3dNPlyIOk-^i| K&t;ucLK6UwcWFQX literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/ling_absorb.png b/Resources/Textures/Interface/Actions/ling_absorb.png new file mode 100644 index 0000000000000000000000000000000000000000..1eb4a0bb0b28f47bae2ebf83ded44d0837b913cd GIT binary patch literal 231 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{*8>L*Fa<3#K4ot_^Z)<< z4FB2K;*~=AfwGJxL4Lsu4$p3+0XfZ{E{-7@6W2~S$a&a+$92C9uflY`3$u6<40hgL zz{qp(_xej;)HIKOYv%i9Q+t}}pH`P!-+{#}3sg&w6|cT?)ThAy*#6YiuYXC*(>Nb5zKF@~rd;PP)Px$O-V#SR9J=WR!feAAP}vd%n?l37|-IyghR0762=1%F2RmNu=FhMNW25H84U94 zlIcz_#% z28=PTChXxkLI{U3@r4!VoMWg5IOo-&LL2bngpf-H2q7pFs0zFjRRgVm?}4ep$18wd z1yah>^E}T@oMr=k-zQ?BfRr-fV1PEzb=`5l?|UNVpFZeJG{CTAP?*jGbbP zu|(Wj3reYKS43BUQksoxtq(IXCpMnL(^U{wAx@~ZK3zj$%-;iC*bT#WT(Pj^G5)WOH(z0`=pq;dJfVZe$5G#p5bF>V-!;y2s2Wet>ups5v4(*yu;%zl6M x@hfOPlI6|^wORj?Bbi4yA>(*Uz8g0_iXYLeq*3X%_Rs(T002ovPDHLkV1hMtuNeRU literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/sting_blind.png b/Resources/Textures/Interface/Actions/sting_blind.png new file mode 100644 index 0000000000000000000000000000000000000000..520fb18c592f9bdeb96d36aff2b44d65e7d2c961 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvtpJ}8*Z=?jr7@{#T zIbngZfYW1R?nR#>n|K3Q?*s+@VS)*Z=?jGtB(YaOOWl+JB%J zgYkbmd4@D&h73&x-%aJZKoQ20AirP+hi5m^fSe9b7sn8diOC5Ij0Kz?8*?xE6xqZZ zzvmIjYVl32rsn|BzJi{_USyIx6cR|#hI}!n`K|wMBTM8U%8V#l_ZDd@; mnx@;%?V`Tv@B$?s6J`e1oBX$u-v8eMaW literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/sting_hallucination.png b/Resources/Textures/Interface/Actions/sting_hallucination.png new file mode 100644 index 0000000000000000000000000000000000000000..7cff4fa73cb8776fcd2348c02d4a01474fcb591e GIT binary patch literal 324 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dymH|E?uK)l4KhMDMpMhZ;P=tYD z9s`4&JVTl>Lxv_pXY`3$fBye78UFug_w*q%yL`1r)=5pDJLVRaUZz9>UFMN z$=udL8Mev7y<%)mPKO`NIm>zR=HH_qWEvi%I#sqTuW?%cRB^@QI_;Iq-iB7x7oYfZ zoGDQDW6=9s9~V_cwck5#y5+?!dHoof%k7Wt?yu>Sm>K!ghe{dx|LqaXZ(=cBxU)X&@UC@dG~e=Ntu8)S&2pt>^QY_g@4wna**Z=?jKYz^d|3Ab3`#>Rv z*9WET_!@pGsV-zF+^ixa>4>* z0jI~t+>1U%Ht`0q-U$l)$)~Bgg6U}C zhge5?c}ZBLHyvQ%n16O|W8&95fg7Brimagr&h|38GQ8kkFn9I#H{b3t34UPs&E)Co z%{<}3tzLl-1&+cD)iW*Z=?j>+9)dq@^h-ssqLB z@q zMV}()@CLBPI0yXXQ_Nt>T*s$T6E>a2L^viWz*mFQHB{ggpHX9Tqwuq~3k+;}DU4rB(?44W?t3Bz&3;7jBG)JqJtg-LuIwpiV6R$>VWQJ@O1TaS?83{ F1OQ|~QB42< literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/tentacle_arm.png b/Resources/Textures/Interface/Actions/tentacle_arm.png new file mode 100644 index 0000000000000000000000000000000000000000..5dc17c582fed375f8ad36c1a536ca559dbe6be3d GIT binary patch literal 1095 zcmV-N1i1T&P)iTH){*tPu>P-`D`2p5s z8A*~zCX?3WPf^9z3Y{ADTcXp~Q`p{myPNQ7)A!}~2k`lhEU?-SkNSGTu`>bek5r-eFEB%aG5Ns>nu zca*VM%#tLDnNSExlK4q#!7vPN_w=w~`^*gtSx_!SS(XXy{^n6!&~?269#5w|kphg2 zj98{=VwSraV}`-Ia#=)4k^qS3a^%Bd-q_e+I2a_G%>wYLy`QUVs6@S_s+zlI0sz$# z11NS50ovNyo^BXM1^A^iXV?pe`9%?U$a6nQb5uYNOjGaGE@8%`|@2sw})zrl8M>|N8ge=Q7;7h8S z0_NRo`x0>1L&7f1=H_O;+ukN}`ZT|W!#w-&A;ZBS6KBsd*wDZahQWK;AGy%l>YG-8 zc1`ni&;XRH+5Ld7>opH2lS%9Id>(+0LLu%Kix3J`iWAC}{)H?v=riaR6eSg{S#_NP zfI-6`aRB%yt*tEX?07)m3OsYS`WtXZQ8ZgQ)d2^n_n8|6XDoNl^xKct`S}m4l7ioR zdo6(BV6X!0R%>oxNCT)QVYlyjr|SK{Jp`&rIQH>#FL)yM%kA92kmmO#JScKs0uBj3 z$Nh@RA<(*+_e{Oc$mTb$9Z29Pwo)~y{1y1I6F7{W_&=ln37j~AKLNpf)Eft~YPSFY N002ovPDHLkV1m`=6PW-2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Actions/transform.png b/Resources/Textures/Interface/Actions/transform.png new file mode 100644 index 0000000000000000000000000000000000000000..0e9e02588f03d8f29c32b97045ec5f0aa17b2da9 GIT binary patch literal 705 zcmV;y0zUnTP)Px%dr3q=R9J=Wmd#2VQ5462kt#9GgeWF9NvR}OL1;oTMF`zw+htzBx@o%+N_Reh z1)*I^p<5q7Um**(+J!>Eng%5%r3p!x$ro0fF4CEfF?Q~q2&Ld}b#rg#od29T9~b^H zF1tTuv)R9%@2>3Yk4B?RjRx?1x)^9Lolf8Q{D_?ZI~F}loc=5TaQdpQ%RWDpfQXvc zrxQUIJxc&2o+sE{*_RUGSU^P0>yMu#&ELtu9HD~O6eG&LVV!qU>-bXe6`0%pKr%2# zj-0S9=?GYzzu)=@K(>Bqo^L;WDP_;mCunOL0Piwy0C?uU0N~4ux6-zx1)TY2U8R#F z+L}hU)kOXLn#-+Knw@hBfERRgMR1D&bgEbsVX==+@srSnrkZu-+lnlXm-uD2(~$3ynXF^8Gu@~ z1oV>7N;KrBSW&5KT>y#|m3S=3K`#n7=7VqqYSmJ&{C?w6lq=`n@iB9~kXdsXLhKlN z91n|d8BR`xp_&)U?A*?VKB2gU>M2 za)Rog1{4kX9oZSW44sVNTNN-YcLM$|0mgOA4$;eqlmKHa54w_BiN}Ic nHzKJ%(0>WBQYbfMOqu)w#6I=gVB8>300000NkvXXu0mjfL)t`& literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/base.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/base.png new file mode 100644 index 0000000000000000000000000000000000000000..9226c273d48287bc27fe36a2b21a1bd0b29824bf GIT binary patch literal 1078 zcmV-61j+k}P)+N(L^9K)4u*{S5{GnN=e&9MdEc8i z{)?Lqyt#MoyYGJA`R+OAUJD$u{U@-m1V#W_@qyA~0_cf*^p?>6C)O zplMG85&=K3J7UCE|7hMLVTszbv^1!gm;jG5zH0)d24f8T$?s4cv1X?e{Ij$0E*yr> z-1x2tjBxTZ>%*~+3BXBm)*m&S0iw~p5g;p#m%j|bYmDZK7z_U8W%#td4rlTIo)y@M zp)yh(wgmnjz4w)r!1=MUoe{u@_EI~wvN9wOpoF)~=GKDLR5`+LilU+CGPunqM}8wd zo?s9zAvlcz#)2UMo6RQi{~xWb_3|KW|2w167Sxu=NeJYa z4TFR790Zi8CQu;ae8|E+Zx$XLWmTE6ei2Hn48>aX4(B&hm%z!0Xec`yhUWZ~axjSC z1W(&T;4{fbAh*yz7KGlL03uC!wgQXk!!!FyK=uDG)*6UnUb=o15d6p*7_` zf%B5*#>%snfPqQn<>hc;cvy~w^M6fpc~{ zd{992;AB6DJVzp78WK3$hQ?@Ajfa%tVz@FsE(dCRULIV<1BNucV0_+ve6Hxa1s7yg+B@Fm{ zCZlyRoi!Cy4MJV2#eGcm{288NWX}=w8|AzL&W!qRasO+5J{Zt_PiF<__Ftm_k*Gxv zJPE+Nai2T$^I;Tgy12NgzPn*L(ra)AcpLHYC|9sPrFc~t9N%gd5CJMYZS%9z&%cto z1^rg51PJbSI6%bXl+9OHusgh%>t3D&0aN+zLSPp4RS9lPsshTQ(u<4}aC?3h+8{|z zLwANFCbP1TzA6$h5zsG8rF%2a;|TC19;c*j3?=~98(ld0TQ6PNs4{GISB%Sot$>V- w$=|KJsBGt=y07b%7 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/frope.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/frope.png new file mode 100644 index 0000000000000000000000000000000000000000..5f72836fd2643956d8605642456e25840fa7147b GIT binary patch literal 1324 zcmV+{1=IS8P)P89>BK1iI@W zPaQFgQTB|}*$dzoJF_GJC|H2}B>Wpy@qGpa-qN(!HBp?un>v4@@N4{?l1CT|6aX+Q zC@qdNJ7>Yz--@!c-ElI_t~1TwVOsm8Xde~;8{}+MZGFl#^u5%cA64WWTSV>W_&yWr zL5ae{0tnoICJ;VVweB{DX0x7G&82!M)xler0|?xwj7$1_AyQaj*gI8*5 zn-65tBaN zjr+d=FfA@~3-AMJqvMWuWYRw;Yijqu1MuPrU4IwX&5yK-?&JFM$NXKp*x6xSz%*mN zwlN5&u8ZRA!Ax2isHu&APwAs|XmtrQ1oG1=cHoiCk`Ois10dfRwEgfrfK%+UOh!6>P9%E!0AQ33=Za<%5V2o(DvX{+)*n;}+h_ z$8))FFy|x$5#R24f8g43zY?wQw9F|e!2(+?kAwywq1BLxdXR{6*NGTs8|ZtCOu;~Y zs7B#Q$fsZ-Yi>K3Ti*}_5;hCq3DZb@rfLUIR#v{+LFs5xlH$88YvM`M=*R6D_%Htp zpl=I;rS>zn=dC3TePq7zeUu~zt2cBo-?w<)U%*ZNg{p4Hs`wk^QSI6wqH3=si8 zkUn7B6PoW|yadouMM$>Ern9&%4k$`9X2=p3UNj9aR*QaE3oroo1x2ZU*s>-Qne?wL z3+kQuYmNZfIU>elzMq5;JwX^ASOI~;$3`SMu?c$u48V5L2MJ^1wNBIe@Nv_a;Q)$O z!kd;)*3ys!=DnZVv)_r)jyB6m^=WDsrsjEM$3a-tloSK;)1u>sB_a_Uk~VBea#tj2 z2h*Iz0NeN{64}hI$OGFj6>mTpI--CFWbmb7S-xkHh^=3#(Jn^CZ1a*N-FL4fH=ytV z;7!ST0|-DoW}4HdRqdgyNVd2d_NAG0u(6-gU(hX+u&OI62Y>(sR>9Q!(TZO7z#0;E zeIb`S19=i+<+?0AVIim-)HHS2Am?sUx^+lV;w%tx-9{>ISRh0`WEzX*01!m?*HN`` zqvzIdaoov>kBx^UL96SHV^D5PMa0VIbbSEIWC1pOtOa9Oj1s$jx;!gI>X`PYF%2;V zZ#1~>5UeW0>)jQvQ0p;lI?hGaqgV^}uxHjAA`kRKz8IC%3xox*(h;ExcAynZY|n=l zGU=9KNouciTowa5@P9%mLlzXY!58@JPaFCOaCepRceIu?6tEwd7>$U#&Z_DmB%oooh8402 it&`?w2I*SLq5lHP#yEE!E+FIp0000s9> literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/hook.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/hook.png new file mode 100644 index 0000000000000000000000000000000000000000..5c1411be3d8c9d82dc722207367de95e918dbbb5 GIT binary patch literal 945 zcmV;i15W&jP)NM3 zr9;#NXj%{IVOy^KZ=%?e6@Jv4&Ig=a7XUc9E&#a&{{Xz@wNHQ_uz?0g-KajJ{v-h0 zXcM_#8wMHO1@e4-=Xp+PEesU^GR#Es@%vP!6a*UHfnJoCfW<&W0iYoU(uHO0R6ZAi z&v^85=SXvP(>;7f2tp8m%Xk~eZyet(j@@*Vh}73=~w@jA*R=1v=Kf+!NPL&!-k8uM>Pu+Jm>QkD-@U;SG3otM@7}F1B|D}Atzkh^qgB2*h&`wIJqtWaB^J$ zN(%ERPT$xrPpf1AGEPs&%}0fy0uXvRp%b4x{}Dh$0cc|MQv4-_)*%D|q{Qe+GCtor z1TTOg5>fGqhotGVWe83H+8BLE97M|y761mtNs#Zj$`_k&c}!4GgPJos--`-cMtV}E z0)U3`Xshy|;(demH3Ps$eHo)CrJT~ZUQm$n`Mu?A0Y2k%#36*%4tq(}1#-^jM44v0 z!wi7SJ6pu)_msBGbrm!M~^o2|erduY>>q01jnXNoGw=04e|g00;m800000 T0Mb*F00000NkvXXu0mjfR^XU( literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/inhand-left.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..c2cf0f4b5b04d9b4a6cde61e140e5b8229877dfa GIT binary patch literal 1250 zcmV<81ReW{P)`S2tlNhHDI%g_F%LpF209O}Bw2qC%?h{Y ztUI!lQg<@vMwgk7gq>aXe0$G#&OP_eoZE(wBN6~1M1mNf|Q1tIw7S7$cp^lLVsZ>g_^W?+=y&m$*S+`53N+snu zj`68!s_!2?0*ib|NC+_jcrYx&m0Pzkvu_`)cwCiAB`Nya{GX{*utA(QU`&(P5)hvP z9JOzNkNACOXD7d?EJbfCe4I6$Ma)xS=QlR+t>awI3XWOJD!}Rbm*c4Orirgy7eVrr zcyNAw-Q!`Z1qedxKA`*PQTM{7OQtH9)qH=yJ5ng@Y}R(JzyJA>LczR{OrrbnVHeZW zrj9X+3&3eR+YqJ7Hk4X2J9XmAHd`! zT#9}}>iP!TDc4n;MUA|Y&?3O|`T6z_(rLuwaZFGOqgdQBS0m2};)O7@2yikM!|>V~ z-l6|h!@ver@}KwbUm99mOrQzyG+7)Py8Zc`I|q8H0QL~i(>kv-v=zbYU0pc1xrud( zTqiAIHI<3|z`W@vmZnsnLX&ID;xB&TBObyb~#TckoF+l@AK@OaxEO316 z2YCHwy9v7ff&~1XqVLpsB_S$6&3ST~U>xY^P#m#hv1p9a2frqhs#y`(`Lo0`S}q$P z5kR)YhqIJTqx@bFq5_cfubf+Y@&sQuq-=t(lg-}~CH`f*L>aKaf(S!f4d6&;DUx(i z%Zw~J-|Q6dZ1c2%Vp09r`i0h=pf~8_eSL6fD)_ZMPDPNHP+iV5w&jsn(Wmo4?ff8m zCWw$D5&$7bBmhE=NC1Q!kpKueA^{L`L;^?=={ggj8)5?3^=`1vdv_fW5x_gNz|B#9 zlDi%E@lpsZ0Jp@)ayeXIUBwg$@grIbfdzQc-Mx7wm1_T~rw6BiS_`|C5J-Tz@$tV$ zCMS3B+&@Jbz%48mV195=9m(fKr-2SmhDZ(I9{$|$u={Xk#>^@Og9@SKfOR>~2qXYU zpR>U0*zQOq0O7`kZUoK%V`b%4$JRRw@FwARDGojbdV60WpKtTd0$dy$1E!|XDhMpV z>1)^8ey}X`aSwlG1!wNvdwIM?aLzIpajKC*;kBH88>oQH&jK}zW}L6Xm+*lE_-@Z0yjQ8<-D(x>u~;n&{-F1|pktRANPv2=QN0xT zKX>gy>E=!71n)7V?>7Poz=N5kWseUo&Mq%QXBU^j-29u8oFW?`9@t%~2Rcr0{Ks#J z@&%p(gLqjCkI@ncU_0&t-<}%aVH=$nhtva+mUNyGJrhL85qbgs0kKaM-3n(c82|tP M07*qoM6N<$f*n9c4FCWD literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/inhand-right.png b/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..35fbe866b10bfd4f536bd4d092a1b948c56d8d61 GIT binary patch literal 1259 zcmVJNRCt{2nn7qAM-+yCL6x9TL*xVrj1DGND2AXWmmGRg53(_> zt2Cy^go3DJQ<7d9LZGFG(nB%y)D&!`7zz#aQ0zoi!6@oM5JGPWA_R(~#L&$_A+~U6 zv(zd~->-OyaCarky9&GP{#a-&W}n{s=DnHG&b$VnD*ynWD*ynWD*ynWD*ynWD*(_- z<8(T$DwRqIE>|yp_M8Dq{(i}3ku4Nl1)`A6D!#v0DB$fL?&}I*5vc!=PRDpnsxty0 z@3P=2qeBSd4S^tO&;s&drSc>P#I5e@2ta}_^5#UXrZi0peH)3WpHH5|xk^P%x-1JA zcu@lXW^)rCL?XybXC)E|MgOd~V}N297H zzIIbb0Hj8WgM));F!+rPe5q-_W;JaRE*1Uz0(h6#^joBIlD$^#yJoo~9L z5GyoPgv-Tp6~EOLDTQsnEoBK{DU*4Ax?GN2i^nl^>XeRrJ_3(v=DX;PD8H~s~4JK|8{ab9yQu7-9~#DMt!e-oJk zAf=qpD#-$ZJ)M&qwGPWkH_C_s+huDNgJlM=@kJQ~IfZyFcteKY7e_~-Nhz4M>9`K4 zTHNF{b&4Z+CJ3J^005sW005sW005sW005sW005sW000ll$#V*x1Hi|1P9DQ7Y;m`G@$q9EoSxRF z^7)7p#ywR6)c)AZ?LE7_PxS;KVX?jRD%E~oA@uN}?cHimWx(~B8Ju5OfN@>{$>hH% zm2BDU34pQb*iB2BjH81fZSlyCdi1DuUg51HN5B(nja#>DcVMpub`{iAx$JnE@Q4c+ zLYxKuUR^~p97b3`Os}r03%b7N?{mBajMWD|d?Mtu0s!#2{sX(^ V6*Ikw1GNAE002ovPDHLkV1ngPRh0k$ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/meta.json b/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/meta.json new file mode 100644 index 0000000000..f877bb35f1 --- /dev/null +++ b/Resources/Textures/Objects/Weapons/Guns/Launchers/tentacle_gun.rsi/meta.json @@ -0,0 +1,35 @@ +{ + "version": 1, + "license": null, + "copyright": null, + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "base" + }, + { + "name": "inhand-left", + "directions": 4 + }, + { + "name": "inhand-right", + "directions": 4 + }, + { + "name": "frope" + }, + { + "name": "hook", + "delays": [ + [ + 0.07, + 0.07, + 0.07 + ] + ] + } + ] +} diff --git a/Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-icon.png b/Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0d906cf44708bbc4741f43a99fb6b58e48592a23 GIT binary patch literal 413 zcmV;O0b>4%P);>S1%oCN-hKtj5u$FredxX$Y9+ZxE5A1M0HVFValP#^R5w2$DQz(rurREAv> zv^DtQA}|N1tzcx%cNWCp-U1uEYT#AZmPizD3CMnPzD~Yk<|2Dmas__8B_P#I65V{f z>Y&ZmMFj2|DA;Evb5Ue;#;|v13%UqM_6zns&VX#cRRe!b0cP1JTfWdPNvLjwM74uXLR*B%3}9rMre_2$1(;dFklY@{R0is=@FRE`jo|kDkM@3u zKvMacxyYWMzC9wDy9r27OHnaH7NNbA=%f70k>>!x_TqV92{H4A*iGmru&4@L+x@r2 z_X$F`VKsC4E6QeTxNZUYLj+#^V$H##!9xVTg_FQZ;O7MH(|2&_-hFlX00000NkvXX Hu0mjf6oj_j literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-inhand-left.png b/Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..65ca7b4a563949246112cfe574d90f732bac04d9 GIT binary patch literal 879 zcmV-#1CacQP)qxQl`VSKVCUNv(=qMTw`*FEG9f&wh zz@!}2P7lNmCzM8z?F@1s<;9rAerHI;DFV=V?POe^Cj{cgtao!R34=r%wg9%%)u;ds zL>-Z5rz4eN`~6Yuc20TVFaaEWZs?$%q2WPZN5M8V5natmkQ>dBUL?LWI*8;`0|LDx zI3BQ>e(%?K#wh}HqMJjY7}NW$0v?5Sd-{JkM1aox05={{U*D_G0wFnuxujHf&e3O_ zQb3TFvAlJ{Lj<9QTxepqzvB{EO%>oMu7i{K*$o(oHqm%TXLscF) zO#mW2g+u_N%ns|)STYiX-R&gHLj`6EK(ZU88{jzulKuv6+^{K^p3!HVA^@`fr~o_O zmP8+l8tF5SoS9tx1``G7{=FRvBl;zi8ZCRwE2_)^+uE)dGZ>I`N%Z!{3!ZC(re$z1 zL0KfN>K8??jGF*z95(^fIBo){aohw@ylrGCyIFJ+I=J z*;@#GZMDTKBtc7O^*JQobF{95rUdBiZA)j7RMQ4a{uWyfT#}%rv)F{ii8IUG!Z0Pk z+u8NJfw5h;p|GX1*l*;sMoetgWl%fIdBDQi*J%?mp9GrrhSXtHP01sz-We@v~)eeO{oz+KhE#cAyES()UsV=yCY8=fI@E2#VOS2-oksbg5002ovPDHLk FV1g{;eq8_n literal 0 HcmV?d00001 diff --git a/Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-inhand-right.png b/Resources/Textures/Objects/Weapons/Melee/shields.rsi/changeling-inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..c18168848ee6320fb48bec9fbe205df7d61abb50 GIT binary patch literal 882 zcmV-&1C9KNP)(%F|J_D08NX%uIUj!21O&NoRYh|9@w4t&DjH!^on_YaJ z_Oz}bhRS=g{@NZ|{@bT%ut_e1K^W(Fv6VS5a9Yt`(TXpujlo2YM7XX6vL4W=xapIN zQ&C+QMfJQK6j=gVRY?Kbg-Y4fJ4!Ma2zajmSrxWn2@S8%Zr&$j?w$q-D!*2?N#C7w zAk1T*kR?DfrI(ewk80}kMnta^r`Lv!=Qmb^uggjRb3Qx(*XvT#AM`4an8SMKYEXN@ zlhL>k!ve`;Qe4+7%mF!r6+&QMNv#(`BCj6d5jlBz-%S7qn2U#HMMZRu=Y1M4%JyC$ zu`vrQFLwtkbcQd;`=@t>_uwKxPL7KJIXNx@#_OJPTK%3~B*-xj6>=svuqu*?4B_K~;mph^<76t+p5 zoVEIrrUcX$mZ#J#$E{WhTMEmw2t1$QdK%QiA`e&y%cceiwLG0>PJkBHLQp2wr5IYy z|5>2gEC5&v%QoF1)&hiuu#GzQ3c$16=71W)wrV`~C*799HHBqI+WJ67l@PELc363p zb!*;~fTil26Q7l^+KXIQf&_{1c5)<#!)%Jy!bO0b9H%GXKlZUzQ72_6eETrue True True + True True True True @@ -675,6 +676,7 @@ public sealed partial class $CLASS$ : Shared$CLASS$ { True True True + True True True True