From 7684279e45c9ab8f8abc15b6bcbc64844f530114 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 13:11:48 -0400 Subject: [PATCH 01/20] Cherry-Pick Antag Refactor (#734) This cherry-pick's Wizden's Antag Refactor, which is needed for all future antag updates, as well as for me to cherry-pick over Cultists(Who are going to need some editing to fit the antag refactor), Changelings, Heretics, and Wizards. I actually selected the White-Dream-Public version of the Antag Refactor, due to it having commits made tailored to our repository, so it comes prepackaged with all the changes necessary for our repo-specific content. https://github.com/frosty-dev/ss14-wwdp/pull/10 Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> Co-authored-by: ThereDrD <88589686+ThereDrD0@users.noreply.github.com> Co-authored-by: Jeff Co-authored-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> Co-authored-by: Timemaster99 Co-authored-by: luckywill339@gmail.com Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com> Co-authored-by: Azzy --- .../SpawnEquipDeleteBenchmark.cs | 2 +- .../Humanoid/HumanoidAppearanceSystem.cs | 5 +- .../Tests/GameRules/NukeOpsTest.cs | 208 + .../Tests/GameRules/RuleMaxTimeRestartTest.cs | 1 + .../Tests/GameRules/SecretStartsTest.cs | 6 +- Content.Server/Administration/ServerApi.cs | 1 + .../Systems/AdminVerbSystem.Antags.cs | 42 +- .../Antag/AntagSelectionPlayerPool.cs | 27 + .../Antag/AntagSelectionSystem.API.cs | 303 ++ Content.Server/Antag/AntagSelectionSystem.cs | 590 +- .../Components/AntagSelectionComponent.cs | 189 + .../GhostRoleAntagSpawnerComponent.cs | 14 + .../Antag/MobReplacementRuleSystem.cs | 153 +- .../Systems/ParadoxAnomalySystem.cs | 2 +- .../Events/GlimmerMobSpawnRule.cs | 1 + .../Events/PirateRadioSpawnRule.cs | 1 + .../Destructible/Thresholds/MinMax.cs | 15 +- Content.Server/Entry/EntryPoint.cs | 2 +- .../Components/ActiveGameRuleComponent.cs | 2 +- .../Components/DelayedStartRuleComponent.cs | 16 + .../Components/EndedGameRuleComponent.cs | 2 +- .../Components/GameRuleComponent.cs | 9 +- .../GameTicking/GameTicker.GameRule.cs | 65 +- Content.Server/GameTicking/GameTicker.cs | 1 + .../Rules/Components/LoadMapRuleComponent.cs | 29 + .../Rules/Components/NinjaRuleComponent.cs | 2 +- .../NukeOperativeSpawnerComponent.cs | 11 +- .../Components/NukeOpsShuttleComponent.cs | 2 + .../Rules/Components/NukeopsRuleComponent.cs | 60 +- .../Rules/Components/PiratesRuleComponent.cs | 24 - .../Components/RevolutionaryRuleComponent.cs | 37 - .../Rules/Components/ThiefRuleComponent.cs | 36 +- .../Rules/Components/TraitorRuleComponent.cs | 15 + .../Rules/Components/ZombieRuleComponent.cs | 58 - .../GameTicking/Rules/DeathMatchRuleSystem.cs | 24 +- .../Rules/GameRuleSystem.Utility.cs | 28 +- .../GameTicking/Rules/GameRuleSystem.cs | 38 +- .../Rules/InactivityTimeRestartRuleSystem.cs | 1 + .../Rules/KillCalloutRuleSystem.cs | 1 + .../GameTicking/Rules/LoadMapRuleSystem.cs | 80 + .../Rules/MaxTimeRestartRuleSystem.cs | 1 + .../GameTicking/Rules/NukeopsRuleSystem.cs | 757 +-- .../GameTicking/Rules/PiratesRuleSystem.cs | 321 -- .../GameTicking/Rules/RespawnRuleSystem.cs | 1 + .../Rules/RevolutionaryRuleSystem.cs | 135 +- .../RoundstartStationVariationRuleSystem.cs | 1 + .../GameTicking/Rules/SandboxRuleSystem.cs | 1 + .../GameTicking/Rules/SecretRuleSystem.cs | 1 + .../GameTicking/Rules/SubGamemodesSystem.cs | 1 + .../GameTicking/Rules/ThiefRuleSystem.cs | 99 +- .../GameTicking/Rules/TraitorRuleSystem.cs | 212 +- .../GameTicking/Rules/ZombieRuleSystem.cs | 213 +- Content.Server/IoC/ServerContentIoC.cs | 2 +- .../StationEvents/Events/FreeProberRule.cs | 1 + .../Events/GlimmerEventSystem.cs | 1 + .../Events/GlimmerRandomSentienceRule.cs | 1 + .../Events/GlimmerRevenantSpawnRule.cs | 1 + .../Events/GlimmerWispSpawnRule.cs | 1 + .../StationEvents/Events/MassMindSwapRule.cs | 1 + .../StationEvents/Events/MidRoundAntagRule.cs | 1 + .../StationEvents/Events/NoosphericFryRule.cs | 1 + .../Events/NoosphericStormRule.cs | 1 + .../StationEvents/Events/NoosphericZapRule.cs | 1 + .../Events/PsionicCatGotYourTongueRule.cs | 1 + Content.Server/Objectives/ObjectivesSystem.cs | 19 +- .../PowerMonitoringConsoleSystem.cs | 5 +- .../Managers/IServerPreferencesManager.cs | 1 + .../Managers/ServerPreferencesManager.cs | 14 + .../RandomMetadata/RandomMetadataSystem.cs | 11 +- .../EntitySystems/ConditionalSpawnerSystem.cs | 1 + .../Station/Systems/StationSpawningSystem.cs | 2 +- .../BasicStationEventSchedulerSystem.cs | 1 + .../Components/LoneOpsSpawnRuleComponent.cs | 18 - .../StationEvents/Events/AnomalySpawnRule.cs | 1 + .../Events/BluespaceArtifactRule.cs | 3 +- .../Events/BluespaceLockerRule.cs | 3 +- .../StationEvents/Events/BreakerFlipRule.cs | 3 +- .../Events/BureaucraticErrorRule.cs | 1 + .../StationEvents/Events/CargoGiftsRule.cs | 1 + .../StationEvents/Events/ClericalErrorRule.cs | 3 +- .../StationEvents/Events/FalseAlarmRule.cs | 1 + .../StationEvents/Events/GasLeakRule.cs | 1 + .../StationEvents/Events/ImmovableRodRule.cs | 1 + .../StationEvents/Events/IonStormRule.cs | 2 +- .../StationEvents/Events/KudzuGrowthRule.cs | 1 + .../StationEvents/Events/LoneOpsSpawnRule.cs | 47 - .../Events/MassHallucinationsRule.cs | 1 + .../StationEvents/Events/MeteorSwarmRule.cs | 1 + .../StationEvents/Events/NinjaSpawnRule.cs | 1 + .../Events/PowerGridCheckRule.cs | 1 + .../Events/RandomEntityStorageSpawnRule.cs | 1 + .../Events/RandomSentienceRule.cs | 1 + .../StationEvents/Events/RandomSpawnRule.cs | 1 + .../StationEvents/Events/SolarFlareRule.cs | 1 + .../Events/StationEventSystem.cs | 1 + .../StationEvents/Events/VentClogRule.cs | 1 + .../StationEvents/Events/VentCrittersRule.cs | 1 + .../RampingStationEventSchedulerSystem.cs | 1 + .../Components/AutoTraitorComponent.cs | 4 +- .../Traitor/Systems/AutoTraitorSystem.cs | 48 +- .../Uplink/Commands/AddUplinkCommand.cs | 5 +- .../Zombies/PendingZombieComponent.cs | 16 + Content.Server/Zombies/ZombieSystem.cs | 4 + Content.Shared/Antag/AntagAcceptability.cs | 5 + Content.Shared/CCVar/CCVars.cs | 85 - .../Loadouts/Systems/LoadoutSystem.cs | 2 +- .../SharedHumanoidAppearanceSystem.cs | 5 +- .../Inventory/InventorySystem.Helpers.cs | 4 +- .../NukeOps/NukeOperativeComponent.cs | 7 +- Content.Shared/Roles/SharedRoleSystem.cs | 64 + .../Station/SharedStationSpawningSystem.cs | 22 +- .../game-presets/preset-pirates.ftl | 10 - Resources/Maps/Shuttles/striker.yml | 4778 ++++++++--------- .../Entities/Markers/Spawners/ghost_roles.yml | 3 +- Resources/Prototypes/GameRules/events.yml | 51 +- Resources/Prototypes/GameRules/midround.yml | 17 + Resources/Prototypes/GameRules/roundstart.yml | 129 +- Resources/Prototypes/Roles/Antags/nukeops.yml | 30 +- .../Roles/Jobs/Fun/misc_startinggear.yml | 14 +- Resources/Prototypes/game_presets.yml | 12 - 120 files changed, 4542 insertions(+), 4784 deletions(-) create mode 100644 Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs create mode 100644 Content.Server/Antag/AntagSelectionPlayerPool.cs create mode 100644 Content.Server/Antag/AntagSelectionSystem.API.cs create mode 100644 Content.Server/Antag/Components/AntagSelectionComponent.cs create mode 100644 Content.Server/Antag/Components/GhostRoleAntagSpawnerComponent.cs rename Content.Server/GameTicking/{Rules => }/Components/ActiveGameRuleComponent.cs (84%) create mode 100644 Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs rename Content.Server/GameTicking/{Rules => }/Components/EndedGameRuleComponent.cs (81%) rename Content.Server/GameTicking/{Rules => }/Components/GameRuleComponent.cs (83%) create mode 100644 Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs delete mode 100644 Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs create mode 100644 Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs delete mode 100644 Content.Server/StationEvents/Components/LoneOpsSpawnRuleComponent.cs delete mode 100644 Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs delete mode 100644 Resources/Locale/en-US/game-ticking/game-presets/preset-pirates.ftl diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs index de51b2fb19..8512107b69 100644 --- a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs +++ b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs @@ -58,7 +58,7 @@ await _pair.Server.WaitPost(() => for (var i = 0; i < N; i++) { _entity = server.EntMan.SpawnAttachedTo(Mob, _coords); - _spawnSys.EquipStartingGear(_entity, _gear, null); + _spawnSys.EquipStartingGear(_entity, _gear); server.EntMan.DeleteEntity(_entity); } }); diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index 8087d1833e..867dcbc269 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -118,8 +118,11 @@ private void SetLayerData( /// This should not be used if the entity is owned by the server. The server will otherwise /// override this with the appearance data it sends over. /// - public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null) + public override void LoadProfile(EntityUid uid, HumanoidCharacterProfile? profile, HumanoidAppearanceComponent? humanoid = null) { + if (profile == null) + return; + if (!Resolve(uid, ref humanoid)) { return; diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs new file mode 100644 index 0000000000..f539daee36 --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs @@ -0,0 +1,208 @@ +/* WD edit + +#nullable enable +using System.Linq; +using Content.Server.Body.Components; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Presets; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Mind; +using Content.Server.NPC.Systems; +using Content.Server.Pinpointer; +using Content.Server.Roles; +using Content.Server.Shuttles.Components; +using Content.Server.Station.Components; +using Content.Shared.CCVar; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.GameTicking; +using Content.Shared.Hands.Components; +using Content.Shared.Inventory; +using Content.Shared.NukeOps; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map.Components; + +namespace Content.IntegrationTests.Tests.GameRules; + +[TestFixture] +public sealed class NukeOpsTest +{ + /// + /// Check that a nuke ops game mode can start without issue. I.e., that the nuke station and such all get loaded. + /// + [Test] + public async Task TryStopNukeOpsFromConstantlyFailing() + { + await using var pair = await PoolManager.GetServerClient(new PoolSettings + { + Dirty = true, + DummyTicker = false, + Connected = true, + InLobby = true + }); + + var server = pair.Server; + var client = pair.Client; + var entMan = server.EntMan; + var mapSys = server.System(); + var ticker = server.System(); + var mindSys = server.System(); + var roleSys = server.System(); + var invSys = server.System(); + var factionSys = server.System(); + + Assert.That(server.CfgMan.GetCVar(CCVars.GridFill), Is.False); + server.CfgMan.SetCVar(CCVars.GridFill, true); + + // Initially in the lobby + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.PreRoundLobby)); + Assert.That(client.AttachedEntity, Is.Null); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.NotReadyToPlay)); + + // There are no grids or maps + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + + // And no nukie related components + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + + // Ready up and start nukeops + await pair.WaitClientCommand("toggleready True"); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.ReadyToPlay)); + await pair.WaitCommand("forcepreset Nukeops"); + await pair.RunTicksSync(10); + + // Game should have started + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + Assert.That(ticker.PlayerGameStatuses[client.User!.Value], Is.EqualTo(PlayerGameStatus.JoinedGame)); + Assert.That(client.EntMan.EntityExists(client.AttachedEntity)); + var player = pair.Player!.AttachedEntity!.Value; + Assert.That(entMan.EntityExists(player)); + + // Maps now exist + Assert.That(entMan.Count(), Is.GreaterThan(0)); + Assert.That(entMan.Count(), Is.GreaterThan(0)); + Assert.That(entMan.Count(), Is.EqualTo(2)); // The main station & nukie station + Assert.That(entMan.Count(), Is.GreaterThan(3)); // Each station has at least 1 grid, plus some shuttles + Assert.That(entMan.Count(), Is.EqualTo(1)); + + // And we now have nukie related components + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count(), Is.EqualTo(1)); + + // The player entity should be the nukie commander + var mind = mindSys.GetMind(player)!.Value; + Assert.That(entMan.HasComponent(player)); + Assert.That(roleSys.MindIsAntagonist(mind)); + Assert.That(roleSys.MindHasRole(mind)); + + Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True); + Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False); + + var roles = roleSys.MindGetAllRoles(mind); + var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent); + Assert.That(cmdRoles.Count(), Is.EqualTo(1)); + + // The game rule exists, and all the stations/shuttles/maps are properly initialized + var rule = entMan.AllComponents().Single().Component; + var mapRule = entMan.AllComponents().Single().Component; + foreach (var grid in mapRule.MapGrids) + { + Assert.That(entMan.EntityExists(grid)); + Assert.That(entMan.HasComponent(grid)); + Assert.That(entMan.HasComponent(grid)); + } + Assert.That(entMan.EntityExists(rule.TargetStation)); + + Assert.That(entMan.HasComponent(rule.TargetStation)); + + var nukieShuttlEnt = entMan.AllComponents().FirstOrDefault().Uid; + Assert.That(entMan.EntityExists(nukieShuttlEnt)); + + EntityUid? nukieStationEnt = null; + foreach (var grid in mapRule.MapGrids) + { + if (entMan.HasComponent(grid)) + { + nukieStationEnt = grid; + break; + } + } + + Assert.That(entMan.EntityExists(nukieStationEnt)); + var nukieStation = entMan.GetComponent(nukieStationEnt!.Value); + + Assert.That(entMan.EntityExists(nukieStation.Station)); + Assert.That(nukieStation.Station, Is.Not.EqualTo(rule.TargetStation)); + + Assert.That(server.MapMan.MapExists(mapRule.Map)); + var nukieMap = mapSys.GetMap(mapRule.Map!.Value); + + var targetStation = entMan.GetComponent(rule.TargetStation!.Value); + var targetGrid = targetStation.Grids.First(); + var targetMap = entMan.GetComponent(targetGrid).MapUid!.Value; + Assert.That(targetMap, Is.Not.EqualTo(nukieMap)); + + Assert.That(entMan.GetComponent(player).MapUid, Is.EqualTo(nukieMap)); + Assert.That(entMan.GetComponent(nukieStationEnt.Value).MapUid, Is.EqualTo(nukieMap)); + Assert.That(entMan.GetComponent(nukieShuttlEnt).MapUid, Is.EqualTo(nukieMap)); + + // The maps are all map-initialized, including the player + // Yes, this is necessary as this has repeatedly been broken somehow. + Assert.That(mapSys.IsInitialized(nukieMap)); + Assert.That(mapSys.IsInitialized(targetMap)); + Assert.That(mapSys.IsPaused(nukieMap), Is.False); + Assert.That(mapSys.IsPaused(targetMap), Is.False); + + EntityLifeStage LifeStage(EntityUid? uid) => entMan.GetComponent(uid!.Value).EntityLifeStage; + Assert.That(LifeStage(player), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(nukieMap), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(targetMap), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(nukieStationEnt.Value), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(nukieShuttlEnt), Is.GreaterThan(EntityLifeStage.Initialized)); + Assert.That(LifeStage(rule.TargetStation), Is.GreaterThan(EntityLifeStage.Initialized)); + + // Make sure the player has hands. We've had fucking disarmed nukies before. + Assert.That(entMan.HasComponent(player)); + Assert.That(entMan.GetComponent(player).Hands.Count, Is.GreaterThan(0)); + + // While we're at it, lets make sure they aren't naked. I don't know how many inventory slots all mobs will be + // likely to have in the future. But nukies should probably have at least 3 slots with something in them. + var enumerator = invSys.GetSlotEnumerator(player); + int total = 0; + while (enumerator.NextItem(out _)) + { + total++; + } + Assert.That(total, Is.GreaterThan(3)); + + // Finally lets check the nukie commander passed basic training and figured out how to breathe. + var totalSeconds = 30; + var totalTicks = (int) Math.Ceiling(totalSeconds / server.Timing.TickPeriod.TotalSeconds); + int increment = 5; + var resp = entMan.GetComponent(player); + var damage = entMan.GetComponent(player); + for (var tick = 0; tick < totalTicks; tick += increment) + { + await pair.RunTicksSync(increment); + Assert.That(resp.SuffocationCycles, Is.LessThanOrEqualTo(resp.SuffocationCycleThreshold)); + Assert.That(damage.TotalDamage, Is.EqualTo(FixedPoint2.Zero)); + } + + //ticker.SetGamePreset((GamePresetPrototype?)null); WD edit + server.CfgMan.SetCVar(CCVars.GridFill, false); + await pair.CleanReturnAsync(); + } +} + +*/ diff --git a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs index 1e3f9c9854..ffaff3b8de 100644 --- a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs @@ -1,5 +1,6 @@ using Content.Server.GameTicking; using Content.Server.GameTicking.Commands; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Shared.CCVar; diff --git a/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs b/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs index 0f665a63de..5d7ae8efbf 100644 --- a/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs @@ -17,6 +17,7 @@ public async Task TestSecretStarts() var server = pair.Server; await server.WaitIdleAsync(); + var entMan = server.ResolveDependency(); var gameTicker = server.ResolveDependency().GetEntitySystem(); await server.WaitAssertion(() => @@ -32,10 +33,7 @@ await server.WaitAssertion(() => await server.WaitAssertion(() => { - foreach (var rule in gameTicker.GetAddedGameRules()) - { - Assert.That(gameTicker.GetActiveGameRules(), Does.Contain(rule)); - } + Assert.That(gameTicker.GetAddedGameRules().Count(), Is.GreaterThan(1), $"No additional rules started by secret rule."); // End all rules gameTicker.ClearGameRules(); diff --git a/Content.Server/Administration/ServerApi.cs b/Content.Server/Administration/ServerApi.cs index 6f10ef9b47..04fd38598f 100644 --- a/Content.Server/Administration/ServerApi.cs +++ b/Content.Server/Administration/ServerApi.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Content.Server.Administration.Systems; using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; using Content.Server.Maps; diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 9849d2df79..df77a3a1a7 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -1,23 +1,37 @@ -using Content.Server.GameTicking.Rules; +using Content.Server.Administration.Commands; +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; using Content.Server.Zombies; using Content.Shared.Administration; using Content.Shared.Database; -using Content.Shared.Humanoid; using Content.Shared.Mind.Components; +using Content.Shared.Roles; using Content.Shared.Verbs; using Robust.Shared.Player; +using Robust.Shared.Prototypes; using Robust.Shared.Utility; namespace Content.Server.Administration.Systems; public sealed partial class AdminVerbSystem { + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly ZombieSystem _zombie = default!; - [Dependency] private readonly ThiefRuleSystem _thief = default!; - [Dependency] private readonly TraitorRuleSystem _traitorRule = default!; - [Dependency] private readonly NukeopsRuleSystem _nukeopsRule = default!; - [Dependency] private readonly PiratesRuleSystem _piratesRule = default!; - [Dependency] private readonly RevolutionaryRuleSystem _revolutionaryRule = default!; + + [ValidatePrototypeId] + private const string DefaultTraitorRule = "Traitor"; + + [ValidatePrototypeId] + private const string DefaultNukeOpRule = "LoneOpsSpawn"; + + [ValidatePrototypeId] + private const string DefaultRevsRule = "Revolutionary"; + + [ValidatePrototypeId] + private const string DefaultThiefRule = "Thief"; + + [ValidatePrototypeId] + private const string PirateGearId = "PirateGear"; // All antag verbs have names so invokeverb works. private void AddAntagVerbs(GetVerbsEvent args) @@ -40,9 +54,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster5_contraband"), Act = () => { - // if its a monkey or mouse or something dont give uplink or objectives - var isHuman = HasComp(args.Target); - _traitorRule.MakeTraitorAdmin(args.Target, giveUplink: isHuman, giveObjectives: isHuman); + _antag.ForceMakeAntag(player, DefaultTraitorRule); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-traitor"), @@ -71,7 +83,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Wallmounts/signs.rsi"), "radiation"), Act = () => { - _nukeopsRule.MakeLoneNukie(args.Target); + _antag.ForceMakeAntag(player, DefaultNukeOpRule); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-nuclear-operative"), @@ -85,14 +97,14 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Clothing/Head/Hats/pirate.rsi"), "icon"), Act = () => { - _piratesRule.MakePirate(args.Target); + // pirates just get an outfit because they don't really have logic associated with them + SetOutfitCommand.SetOutfit(args.Target, PirateGearId, EntityManager); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-pirate"), }; args.Verbs.Add(pirate); - //todo come here at some point dear lort. Verb headRev = new() { Text = Loc.GetString("admin-verb-text-make-head-rev"), @@ -100,7 +112,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"), Act = () => { - _revolutionaryRule.OnHeadRevAdmin(args.Target); + _antag.ForceMakeAntag(player, DefaultRevsRule); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-head-rev"), @@ -114,7 +126,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/ihscombat.rsi"), "icon"), Act = () => { - _thief.AdminMakeThief(args.Target, false); //Midround add pacified is bad + _antag.ForceMakeAntag(player, DefaultThiefRule); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-thief"), diff --git a/Content.Server/Antag/AntagSelectionPlayerPool.cs b/Content.Server/Antag/AntagSelectionPlayerPool.cs new file mode 100644 index 0000000000..87873e96d1 --- /dev/null +++ b/Content.Server/Antag/AntagSelectionPlayerPool.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Robust.Shared.Player; +using Robust.Shared.Random; + +namespace Content.Server.Antag; + +public sealed class AntagSelectionPlayerPool (List> orderedPools) +{ + public bool TryPickAndTake(IRobustRandom random, [NotNullWhen(true)] out ICommonSession? session) + { + session = null; + + foreach (var pool in orderedPools) + { + if (pool.Count == 0) + continue; + + session = random.PickAndTake(pool); + break; + } + + return session != null; + } + + public int Count => orderedPools.Sum(p => p.Count); +} diff --git a/Content.Server/Antag/AntagSelectionSystem.API.cs b/Content.Server/Antag/AntagSelectionSystem.API.cs new file mode 100644 index 0000000000..470f98fca1 --- /dev/null +++ b/Content.Server/Antag/AntagSelectionSystem.API.cs @@ -0,0 +1,303 @@ +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using Content.Server.Antag.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Objectives; +using Content.Shared.Chat; +using Content.Shared.Mind; +using JetBrains.Annotations; +using Robust.Shared.Audio; +using Robust.Shared.Player; + +namespace Content.Server.Antag; + +public sealed partial class AntagSelectionSystem +{ + /// + /// Tries to get the next non-filled definition based on the current amount of selected minds and other factors. + /// + public bool TryGetNextAvailableDefinition(Entity ent, + [NotNullWhen(true)] out AntagSelectionDefinition? definition) + { + definition = null; + + var totalTargetCount = GetTargetAntagCount(ent); + var mindCount = ent.Comp.SelectedMinds.Count; + if (mindCount >= totalTargetCount) + return false; + + foreach (var def in ent.Comp.Definitions) + { + var target = GetTargetAntagCount(ent, null, def); + + if (mindCount < target) + { + definition = def; + return true; + } + + mindCount -= target; + } + + return false; + } + + /// + /// Gets the number of antagonists that should be present for a given rule based on the provided pool. + /// A null pool will simply use the player count. + /// + public int GetTargetAntagCount(Entity ent, AntagSelectionPlayerPool? pool = null) + { + var count = 0; + foreach (var def in ent.Comp.Definitions) + { + count += GetTargetAntagCount(ent, pool, def); + } + + return count; + } + + /// + /// Gets the number of antagonists that should be present for a given antag definition based on the provided pool. + /// A null pool will simply use the player count. + /// + public int GetTargetAntagCount(Entity ent, AntagSelectionPlayerPool? pool, AntagSelectionDefinition def) + { + var poolSize = pool?.Count ?? _playerManager.Sessions.Length; + // factor in other definitions' affect on the count. + var countOffset = 0; + foreach (var otherDef in ent.Comp.Definitions) + { + countOffset += Math.Clamp(poolSize / otherDef.PlayerRatio, otherDef.Min, otherDef.Max) * otherDef.PlayerRatio; + } + // make sure we don't double-count the current selection + countOffset -= Math.Clamp((poolSize + countOffset) / def.PlayerRatio, def.Min, def.Max) * def.PlayerRatio; + + return Math.Clamp((poolSize - countOffset) / def.PlayerRatio, def.Min, def.Max); + } + + /// + /// Returns identifiable information for all antagonists to be used in a round end summary. + /// + /// + /// A list containing, in order, the antag's mind, the session data, and the original name stored as a string. + /// + public List<(EntityUid, SessionData, string)> GetAntagIdentifiers(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return new List<(EntityUid, SessionData, string)>(); + + var output = new List<(EntityUid, SessionData, string)>(); + foreach (var (mind, name) in ent.Comp.SelectedMinds) + { + if (!TryComp(mind, out var mindComp) || mindComp.OriginalOwnerUserId == null) + continue; + + if (!_playerManager.TryGetPlayerData(mindComp.OriginalOwnerUserId.Value, out var data)) + continue; + + output.Add((mind, data, name)); + } + return output; + } + + /// + /// Returns all the minds of antagonists. + /// + public List> GetAntagMinds(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return new(); + + var output = new List>(); + foreach (var (mind, _) in ent.Comp.SelectedMinds) + { + if (!TryComp(mind, out var mindComp) || mindComp.OriginalOwnerUserId == null) + continue; + + output.Add((mind, mindComp)); + } + return output; + } + + /// + /// Helper specifically for + /// + public List GetAntagMindEntityUids(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return new(); + + return ent.Comp.SelectedMinds.Select(p => p.Item1).ToList(); + } + + /// + /// Returns all the antagonists for this rule who are currently alive + /// + public IEnumerable GetAliveAntags(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + yield break; + + var minds = GetAntagMinds(ent); + foreach (var mind in minds) + { + if (_mind.IsCharacterDeadIc(mind)) + continue; + + if (mind.Comp.OriginalOwnedEntity != null) + yield return GetEntity(mind.Comp.OriginalOwnedEntity.Value); + } + } + + /// + /// Returns the number of alive antagonists for this rule. + /// + public int GetAliveAntagCount(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return 0; + + var numbah = 0; + var minds = GetAntagMinds(ent); + foreach (var mind in minds) + { + if (_mind.IsCharacterDeadIc(mind)) + continue; + + numbah++; + } + + return numbah; + } + + /// + /// Returns if there are any remaining antagonists alive for this rule. + /// + public bool AnyAliveAntags(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + return GetAliveAntags(ent).Any(); + } + + /// + /// Checks if all the antagonists for this rule are alive. + /// + public bool AllAntagsAlive(Entity ent) + { + if (!Resolve(ent, ref ent.Comp, false)) + return false; + + return GetAliveAntagCount(ent) == ent.Comp.SelectedMinds.Count; + } + + /// + /// Helper method to send the briefing text and sound to a player entity + /// + /// The entity chosen to be antag + /// The briefing text to send + /// The color the briefing should be, null for default + /// The sound to briefing/greeting sound to play + public void SendBriefing(EntityUid entity, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + { + if (!_mind.TryGetMind(entity, out _, out var mindComponent)) + return; + + if (mindComponent.Session == null) + return; + + SendBriefing(mindComponent.Session, briefing, briefingColor, briefingSound); + } + + /// + /// Helper method to send the briefing text and sound to a list of sessions + /// + /// The sessions that will be sent the briefing + /// The briefing text to send + /// The color the briefing should be, null for default + /// The sound to briefing/greeting sound to play + [PublicAPI] + public void SendBriefing(List sessions, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + { + foreach (var session in sessions) + { + SendBriefing(session, briefing, briefingColor, briefingSound); + } + } + + /// + /// Helper method to send the briefing text and sound to a session + /// + /// The player chosen to be an antag + /// The briefing data + public void SendBriefing( + ICommonSession? session, + BriefingData? data) + { + if (session == null || data == null) + return; + + var text = data.Value.Text == null ? string.Empty : Loc.GetString(data.Value.Text); + SendBriefing(session, text, data.Value.Color, data.Value.Sound); + } + + /// + /// Helper method to send the briefing text and sound to a session + /// + /// The player chosen to be an antag + /// The briefing text to send + /// The color the briefing should be, null for default + /// The sound to briefing/greeting sound to play + public void SendBriefing( + ICommonSession? session, + string briefing, + Color? briefingColor, + SoundSpecifier? briefingSound) + { + if (session == null) + return; + + _audio.PlayGlobal(briefingSound, session); + if (!string.IsNullOrEmpty(briefing)) + { + var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", briefing)); + _chat.ChatMessageToOne(ChatChannel.Server, briefing, wrappedMessage, default, false, session.Channel, + briefingColor); + } + } + + /// + /// This technically is a gamerule-ent-less way to make an entity an antag. + /// You should almost never be using this. + /// + public void ForceMakeAntag(ICommonSession? player, string defaultRule) where T : Component + { + var rule = ForceGetGameRuleEnt(defaultRule); + + if (!TryGetNextAvailableDefinition(rule, out var def)) + def = rule.Comp.Definitions.Last(); + + MakeAntag(rule, player, def.Value); + } + + /// + /// Tries to grab one of the weird specific antag gamerule ents or starts a new one. + /// This is gross code but also most of this is pretty gross to begin with. + /// + public Entity ForceGetGameRuleEnt(string id) where T : Component + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var comp)) + { + return (uid, comp); + } + var ruleEnt = GameTicker.AddGameRule(id); + RemComp(ruleEnt); + var antag = Comp(ruleEnt); + antag.SelectionsComplete = true; // don't do normal selection. + GameTicker.StartGameRule(ruleEnt); + return (ruleEnt, antag); + } +} diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index b11c562df5..6bfb7394f5 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -1,347 +1,461 @@ +using System.Linq; +using Content.Server.Antag.Components; +using Content.Server.Chat.Managers; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; -using Content.Server.GameTicking.Rules.Components; +using Content.Server.Ghost.Roles; +using Content.Server.Ghost.Roles.Components; using Content.Server.Mind; using Content.Server.Preferences.Managers; +using Content.Server.Roles; using Content.Server.Roles.Jobs; using Content.Server.Shuttles.Components; +using Content.Server.Station.Systems; using Content.Shared.Antag; +using Content.Shared.Ghost; using Content.Shared.Humanoid; using Content.Shared.Players; using Content.Shared.Preferences; -using Content.Shared.Roles; using Robust.Server.Audio; -using Robust.Shared.Audio; +using Robust.Server.GameObjects; +using Robust.Server.Player; +using Robust.Shared.Enums; +using Robust.Shared.Map; using Robust.Shared.Player; -using Robust.Shared.Prototypes; using Robust.Shared.Random; -using System.Linq; -using Content.Shared.Chat; -using Robust.Shared.Enums; namespace Content.Server.Antag; -public sealed class AntagSelectionSystem : GameRuleSystem +public sealed partial class AntagSelectionSystem : GameRuleSystem { - [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IServerPreferencesManager _pref = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly GhostRoleSystem _ghostRole = default!; [Dependency] private readonly JobSystem _jobs = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly RoleSystem _role = default!; + [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; + [Dependency] private readonly TransformSystem _transform = default!; - #region Eligible Player Selection - /// - /// Get all players that are eligible for an antag role - /// - /// All sessions from which to select eligible players - /// The prototype to get eligible players for - /// Should jobs that prohibit antag roles (ie Heads, Sec, Interns) be included - /// Should players already selected as antags be eligible - /// Should we ignore if the player has enabled this specific role - /// A custom condition that each player is tested against, if it returns true the player is excluded from eligibility - /// List of all player entities that match the requirements - public List GetEligiblePlayers(IEnumerable playerSessions, - ProtoId antagPrototype, - bool includeAllJobs = false, - AntagAcceptability acceptableAntags = AntagAcceptability.NotExclusive, - bool ignorePreferences = false, - bool allowNonHumanoids = false, - Func? customExcludeCondition = null) + // arbitrary random number to give late joining some mild interest. + public const float LateJoinRandomChance = 0.5f; + + /// + public override void Initialize() { - var eligiblePlayers = new List(); + base.Initialize(); - foreach (var player in playerSessions) - { - if (IsPlayerEligible(player, antagPrototype, includeAllJobs, acceptableAntags, ignorePreferences, allowNonHumanoids, customExcludeCondition)) - eligiblePlayers.Add(player.AttachedEntity!.Value); - } + SubscribeLocalEvent(OnTakeGhostRole); - return eligiblePlayers; + SubscribeLocalEvent(OnPlayerSpawning); + SubscribeLocalEvent(OnJobsAssigned); + SubscribeLocalEvent(OnSpawnComplete); } - /// - /// Get all sessions that are eligible for an antag role, can be run prior to sessions being attached to an entity - /// This does not exclude sessions that have already been chosen as antags - that must be handled manually - /// - /// All sessions from which to select eligible players - /// The prototype to get eligible players for - /// Should we ignore if the player has enabled this specific role - /// List of all player sessions that match the requirements - public List GetEligibleSessions(IEnumerable playerSessions, ProtoId antagPrototype, bool ignorePreferences = false) + private void OnTakeGhostRole(Entity ent, ref TakeGhostRoleEvent args) { - var eligibleSessions = new List(); + if (args.TookRole) + return; - foreach (var session in playerSessions) + if (ent.Comp.Rule is not { } rule || ent.Comp.Definition is not { } def) + return; + + if (!Exists(rule) || !TryComp(rule, out var select)) + return; + + MakeAntag((rule, select), args.Player, def, ignoreSpawner: true); + args.TookRole = true; + _ghostRole.UnregisterGhostRole((ent, Comp(ent))); + } + + private void OnPlayerSpawning(RulePlayerSpawningEvent args) + { + var pool = args.PlayerPool; + + var query = QueryActiveRules(); + while (query.MoveNext(out var uid, out _, out var comp, out _)) { - if (IsSessionEligible(session, antagPrototype, ignorePreferences)) - eligibleSessions.Add(session); + if (comp.SelectionTime != AntagSelectionTime.PrePlayerSpawn) + continue; + + if (comp.SelectionsComplete) + return; + + ChooseAntags((uid, comp), pool); + comp.SelectionsComplete = true; + + foreach (var session in comp.SelectedSessions) + { + args.PlayerPool.Remove(session); + GameTicker.PlayerJoinGame(session); + } } + } + + private void OnJobsAssigned(RulePlayerJobsAssignedEvent args) + { + var query = QueryActiveRules(); + while (query.MoveNext(out var uid, out _, out var comp, out _)) + { + if (comp.SelectionTime != AntagSelectionTime.PostPlayerSpawn) + continue; + + if (comp.SelectionsComplete) + continue; - return eligibleSessions; + ChooseAntags((uid, comp)); + comp.SelectionsComplete = true; + } } - /// - /// Test eligibility of the player for a specific antag role - /// - /// The player session to test - /// The prototype to get eligible players for - /// Should jobs that prohibit antag roles (ie Heads, Sec, Interns) be included - /// Should players already selected as antags be eligible - /// Should we ignore if the player has enabled this specific role - /// A function, accepting an EntityUid and returning bool. Each player is tested against this, returning truw will exclude the player from eligibility - /// True if the player session matches the requirements, false otherwise - public bool IsPlayerEligible(ICommonSession session, - ProtoId antagPrototype, - bool includeAllJobs = false, - AntagAcceptability acceptableAntags = AntagAcceptability.NotExclusive, - bool ignorePreferences = false, - bool allowNonHumanoids = false, - Func? customExcludeCondition = null) + private void OnSpawnComplete(PlayerSpawnCompleteEvent args) { - if (!IsSessionEligible(session, antagPrototype, ignorePreferences)) - return false; + if (!args.LateJoin) + return; - //Ensure the player has a mind - if (session.GetMind() is not { } playerMind) - return false; + // TODO: this really doesn't handle multiple latejoin definitions well + // eventually this should probably store the players per definition with some kind of unique identifier. + // something to figure out later. - //Ensure the player has an attached entity - if (session.AttachedEntity is not { } playerEntity) - return false; + var query = QueryActiveRules(); + while (query.MoveNext(out var uid, out _, out var antag, out _)) + { + if (!RobustRandom.Prob(LateJoinRandomChance)) + continue; - //Ignore latejoined players, ie those on the arrivals station - if (HasComp(playerEntity)) - return false; + if (!antag.Definitions.Any(p => p.LateJoinAdditional)) + continue; - //Exclude jobs that cannot be antag, unless explicitly allowed - if (!includeAllJobs && !_jobs.CanBeAntag(session)) - return false; + if (!TryGetNextAvailableDefinition((uid, antag), out var def)) + continue; - //Check if the entity is already an antag - switch (acceptableAntags) + if (TryMakeAntag((uid, antag), args.Player, def.Value)) + break; + } + } + + protected override void Added(EntityUid uid, AntagSelectionComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) + { + base.Added(uid, component, gameRule, args); + + for (var i = 0; i < component.Definitions.Count; i++) { - //If we dont want to select any antag roles - case AntagAcceptability.None: - { - if (_roleSystem.MindIsAntagonist(playerMind)) - return false; - break; - } - //If we dont want to select exclusive antag roles - case AntagAcceptability.NotExclusive: - { - if (_roleSystem.MindIsExclusiveAntagonist(playerMind)) - return false; - break; - } + var def = component.Definitions[i]; + + if (def.MinRange != null) + { + def.Min = def.MinRange.Value.Next(RobustRandom); + } + + if (def.MaxRange != null) + { + def.Max = def.MaxRange.Value.Next(RobustRandom); + } } + } - //Unless explictly allowed, ignore non humanoids (eg pets) - if (!allowNonHumanoids && !HasComp(playerEntity)) - return false; + protected override void Started(EntityUid uid, AntagSelectionComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); - //If a custom condition was provided, test it and exclude the player if it returns true - if (customExcludeCondition != null && customExcludeCondition(playerEntity)) - return false; + if (component.SelectionsComplete) + return; + if (GameTicker.RunLevel != GameRunLevel.InRound) + return; - return true; + if (GameTicker.RunLevel == GameRunLevel.InRound && component.SelectionTime == AntagSelectionTime.PrePlayerSpawn) + return; + + ChooseAntags((uid, component)); + component.SelectionsComplete = true; } /// - /// Check if the session is eligible for a role, can be run prior to the session being attached to an entity + /// Chooses antagonists from the current selection of players /// - /// Player session to check - /// Which antag prototype to check for - /// Ignore if the player has enabled this antag - /// True if the session matches the requirements, false otherwise - public bool IsSessionEligible(ICommonSession session, ProtoId antagPrototype, bool ignorePreferences = false) + public void ChooseAntags(Entity ent) { - //Exclude disconnected or zombie sessions - //No point giving antag roles to them - if (session.Status == SessionStatus.Disconnected || - session.Status == SessionStatus.Zombie) - return false; - - //Check the player has this antag preference selected - //Unless we are ignoring preferences, in which case add them anyway - var pref = (HumanoidCharacterProfile) _prefs.GetPreferences(session.UserId).SelectedCharacter; - if (!pref.AntagPreferences.Contains(antagPrototype.Id) && !ignorePreferences) - return false; - - return true; + var sessions = _playerManager.Sessions.ToList(); + ChooseAntags(ent, sessions); } - #endregion /// - /// Helper method to calculate the number of antags to select based upon the number of players + /// Chooses antagonists from the given selection of players /// - /// How many players there are on the server - /// How many players should there be for an additional antag - /// Maximum number of antags allowed - /// The number of antags that should be chosen - public int CalculateAntagCount(int playerCount, int playersPerAntag, int maxAntags) + public void ChooseAntags(Entity ent, List pool) { - return Math.Clamp(playerCount / playersPerAntag, 1, maxAntags); + foreach (var def in ent.Comp.Definitions) + { + ChooseAntags(ent, pool, def); + } } - #region Antag Selection /// - /// Selects a set number of entities from several lists, prioritising the first list till its empty, then second list etc + /// Chooses antagonists from the given selection of players for the given antag definition. /// - /// Array of lists, which are chosen from in order until the correct number of items are selected - /// How many items to select - /// Up to the specified count of elements from all provided lists - public List ChooseAntags(int count, params List[] eligiblePlayerLists) + public void ChooseAntags(Entity ent, List pool, AntagSelectionDefinition def) { - var chosenPlayers = new List(); - foreach (var playerList in eligiblePlayerLists) + var playerPool = GetPlayerPool(ent, pool, def); + var count = GetTargetAntagCount(ent, playerPool, def); + + for (var i = 0; i < count; i++) { - //Remove all chosen players from this list, to prevent duplicates - foreach (var chosenPlayer in chosenPlayers) + var session = (ICommonSession?) null; + if (def.PickPlayer) { - playerList.Remove(chosenPlayer); - } + if (!playerPool.TryPickAndTake(RobustRandom, out session)) + break; - //If we have reached the desired number of players, skip - if (chosenPlayers.Count >= count) - continue; + if (ent.Comp.SelectedSessions.Contains(session)) + continue; + } - //Pick and choose a random number of players from this list - chosenPlayers.AddRange(ChooseAntags(count - chosenPlayers.Count, playerList)); + MakeAntag(ent, session, def); } - return chosenPlayers; } + /// - /// Helper method to choose antags from a list + /// Tries to makes a given player into the specified antagonist. /// - /// List of eligible players - /// How many to choose - /// Up to the specified count of elements from the provided list - public List ChooseAntags(int count, List eligiblePlayers) + public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false) { - var chosenPlayers = new List(); - - for (var i = 0; i < count; i++) + if (!IsSessionValid(ent, session, def) || + !IsEntityValid(session?.AttachedEntity, def)) { - if (eligiblePlayers.Count == 0) - break; - - chosenPlayers.Add(RobustRandom.PickAndTake(eligiblePlayers)); + return false; } - return chosenPlayers; + MakeAntag(ent, session, def, ignoreSpawner); + return true; } /// - /// Selects a set number of sessions from several lists, prioritising the first list till its empty, then second list etc + /// Makes a given player into the specified antagonist. /// - /// Array of lists, which are chosen from in order until the correct number of items are selected - /// How many items to select - /// Up to the specified count of elements from all provided lists - public List ChooseAntags(int count, params List[] eligiblePlayerLists) + public void MakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false) { - var chosenPlayers = new List(); - foreach (var playerList in eligiblePlayerLists) + var antagEnt = (EntityUid?) null; + var isSpawner = false; + + if (session != null) + { + ent.Comp.SelectedSessions.Add(session); + + // we shouldn't be blocking the entity if they're just a ghost or smth. + if (!HasComp(session.AttachedEntity)) + antagEnt = session.AttachedEntity; + } + else if (!ignoreSpawner && def.SpawnerPrototype != null) // don't add spawners if we have a player, dummy. + { + antagEnt = Spawn(def.SpawnerPrototype); + isSpawner = true; + } + + if (!antagEnt.HasValue) { - //Remove all chosen players from this list, to prevent duplicates - foreach (var chosenPlayer in chosenPlayers) + var getEntEv = new AntagSelectEntityEvent(session, ent); + RaiseLocalEvent(ent, ref getEntEv, true); + + if (!getEntEv.Handled) { - playerList.Remove(chosenPlayer); + throw new InvalidOperationException($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player."); } - //If we have reached the desired number of players, skip - if (chosenPlayers.Count >= count) - continue; + antagEnt = getEntEv.Entity; + } + + if (antagEnt is not { } player) + return; - //Pick and choose a random number of players from this list - chosenPlayers.AddRange(ChooseAntags(count - chosenPlayers.Count, playerList)); + var getPosEv = new AntagSelectLocationEvent(session, ent); + RaiseLocalEvent(ent, ref getPosEv, true); + if (getPosEv.Handled) + { + var playerXform = Transform(player); + var pos = RobustRandom.Pick(getPosEv.Coordinates); + _transform.SetMapCoordinates((player, playerXform), pos); } - return chosenPlayers; - } - /// - /// Helper method to choose sessions from a list - /// - /// List of eligible sessions - /// How many to choose - /// Up to the specified count of elements from the provided list - public List ChooseAntags(int count, List eligiblePlayers) - { - var chosenPlayers = new List(); - for (int i = 0; i < count; i++) + if (isSpawner) { - if (eligiblePlayers.Count == 0) - break; + if (!TryComp(player, out var spawnerComp)) + { + Log.Error("Antag spawner with GhostRoleAntagSpawnerComponent."); + return; + } + + spawnerComp.Rule = ent; + spawnerComp.Definition = def; + return; + } - chosenPlayers.Add(RobustRandom.PickAndTake(eligiblePlayers)); + EntityManager.AddComponents(player, def.Components); + _stationSpawning.EquipStartingGear(player, def.StartingGear); + + if (session != null) + { + var curMind = session.GetMind(); + if (curMind == null) + { + curMind = _mind.CreateMind(session.UserId, Name(antagEnt.Value)); + _mind.SetUserId(curMind.Value, session.UserId); + } + + _mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true); + _role.MindAddRoles(curMind.Value, def.MindComponents); + ent.Comp.SelectedMinds.Add((curMind.Value, Name(player))); + } + + if (def.Briefing is { } briefing) + { + SendBriefing(session, briefing); } - return chosenPlayers; + var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def); + RaiseLocalEvent(ent, ref afterEv, true); } - #endregion - #region Briefings /// - /// Helper method to send the briefing text and sound to a list of entities + /// Gets an ordered player pool based on player preferences and the antagonist definition. /// - /// The players chosen to be antags - /// The briefing text to send - /// The color the briefing should be, null for default - /// The sound to briefing/greeting sound to play - public void SendBriefing(List entities, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + public AntagSelectionPlayerPool GetPlayerPool(Entity ent, List sessions, AntagSelectionDefinition def) { - foreach (var entity in entities) + var preferredList = new List(); + var secondBestList = new List(); + var unwantedList = new List(); + var invalidList = new List(); + foreach (var session in sessions) { - SendBriefing(entity, briefing, briefingColor, briefingSound); + if (!IsSessionValid(ent, session, def) || + !IsEntityValid(session.AttachedEntity, def)) + { + invalidList.Add(session); + continue; + } + + var pref = (HumanoidCharacterProfile) _pref.GetPreferences(session.UserId).SelectedCharacter; + if (def.PrefRoles.Count != 0 && pref.AntagPreferences.Any(p => def.PrefRoles.Contains(p))) + { + preferredList.Add(session); + } + else if (def.FallbackRoles.Count != 0 && pref.AntagPreferences.Any(p => def.FallbackRoles.Contains(p))) + { + secondBestList.Add(session); + } + else + { + unwantedList.Add(session); + } } + + return new AntagSelectionPlayerPool(new() { preferredList, secondBestList, unwantedList, invalidList }); } /// - /// Helper method to send the briefing text and sound to a player entity + /// Checks if a given session is valid for an antagonist. /// - /// The entity chosen to be antag - /// The briefing text to send - /// The color the briefing should be, null for default - /// The sound to briefing/greeting sound to play - public void SendBriefing(EntityUid entity, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + public bool IsSessionValid(Entity ent, ICommonSession? session, AntagSelectionDefinition def, EntityUid? mind = null) { - if (!_mindSystem.TryGetMind(entity, out _, out var mindComponent)) - return; + if (session == null) + return true; - if (mindComponent.Session == null) - return; + mind ??= session.GetMind(); - SendBriefing(mindComponent.Session, briefing, briefingColor, briefingSound); - } + if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie) + return false; - /// - /// Helper method to send the briefing text and sound to a list of sessions - /// - /// - /// - /// - /// + if (ent.Comp.SelectedSessions.Contains(session)) + return false; - public void SendBriefing(List sessions, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) - { - foreach (var session in sessions) + //todo: we need some way to check that we're not getting the same role twice. (double picking thieves or zombies through midrounds) + + switch (def.MultiAntagSetting) { - SendBriefing(session, briefing, briefingColor, briefingSound); + case AntagAcceptability.None: + { + if (_role.MindIsAntagonist(mind)) + return false; + break; + } + case AntagAcceptability.NotExclusive: + { + if (_role.MindIsExclusiveAntagonist(mind)) + return false; + break; + } } + + // todo: expand this to allow for more fine antag-selection logic for game rules. + if (!_jobs.CanBeAntag(session)) + return false; + + return true; } + /// - /// Helper method to send the briefing text and sound to a session + /// Checks if a given entity (mind/session not included) is valid for a given antagonist. /// - /// The player chosen to be an antag - /// The briefing text to send - /// The color the briefing should be, null for default - /// The sound to briefing/greeting sound to play - - public void SendBriefing(ICommonSession session, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + private bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def) { - _audioSystem.PlayGlobal(briefingSound, session); - var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", briefing)); - ChatManager.ChatMessageToOne(ChatChannel.Server, briefing, wrappedMessage, default, false, session.Channel, briefingColor); + if (entity == null) + return false; + + if (HasComp(entity)) + return false; + + if (!def.AllowNonHumans && !HasComp(entity)) + return false; + + if (def.Whitelist != null) + { + if (!def.Whitelist.IsValid(entity.Value, EntityManager)) + return false; + } + + if (def.Blacklist != null) + { + if (def.Blacklist.IsValid(entity.Value, EntityManager)) + return false; + } + + return true; } - #endregion } + +/// +/// Event raised on a game rule entity in order to determine what the antagonist entity will be. +/// Only raised if the selected player's current entity is invalid. +/// +[ByRefEvent] +public record struct AntagSelectEntityEvent(ICommonSession? Session, Entity GameRule) +{ + public readonly ICommonSession? Session = Session; + + public bool Handled => Entity != null; + + public EntityUid? Entity; +} + +/// +/// Event raised on a game rule entity to determine the location for the antagonist. +/// +[ByRefEvent] +public record struct AntagSelectLocationEvent(ICommonSession? Session, Entity GameRule) +{ + public readonly ICommonSession? Session = Session; + + public bool Handled => Coordinates.Any(); + + public List Coordinates = new(); +} + +/// +/// Event raised on a game rule entity after the setup logic for an antag is complete. +/// Used for applying additional more complex setup logic. +/// +[ByRefEvent] +public readonly record struct AfterAntagEntitySelectedEvent(ICommonSession? Session, EntityUid EntityUid, Entity GameRule, AntagSelectionDefinition Def); diff --git a/Content.Server/Antag/Components/AntagSelectionComponent.cs b/Content.Server/Antag/Components/AntagSelectionComponent.cs new file mode 100644 index 0000000000..096be14049 --- /dev/null +++ b/Content.Server/Antag/Components/AntagSelectionComponent.cs @@ -0,0 +1,189 @@ +using Content.Server.Administration.Systems; +using Content.Server.Destructible.Thresholds; +using Content.Shared.Antag; +using Content.Shared.Roles; +using Content.Shared.Storage; +using Content.Shared.Whitelist; +using Robust.Shared.Audio; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; + +namespace Content.Server.Antag.Components; + +[RegisterComponent, Access(typeof(AntagSelectionSystem), typeof(AdminVerbSystem))] +public sealed partial class AntagSelectionComponent : Component +{ + /// + /// Has the primary selection of antagonists finished yet? + /// + [DataField] + public bool SelectionsComplete; + + /// + /// The definitions for the antagonists + /// + [DataField] + public List Definitions = new(); + + /// + /// The minds and original names of the players selected to be antagonists. + /// + [DataField] + public List<(EntityUid, string)> SelectedMinds = new(); + + /// + /// When the antag selection will occur. + /// + [DataField] + public AntagSelectionTime SelectionTime = AntagSelectionTime.PostPlayerSpawn; + + /// + /// Cached sessions of players who are chosen. Used so we don't have to rebuild the pool multiple times in a tick. + /// Is not serialized. + /// + public HashSet SelectedSessions = new(); +} + +[DataDefinition] +public partial struct AntagSelectionDefinition() +{ + /// + /// A list of antagonist roles that are used for selecting which players will be antagonists. + /// + [DataField] + public List> PrefRoles = new(); + + /// + /// Fallback for . Useful if you need multiple role preferences for a team antagonist. + /// + [DataField] + public List> FallbackRoles = new(); + + /// + /// Should we allow people who already have an antagonist role? + /// + [DataField] + public AntagAcceptability MultiAntagSetting = AntagAcceptability.None; + + /// + /// The minimum number of this antag. + /// + [DataField] + public int Min = 1; + + /// + /// The maximum number of this antag. + /// + [DataField] + public int Max = 1; + + /// + /// A range used to randomly select + /// + [DataField] + public MinMax? MinRange; + + /// + /// A range used to randomly select + /// + [DataField] + public MinMax? MaxRange; + + /// + /// a player to antag ratio: used to determine the amount of antags that will be present. + /// + [DataField] + public int PlayerRatio = 10; + + /// + /// Whether or not players should be picked to inhabit this antag or not. + /// + [DataField] + public bool PickPlayer = true; + + /// + /// If true, players that latejoin into a round have a chance of being converted into antagonists. + /// + [DataField] + public bool LateJoinAdditional = false; + + //todo: find out how to do this with minimal boilerplate: filler department, maybe? + //public HashSet> JobBlacklist = new() + + /// + /// Mostly just here for legacy compatibility and reducing boilerplate + /// + [DataField] + public bool AllowNonHumans = false; + + /// + /// A whitelist for selecting which players can become this antag. + /// + [DataField] + public EntityWhitelist? Whitelist; + + /// + /// A blacklist for selecting which players can become this antag. + /// + [DataField] + public EntityWhitelist? Blacklist; + + /// + /// Components added to the player. + /// + [DataField] + public ComponentRegistry Components = new(); + + /// + /// Components added to the player's mind. + /// + [DataField] + public ComponentRegistry MindComponents = new(); + + /// + /// A set of starting gear that's equipped to the player. + /// + [DataField] + public ProtoId? StartingGear; + + /// + /// A briefing shown to the player. + /// + [DataField] + public BriefingData? Briefing; + + /// + /// A spawner used to defer the selection of this particular definition. + /// + /// + /// Not the cleanest way of doing this code but it's just an odd specific behavior. + /// Sue me. + /// + [DataField] + public EntProtoId? SpawnerPrototype; +} + +/// +/// Contains data used to generate a briefing. +/// +[DataDefinition] +public partial struct BriefingData +{ + /// + /// The text shown + /// + [DataField] + public LocId? Text; + + /// + /// The color of the text. + /// + [DataField] + public Color? Color; + + /// + /// The sound played. + /// + [DataField] + public SoundSpecifier? Sound; +} diff --git a/Content.Server/Antag/Components/GhostRoleAntagSpawnerComponent.cs b/Content.Server/Antag/Components/GhostRoleAntagSpawnerComponent.cs new file mode 100644 index 0000000000..fcaa4d4267 --- /dev/null +++ b/Content.Server/Antag/Components/GhostRoleAntagSpawnerComponent.cs @@ -0,0 +1,14 @@ +namespace Content.Server.Antag.Components; + +/// +/// Ghost role spawner that creates an antag for the associated gamerule. +/// +[RegisterComponent, Access(typeof(AntagSelectionSystem))] +public sealed partial class GhostRoleAntagSpawnerComponent : Component +{ + [DataField] + public EntityUid? Rule; + + [DataField] + public AntagSelectionDefinition? Definition; +} diff --git a/Content.Server/Antag/MobReplacementRuleSystem.cs b/Content.Server/Antag/MobReplacementRuleSystem.cs index ba09c84bce..18837b5a7c 100644 --- a/Content.Server/Antag/MobReplacementRuleSystem.cs +++ b/Content.Server/Antag/MobReplacementRuleSystem.cs @@ -1,45 +1,16 @@ -using System.Numerics; -using Content.Server.Advertise.Components; -using Content.Server.Advertise.EntitySystems; using Content.Server.Antag.Mimic; -using Content.Server.Chat.Systems; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; -using Content.Server.NPC.Systems; -using Content.Server.Station.Systems; -using Content.Server.GameTicking; using Content.Shared.VendingMachines; using Robust.Shared.Map; -using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Server.GameObjects; -using Robust.Shared.Physics.Systems; -using System.Linq; -using Robust.Shared.Physics; -using Content.Shared.Movement.Components; -using Content.Shared.Damage; -using Content.Server.NPC.HTN; -using Content.Server.NPC; -using Content.Shared.Weapons.Melee; -using Content.Server.Power.Components; -using Content.Shared.CombatMode; namespace Content.Server.Antag; public sealed class MobReplacementRuleSystem : GameRuleSystem { [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly GameTicker _gameTicker = default!; - [Dependency] private readonly ChatSystem _chat = default!; - [Dependency] private readonly IPrototypeManager _prototype = default!; - [Dependency] private readonly IComponentFactory _componentFactory = default!; - [Dependency] private readonly SharedPhysicsSystem _physics = default!; - [Dependency] private readonly NpcFactionSystem _npcFaction = default!; - [Dependency] private readonly NPCSystem _npc = default!; - [Dependency] private readonly SharedTransformSystem _transform = default!; - [Dependency] private readonly AdvertiseSystem _advertise = default!; - protected override void Started(EntityUid uid, MobReplacementRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { @@ -47,133 +18,21 @@ protected override void Started(EntityUid uid, MobReplacementRuleComponent compo var query = AllEntityQuery(); var spawns = new List<(EntityUid Entity, EntityCoordinates Coordinates)>(); - var stations = _gameTicker.GetSpawnableStations(); while (query.MoveNext(out var vendingUid, out _, out var xform)) { - var ownerStation = _station.GetOwningStation(vendingUid); - - if (ownerStation == null - || ownerStation != stations[0]) - continue; - - // Make sure that we aren't running this on something that is already a mimic - if (HasComp(vendingUid)) + if (!_random.Prob(component.Chance)) continue; spawns.Add((vendingUid, xform.Coordinates)); } - if (spawns == null) + foreach (var entity in spawns) { - //WTF THE STATION DOESN'T EXIST! WE MUST BE IN A TEST! QUICK, PUT A MIMIC AT 0,0!!! - Spawn(component.Proto, new EntityCoordinates(uid, new Vector2(0, 0))); - } - else - { - // This is intentionally not clamped. If a server host wants to replace every vending machine in the entire station with a mimic, who am I to stop them? - var k = MathF.MaxMagnitude(component.NumberToReplace, 1); - while (k > 0 && spawns != null && spawns.Count > 0) - { - if (k > 1) - { - var spawnLocation = _random.PickAndTake(spawns); - BuildAMimicWorkshop(spawnLocation.Entity, component); - } - else - { - BuildAMimicWorkshop(spawns[0].Entity, component); - } - - if (k == MathF.MaxMagnitude(component.NumberToReplace, 1) - && component.DoAnnouncement) - _chat.DispatchStationAnnouncement(stations[0], Loc.GetString("station-event-rampant-intelligence-announcement"), playDefaultSound: true, - colorOverride: Color.Red, sender: "Central Command"); - - k--; - } - } - } - - /// - /// It's like Build a Bear, but MURDER - /// - /// - public void BuildAMimicWorkshop(EntityUid uid, MobReplacementRuleComponent component) - { - var metaData = MetaData(uid); - var vendorPrototype = metaData.EntityPrototype; - var mimicProto = _prototype.Index(component.Proto); - - var vendorComponents = vendorPrototype?.Components.Keys - .Where(n => n != "Transform" && n != "MetaData") - .Select(name => (name, _componentFactory.GetRegistration(name).Type)) - .ToList() ?? new List<(string name, Type type)>(); - - var mimicComponents = mimicProto?.Components.Keys - .Where(n => n != "Transform" && n != "MetaData") - .Select(name => (name, _componentFactory.GetRegistration(name).Type)) - .ToList() ?? new List<(string name, Type type)>(); + var coordinates = entity.Coordinates; + Del(entity.Entity); - foreach (var name in mimicComponents.Except(vendorComponents)) - { - var newComponent = _componentFactory.GetComponent(name.name); - EntityManager.AddComponent(uid, newComponent); + Spawn(component.Proto, coordinates); } - - var xform = Transform(uid); - if (xform.Anchored) - _transform.Unanchor(uid, xform); - - SetupMimicNPC(uid, component); - - if (TryComp(uid, out var vendor) - && component.VendorModify) - SetupMimicVendor(uid, component, vendor); - } - /// - /// This handles getting the entity ready to be a hostile NPC - /// - /// - /// - private void SetupMimicNPC(EntityUid uid, MobReplacementRuleComponent component) - { - _physics.SetBodyType(uid, BodyType.KinematicController); - _npcFaction.AddFaction(uid, "SimpleHostile"); - - var melee = EnsureComp(uid); - melee.Angle = 0; - DamageSpecifier dspec = new() - { - DamageDict = new() - { - { "Blunt", component.MimicMeleeDamage } - } - }; - melee.Damage = dspec; - - var movementSpeed = EnsureComp(uid); - (movementSpeed.BaseSprintSpeed, movementSpeed.BaseWalkSpeed) = (component.MimicMoveSpeed, component.MimicMoveSpeed); - - var htn = EnsureComp(uid); - htn.RootTask = new HTNCompoundTask() { Task = component.MimicAIType }; - htn.Blackboard.SetValue(NPCBlackboard.NavSmash, component.MimicSmashGlass); - _npc.WakeNPC(uid, htn); - } - - /// - /// Handling specific interactions with vending machines - /// - /// - /// - /// - private void SetupMimicVendor(EntityUid uid, MobReplacementRuleComponent mimicComponent, AdvertiseComponent vendorComponent) - { - vendorComponent.MinimumWait = 5; - vendorComponent.MaximumWait = 15; - _advertise.SayAdvertisement(uid, vendorComponent); - - if (TryComp(uid, out var aPC)) - aPC.NeedsPower = false; } } diff --git a/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs b/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs index 62d994dac3..abdc950020 100644 --- a/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs +++ b/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs @@ -144,7 +144,7 @@ private bool TrySpawnParadoxAnomaly(string rule, [NotNullWhen(true)] out EntityU if (job.StartingGear != null && _proto.TryIndex(job.StartingGear, out var gear)) { - _stationSpawning.EquipStartingGear(spawned, gear, profile); + _stationSpawning.EquipStartingGear(spawned, gear); _stationSpawning.EquipIdCard(spawned, profile.Name, job, diff --git a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs index 22d96a5414..1cb3809e21 100644 --- a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs +++ b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Robust.Shared.Random; using Content.Server.GameTicking.Rules.Components; using Content.Server.NPC.Components; diff --git a/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs b/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs index ba042d8966..c5d199164b 100644 --- a/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs +++ b/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs @@ -19,6 +19,7 @@ using Content.Shared.Salvage; using Content.Shared.Random.Helpers; using System.Linq; +using Content.Server.GameTicking.Components; using Content.Shared.CCVar; namespace Content.Server.StationEvents.Events; diff --git a/Content.Server/Destructible/Thresholds/MinMax.cs b/Content.Server/Destructible/Thresholds/MinMax.cs index b438e7c0e8..c44864183a 100644 --- a/Content.Server/Destructible/Thresholds/MinMax.cs +++ b/Content.Server/Destructible/Thresholds/MinMax.cs @@ -1,4 +1,6 @@ -namespace Content.Server.Destructible.Thresholds +using Robust.Shared.Random; + +namespace Content.Server.Destructible.Thresholds { [Serializable] [DataDefinition] @@ -9,5 +11,16 @@ public partial struct MinMax [DataField("max")] public int Max; + + public MinMax(int min, int max) + { + Min = min; + Max = max; + } + + public int Next(IRobustRandom random) + { + return random.Next(Min, Max + 1); + } } } diff --git a/Content.Server/Entry/EntryPoint.cs b/Content.Server/Entry/EntryPoint.cs index a04f274491..48a6597349 100644 --- a/Content.Server/Entry/EntryPoint.cs +++ b/Content.Server/Entry/EntryPoint.cs @@ -5,9 +5,9 @@ using Content.Server.Afk; using Content.Server.Chat.Managers; using Content.Server.Connection; -using Content.Server.DiscordAuth; using Content.Server.JoinQueue; using Content.Server.Database; +using Content.Server.DiscordAuth; using Content.Server.EUI; using Content.Server.GameTicking; using Content.Server.GhostKick; diff --git a/Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs b/Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs similarity index 84% rename from Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs rename to Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs index 956768bdd9..b9e6fa5d4b 100644 --- a/Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs +++ b/Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.GameTicking.Rules.Components; +namespace Content.Server.GameTicking.Components; /// /// Added to game rules before and removed before . diff --git a/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs b/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs new file mode 100644 index 0000000000..de4be83627 --- /dev/null +++ b/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; + +namespace Content.Server.GameTicking.Components; + +/// +/// Generic component used to track a gamerule that's start has been delayed. +/// +[RegisterComponent, AutoGenerateComponentPause] +public sealed partial class DelayedStartRuleComponent : Component +{ + /// + /// The time at which the rule will start properly. + /// + [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoPausedField] + public TimeSpan RuleStartTime; +} diff --git a/Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs b/Content.Server/GameTicking/Components/EndedGameRuleComponent.cs similarity index 81% rename from Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs rename to Content.Server/GameTicking/Components/EndedGameRuleComponent.cs index 4484abd4d0..3234bfff3a 100644 --- a/Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs +++ b/Content.Server/GameTicking/Components/EndedGameRuleComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.GameTicking.Rules.Components; +namespace Content.Server.GameTicking.Components; /// /// Added to game rules before . diff --git a/Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs b/Content.Server/GameTicking/Components/GameRuleComponent.cs similarity index 83% rename from Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs rename to Content.Server/GameTicking/Components/GameRuleComponent.cs index 6309b97402..1e6c3f0ab1 100644 --- a/Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs +++ b/Content.Server/GameTicking/Components/GameRuleComponent.cs @@ -1,6 +1,7 @@ +using Content.Server.Destructible.Thresholds; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.GameTicking.Rules.Components; +namespace Content.Server.GameTicking.Components; /// /// Component attached to all gamerule entities. @@ -20,6 +21,12 @@ public sealed partial class GameRuleComponent : Component /// [DataField] public int MinPlayers; + + /// + /// A delay for when the rule the is started and when the starting logic actually runs. + /// + [DataField] + public MinMax? Delay; } /// diff --git a/Content.Server/GameTicking/GameTicker.GameRule.cs b/Content.Server/GameTicking/GameTicker.GameRule.cs index 4ebe946af4..f52a3cb296 100644 --- a/Content.Server/GameTicking/GameTicker.GameRule.cs +++ b/Content.Server/GameTicking/GameTicker.GameRule.cs @@ -1,6 +1,6 @@ using System.Linq; using Content.Server.Administration; -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; using Content.Shared.Administration; using Content.Shared.Database; using Content.Shared.Prototypes; @@ -102,6 +102,22 @@ public bool StartGameRule(EntityUid ruleEntity, GameRuleComponent? ruleData = nu if (MetaData(ruleEntity).EntityPrototype?.ID is not { } id) // you really fucked up return false; + // If we already have it, then we just skip the delay as it has already happened. + if (!RemComp(ruleEntity) && ruleData.Delay != null) + { + var delayTime = TimeSpan.FromSeconds(ruleData.Delay.Value.Next(_robustRandom)); + + if (delayTime > TimeSpan.Zero) + { + _sawmill.Info($"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}"); + _adminLogger.Add(LogType.EventStarted, $"Queued start for game rule {ToPrettyString(ruleEntity)} with delay {delayTime}"); + + var delayed = EnsureComp(ruleEntity); + delayed.RuleStartTime = _gameTiming.CurTime + (delayTime); + return true; + } + } + _allPreviousGameRules.Add((RoundDuration(), id)); _sawmill.Info($"Started game rule {ToPrettyString(ruleEntity)}"); _adminLogger.Add(LogType.EventStarted, $"Started game rule {ToPrettyString(ruleEntity)}"); @@ -255,6 +271,18 @@ public IEnumerable GetAllGameRulePrototypes() } } + private void UpdateGameRules() + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var delay, out var rule)) + { + if (_gameTiming.CurTime < delay.RuleStartTime) + continue; + + StartGameRule(uid, rule); + } + } + #region Command Implementations [AdminCommand(AdminFlags.Fun)] @@ -323,38 +351,3 @@ private void ClearGameRulesCommand(IConsoleShell shell, string argstr, string[] #endregion } - -/* -/// -/// Raised broadcast when a game rule is selected, but not started yet. -/// -public sealed class GameRuleAddedEvent -{ - public GameRulePrototype Rule { get; } - - public GameRuleAddedEvent(GameRulePrototype rule) - { - Rule = rule; - } -} - -public sealed class GameRuleStartedEvent -{ - public GameRulePrototype Rule { get; } - - public GameRuleStartedEvent(GameRulePrototype rule) - { - Rule = rule; - } -} - -public sealed class GameRuleEndedEvent -{ - public GameRulePrototype Rule { get; } - - public GameRuleEndedEvent(GameRulePrototype rule) - { - Rule = rule; - } -} -*/ diff --git a/Content.Server/GameTicking/GameTicker.cs b/Content.Server/GameTicking/GameTicker.cs index efda3df0ca..fa23312268 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -133,6 +133,7 @@ public override void Update(float frameTime) return; base.Update(frameTime); UpdateRoundFlow(frameTime); + UpdateGameRules(); } } } diff --git a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs new file mode 100644 index 0000000000..463aecbff5 --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs @@ -0,0 +1,29 @@ +using Content.Server.Maps; +using Content.Shared.Whitelist; +using Robust.Shared.Map; +using Robust.Shared.Prototypes; +using Robust.Shared.Utility; + +namespace Content.Server.GameTicking.Rules.Components; + +/// +/// This is used for a game rule that loads a map when activated. +/// +[RegisterComponent] +public sealed partial class LoadMapRuleComponent : Component +{ + [DataField] + public MapId? Map; + + [DataField] + public ProtoId? GameMap ; + + [DataField] + public ResPath? MapPath; + + [DataField] + public List MapGrids = new(); + + [DataField] + public EntityWhitelist? SpawnerWhitelist; +} diff --git a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs index e6966c1e37..fa352eb320 100644 --- a/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NinjaRuleComponent.cs @@ -8,7 +8,7 @@ namespace Content.Server.GameTicking.Rules.Components; /// /// Stores some configuration used by the ninja system. -/// Objectives and roundend summary are handled by . +/// Objectives and roundend summary are handled by . /// [RegisterComponent, Access(typeof(SpaceNinjaSystem))] public sealed partial class NinjaRuleComponent : Component diff --git a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs index e02d90c18b..bb1b7c8746 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs @@ -1,6 +1,3 @@ -using Content.Shared.Roles; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Server.GameTicking.Rules.Components; /// @@ -9,11 +6,5 @@ namespace Content.Server.GameTicking.Rules.Components; /// TODO: Remove once systems can request spawns from the ghost role system directly. /// [RegisterComponent] -public sealed partial class NukeOperativeSpawnerComponent : Component -{ - [DataField("name", required:true)] - public string OperativeName = default!; +public sealed partial class NukeOperativeSpawnerComponent : Component; - [DataField] - public NukeopSpawnPreset SpawnDetails = default!; -} diff --git a/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs index 358b157cdf..3d097cd7c7 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs @@ -6,4 +6,6 @@ [RegisterComponent] public sealed partial class NukeOpsShuttleComponent : Component { + [DataField] + public EntityUid AssociatedRule; } diff --git a/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs index 8efd61b469..f64947e286 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs @@ -1,10 +1,9 @@ using Content.Server.Maps; using Content.Server.NPC.Components; using Content.Server.RoundEnd; -using Content.Server.StationEvents.Events; using Content.Shared.Dataset; using Content.Shared.Roles; -using Robust.Shared.Map; +using Robust.Shared.Audio; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; @@ -14,18 +13,9 @@ namespace Content.Server.GameTicking.Rules.Components; -[RegisterComponent, Access(typeof(NukeopsRuleSystem), typeof(LoneOpsSpawnRule))] +[RegisterComponent, Access(typeof(NukeopsRuleSystem))] public sealed partial class NukeopsRuleComponent : Component { - /// - /// This INCLUDES the operatives. So a value of 3 is satisfied by 2 players & 1 operative - /// - [DataField] - public int PlayersPerOperative = 10; - - [DataField] - public int MaxOps = 5; - /// /// What will happen if all of the nuclear operatives will die. Used by LoneOpsSpawn event. /// @@ -56,12 +46,6 @@ public sealed partial class NukeopsRuleComponent : Component [DataField] public TimeSpan EvacShuttleTime = TimeSpan.FromMinutes(3); - /// - /// Whether or not to spawn the nuclear operative outpost. Used by LoneOpsSpawn event. - /// - [DataField] - public bool SpawnOutpost = true; - /// /// Whether or not nukie left their outpost /// @@ -84,7 +68,7 @@ public sealed partial class NukeopsRuleComponent : Component /// This amount of TC will be given to each nukie /// [DataField] - public int WarTCAmountPerNukie = 40; + public int WarTcAmountPerNukie = 40; /// /// Delay between war declaration and nuke ops arrival on station map. Gives crew time to prepare @@ -98,49 +82,23 @@ public sealed partial class NukeopsRuleComponent : Component [DataField] public int WarDeclarationMinOps = 4; - [DataField] - public EntProtoId SpawnPointProto = "SpawnPointNukies"; - - [DataField] - public EntProtoId GhostSpawnPointProto = "SpawnPointGhostNukeOperative"; - - [DataField] - public string OperationName = "Test Operation"; - - [DataField] - public ProtoId OutpostMapPrototype = "NukieOutpost"; - [DataField] public WinType WinType = WinType.Neutral; [DataField] public List WinConditions = new (); - public MapId? NukiePlanet; - - // TODO: use components, don't just cache entity UIDs - // There have been (and probably still are) bugs where these refer to deleted entities from old rounds. - public EntityUid? NukieOutpost; - public EntityUid? NukieShuttle; - public EntityUid? TargetStation; - - /// - /// Data to be used in for an operative once the Mind has been added. - /// - [DataField] - public Dictionary OperativeMindPendingData = new(); - - [DataField(required: true)] - public ProtoId Faction = default!; - [DataField] - public NukeopSpawnPreset CommanderSpawnDetails = new() { AntagRoleProto = "NukeopsCommander", GearProto = "SyndicateCommanderGearFull", NamePrefix = "nukeops-role-commander", NameList = "SyndicateNamesElite" }; + public EntityUid? TargetStation; [DataField] - public NukeopSpawnPreset AgentSpawnDetails = new() { AntagRoleProto = "NukeopsMedic", GearProto = "SyndicateOperativeMedicFull", NamePrefix = "nukeops-role-agent", NameList = "SyndicateNamesNormal" }; + public ProtoId Faction = "Syndicate"; + /// + /// Path to antagonist alert sound. + /// [DataField] - public NukeopSpawnPreset OperativeSpawnDetails = new(); + public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/nukeops_start.ogg"); } /// diff --git a/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs deleted file mode 100644 index 1d03b41d77..0000000000 --- a/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Robust.Shared.Audio; - -namespace Content.Server.GameTicking.Rules.Components; - -[RegisterComponent, Access(typeof(PiratesRuleSystem))] -public sealed partial class PiratesRuleComponent : Component -{ - [ViewVariables] - public List Pirates = new(); - [ViewVariables] - public EntityUid PirateShip = EntityUid.Invalid; - [ViewVariables] - public HashSet InitialItems = new(); - [ViewVariables] - public double InitialShipValue; - - /// - /// Path to antagonist alert sound. - /// - [DataField("pirateAlertSound")] - public SoundSpecifier PirateAlertSound = new SoundPathSpecifier( - "/Audio/Ambience/Antag/pirate_start.ogg", - AudioParams.Default.WithVolume(4)); -} diff --git a/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs index 2ce3f1f9a6..3b19bbffb6 100644 --- a/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs @@ -22,43 +22,6 @@ public sealed partial class RevolutionaryRuleComponent : Component [DataField] public TimeSpan TimerWait = TimeSpan.FromSeconds(20); - /// - /// Stores players minds - /// - [DataField] - public Dictionary HeadRevs = new(); - - [DataField] - public ProtoId HeadRevPrototypeId = "HeadRev"; - - /// - /// Min players needed for Revolutionary gamemode to start. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int MinPlayers = 15; - - /// - /// Max Head Revs allowed during selection. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int MaxHeadRevs = 3; - - /// - /// The amount of Head Revs that will spawn per this amount of players. - /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public int PlayersPerHeadRev = 15; - - /// - /// The gear head revolutionaries are given on spawn. - /// - [DataField] - public List StartingGear = new() - { - "Flash", - "ClothingEyesGlassesSunglasses" - }; - /// /// The time it takes after the last head is killed for the shuttle to arrive. /// diff --git a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs index 9dfd6e6627..01a078625a 100644 --- a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs @@ -1,12 +1,11 @@ using Content.Shared.Random; -using Content.Shared.Roles; using Robust.Shared.Audio; using Robust.Shared.Prototypes; namespace Content.Server.GameTicking.Rules.Components; /// -/// Stores data for . +/// Stores data for . /// [RegisterComponent, Access(typeof(ThiefRuleSystem))] public sealed partial class ThiefRuleComponent : Component @@ -23,42 +22,9 @@ public sealed partial class ThiefRuleComponent : Component [DataField] public float BigObjectiveChance = 0.7f; - /// - /// Add a Pacified comp to thieves - /// - [DataField] - public bool PacifistThieves = true; - - [DataField] - public ProtoId ThiefPrototypeId = "Thief"; - [DataField] public float MaxObjectiveDifficulty = 2.5f; [DataField] public int MaxStealObjectives = 10; - - /// - /// Things that will be given to thieves - /// - [DataField] - public List StarterItems = new() { "ToolboxThief", "ClothingHandsChameleonThief" }; - - /// - /// All Thieves created by this rule - /// - [DataField] - public List ThievesMinds = new(); - - /// - /// Max Thiefs created by rule on roundstart - /// - [DataField] - public int MaxAllowThief = 3; - - /// - /// Sound played when making the player a thief via antag control or ghost role - /// - [DataField] - public SoundSpecifier? GreetingSound = new SoundPathSpecifier("/Audio/Misc/thief_greeting.ogg"); } diff --git a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs index 62619db76a..dd359969b6 100644 --- a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs @@ -57,4 +57,19 @@ public enum SelectionState /// [DataField] public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/traitor_start.ogg"); + + /// + /// The amount of codewords that are selected. + /// + [DataField] + public int CodewordCount = 4; + + /// + /// The amount of TC traitors start with. + /// + [DataField] + public int StartingBalance = 20; + + [DataField] + public int MaxDifficulty = 5; } diff --git a/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs index 4fe91e3a5f..59d1940eaf 100644 --- a/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs @@ -8,12 +8,6 @@ namespace Content.Server.GameTicking.Rules.Components; [RegisterComponent, Access(typeof(ZombieRuleSystem))] public sealed partial class ZombieRuleComponent : Component { - [DataField] - public Dictionary InitialInfectedNames = new(); - - [DataField] - public ProtoId PatientZeroPrototypeId = "InitialInfected"; - /// /// When the round will next check for round end. /// @@ -26,61 +20,9 @@ public sealed partial class ZombieRuleComponent : Component [DataField] public TimeSpan EndCheckDelay = TimeSpan.FromSeconds(30); - /// - /// The time at which the initial infected will be chosen. - /// - [DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), ViewVariables(VVAccess.ReadWrite)] - public TimeSpan? StartTime; - - /// - /// The minimum amount of time after the round starts that the initial infected will be chosen. - /// - [DataField] - public TimeSpan MinStartDelay = TimeSpan.FromMinutes(10); - - /// - /// The maximum amount of time after the round starts that the initial infected will be chosen. - /// - [DataField] - public TimeSpan MaxStartDelay = TimeSpan.FromMinutes(15); - - /// - /// The sound that plays when someone becomes an initial infected. - /// todo: this should have a unique sound instead of reusing the zombie one. - /// - [DataField] - public SoundSpecifier InitialInfectedSound = new SoundPathSpecifier("/Audio/Ambience/Antag/zombie_start.ogg"); - - /// - /// The minimum amount of time initial infected have before they start taking infection damage. - /// - [DataField] - public TimeSpan MinInitialInfectedGrace = TimeSpan.FromMinutes(12.5f); - - /// - /// The maximum amount of time initial infected have before they start taking damage. - /// - [DataField] - public TimeSpan MaxInitialInfectedGrace = TimeSpan.FromMinutes(15f); - - /// - /// How many players for each initial infected. - /// - [DataField] - public int PlayersPerInfected = 10; - - /// - /// The maximum number of initial infected. - /// - [DataField] - public int MaxInitialInfected = 6; - /// /// After this amount of the crew become zombies, the shuttle will be automatically called. /// [DataField] public float ZombieShuttleCallPercentage = 0.7f; - - [DataField] - public EntProtoId ZombifySelfActionPrototype = "ActionTurnUndead"; } diff --git a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs index 82ac755592..78b8a8a85c 100644 --- a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Administration.Commands; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.KillTracking; using Content.Server.Mind; @@ -33,7 +34,6 @@ public override void Initialize() SubscribeLocalEvent(OnSpawnComplete); SubscribeLocalEvent(OnKillReported); SubscribeLocalEvent(OnPointChanged); - SubscribeLocalEvent(OnRoundEndTextAppend); } private void OnBeforeSpawn(PlayerBeforeSpawnEvent ev) @@ -113,21 +113,17 @@ private void OnPointChanged(EntityUid uid, DeathMatchRuleComponent component, re _roundEnd.EndRound(component.RestartDelay); } - private void OnRoundEndTextAppend(RoundEndTextAppendEvent ev) + protected override void AppendRoundEndText(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var dm, out var point, out var rule)) - { - if (!GameTicker.IsGameRuleAdded(uid, rule)) - continue; + if (!TryComp(uid, out var point)) + return; - if (dm.Victor != null && _player.TryGetPlayerData(dm.Victor.Value, out var data)) - { - ev.AddLine(Loc.GetString("point-scoreboard-winner", ("player", data.UserName))); - ev.AddLine(""); - } - ev.AddLine(Loc.GetString("point-scoreboard-header")); - ev.AddLine(new FormattedMessage(point.Scoreboard).ToMarkup()); + if (component.Victor != null && _player.TryGetPlayerData(component.Victor.Value, out var data)) + { + args.AddLine(Loc.GetString("point-scoreboard-winner", ("player", data.UserName))); + args.AddLine(""); } + args.AddLine(Loc.GetString("point-scoreboard-header")); + args.AddLine(new FormattedMessage(point.Scoreboard).ToMarkup()); } } diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs index a60a2bfe22..27a9edbad7 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Robust.Shared.Collections; @@ -15,29 +16,12 @@ protected EntityQueryEnumerator Q return EntityQueryEnumerator(); } - protected bool TryRoundStartAttempt(RoundStartAttemptEvent ev, string localizedPresetName) + /// + /// Queries all gamerules, regardless of if they're active or not. + /// + protected EntityQueryEnumerator QueryAllRules() { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out _, out _, out _, out var gameRule)) - { - var minPlayers = gameRule.MinPlayers; - if (!ev.Forced && ev.Players.Length < minPlayers) - { - ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players", - ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers), - ("presetName", localizedPresetName))); - ev.Cancel(); - continue; - } - - if (ev.Players.Length == 0) - { - ChatManager.DispatchServerAnnouncement(Loc.GetString("preset-no-one-ready")); - ev.Cancel(); - } - } - - return !ev.Cancelled; + return EntityQueryEnumerator(); } /// diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.cs index 363c2ad7f7..c167ae7b6c 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.cs @@ -1,6 +1,6 @@ using Content.Server.Atmos.EntitySystems; using Content.Server.Chat.Managers; -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; using Robust.Server.GameObjects; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -22,9 +22,31 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartAttempt); SubscribeLocalEvent(OnGameRuleAdded); SubscribeLocalEvent(OnGameRuleStarted); SubscribeLocalEvent(OnGameRuleEnded); + SubscribeLocalEvent(OnRoundEndTextAppend); + } + + private void OnStartAttempt(RoundStartAttemptEvent args) + { + if (args.Forced || args.Cancelled) + return; + + var query = QueryAllRules(); + while (query.MoveNext(out var uid, out _, out var gameRule)) + { + var minPlayers = gameRule.MinPlayers; + if (args.Players.Length >= minPlayers) + continue; + + ChatManager.SendAdminAnnouncement(Loc.GetString("preset-not-enough-ready-players", + ("readyPlayersCount", args.Players.Length), + ("minimumPlayers", minPlayers), + ("presetName", ToPrettyString(uid)))); + args.Cancel(); + } } private void OnGameRuleAdded(EntityUid uid, T component, ref GameRuleAddedEvent args) @@ -48,6 +70,12 @@ private void OnGameRuleEnded(EntityUid uid, T component, ref GameRuleEndedEvent Ended(uid, component, ruleData, args); } + private void OnRoundEndTextAppend(Entity ent, ref RoundEndTextAppendEvent args) + { + if (!TryComp(ent, out var ruleData)) + return; + AppendRoundEndText(ent, ent, ruleData, ref args); + } /// /// Called when the gamerule is added @@ -73,6 +101,14 @@ protected virtual void Ended(EntityUid uid, T component, GameRuleComponent gameR } + /// + /// Called at the end of a round when text needs to be added for a game rule. + /// + protected virtual void AppendRoundEndText(EntityUid uid, T component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) + { + + } + /// /// Called on an active gamerule entity in the Update function /// diff --git a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs index b775b7af56..01fa387595 100644 --- a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs @@ -1,5 +1,6 @@ using System.Threading; using Content.Server.Chat.Managers; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Robust.Server.Player; using Robust.Shared.Player; diff --git a/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs index 01fd97d9a7..3da55e30c9 100644 --- a/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Managers; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.KillTracking; using Content.Shared.Chat; diff --git a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs new file mode 100644 index 0000000000..aba9ed9e58 --- /dev/null +++ b/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs @@ -0,0 +1,80 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Spawners.Components; +using Robust.Server.GameObjects; +using Robust.Server.Maps; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Rules; + +public sealed class LoadMapRuleSystem : GameRuleSystem +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly MapSystem _map = default!; + [Dependency] private readonly MapLoaderSystem _mapLoader = default!; + [Dependency] private readonly TransformSystem _transform = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSelectLocation); + SubscribeLocalEvent(OnGridSplit); + } + + private void OnGridSplit(ref GridSplitEvent args) + { + var rule = QueryActiveRules(); + while (rule.MoveNext(out _, out var mapComp, out _)) + { + if (!mapComp.MapGrids.Contains(args.Grid)) + continue; + + mapComp.MapGrids.AddRange(args.NewGrids); + break; + } + } + + protected override void Added(EntityUid uid, LoadMapRuleComponent comp, GameRuleComponent rule, GameRuleAddedEvent args) + { + if (comp.Map != null) + return; + + _map.CreateMap(out var mapId); + comp.Map = mapId; + + if (comp.GameMap != null) + { + var gameMap = _prototypeManager.Index(comp.GameMap.Value); + comp.MapGrids.AddRange(GameTicker.LoadGameMap(gameMap, comp.Map.Value, new MapLoadOptions())); + } + else if (comp.MapPath != null) + { + if (_mapLoader.TryLoad(comp.Map.Value, comp.MapPath.Value.ToString(), out var roots, new MapLoadOptions { LoadMap = true })) + comp.MapGrids.AddRange(roots); + } + else + { + Log.Error($"No valid map prototype or map path associated with the rule {ToPrettyString(uid)}"); + } + } + + private void OnSelectLocation(Entity ent, ref AntagSelectLocationEvent args) + { + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out _, out var xform)) + { + if (xform.MapID != ent.Comp.Map) + continue; + + if (xform.GridUid == null || !ent.Comp.MapGrids.Contains(xform.GridUid.Value)) + continue; + + if (ent.Comp.SpawnerWhitelist != null && !ent.Comp.SpawnerWhitelist.IsValid(uid, EntityManager)) + continue; + + args.Coordinates.Add(_transform.GetMapCoordinates(xform)); + } + } +} diff --git a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs index e792a004df..ee3a025533 100644 --- a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs @@ -1,5 +1,6 @@ using System.Threading; using Content.Server.Chat.Managers; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Timer = Robust.Shared.Timing.Timer; diff --git a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs index 46040e2945..d06b9fb899 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -1,77 +1,51 @@ -using Content.Server.Administration.Commands; -using Content.Server.Administration.Managers; using Content.Server.Antag; using Content.Server.Communications; using Content.Server.GameTicking.Rules.Components; -using Content.Server.Ghost.Roles.Components; -using Content.Server.Ghost.Roles.Events; using Content.Server.Humanoid; -using Content.Server.Mind; -using Content.Server.NPC.Components; -using Content.Server.NPC.Systems; using Content.Server.Nuke; using Content.Server.NukeOps; using Content.Server.Popups; using Content.Server.Preferences.Managers; -using Content.Server.RandomMetadata; using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Shuttles.Events; using Content.Server.Shuttles.Systems; -using Content.Server.Spawners.Components; using Content.Server.Station.Components; -using Content.Server.Station.Systems; using Content.Server.Store.Components; using Content.Server.Store.Systems; -using Content.Shared.CCVar; -using Content.Shared.Dataset; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Mind.Components; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Nuke; using Content.Shared.NukeOps; using Content.Shared.Preferences; -using Content.Shared.Roles; using Content.Shared.Store; using Content.Shared.Tag; using Content.Shared.Zombies; -using Robust.Server.Player; -using Robust.Shared.Configuration; using Robust.Shared.Map; -using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Utility; using System.Linq; +using Content.Server.GameTicking.Components; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; namespace Content.Server.GameTicking.Rules; public sealed class NukeopsRuleSystem : GameRuleSystem { - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly IAdminManager _adminManager = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly ILogManager _logManager = default!; [Dependency] private readonly EmergencyShuttleSystem _emergency = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; - [Dependency] private readonly MetaDataSystem _metaData = default!; - [Dependency] private readonly RandomMetadataSystem _randomMetadata = default!; - [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly RoundEndSystem _roundEndSystem = default!; - [Dependency] private readonly SharedRoleSystem _roles = default!; - [Dependency] private readonly StationSpawningSystem _stationSpawning = default!; [Dependency] private readonly StoreSystem _store = default!; [Dependency] private readonly TagSystem _tag = default!; - [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; - - private ISawmill _sawmill = default!; [ValidatePrototypeId] private const string TelecrystalCurrencyPrototype = "Telecrystal"; @@ -79,141 +53,67 @@ public sealed class NukeopsRuleSystem : GameRuleSystem [ValidatePrototypeId] private const string NukeOpsUplinkTagPrototype = "NukeOpsUplink"; - [ValidatePrototypeId] - public const string NukeopsId = "Nukeops"; - - [ValidatePrototypeId] - private const string OperationPrefixDataset = "operationPrefix"; - - [ValidatePrototypeId] - private const string OperationSuffixDataset = "operationSuffix"; - public override void Initialize() { base.Initialize(); - _sawmill = _logManager.GetSawmill("NukeOps"); - - SubscribeLocalEvent(OnStartAttempt); - SubscribeLocalEvent(OnPlayersSpawning); - SubscribeLocalEvent(OnRoundEndText); SubscribeLocalEvent(OnNukeExploded); SubscribeLocalEvent(OnRunLevelChanged); SubscribeLocalEvent(OnNukeDisarm); SubscribeLocalEvent(OnComponentRemove); SubscribeLocalEvent(OnMobStateChanged); - SubscribeLocalEvent(OnPlayersGhostSpawning); - SubscribeLocalEvent(OnMindAdded); SubscribeLocalEvent(OnOperativeZombified); + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnShuttleFTLAttempt); SubscribeLocalEvent(OnWarDeclared); SubscribeLocalEvent(OnShuttleCallAttempt); + + SubscribeLocalEvent(OnAntagSelectEntity); + SubscribeLocalEvent(OnAfterAntagEntSelected); } protected override void Started(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { - base.Started(uid, component, gameRule, args); - - if (GameTicker.RunLevel == GameRunLevel.InRound) - SpawnOperativesForGhostRoles(uid, component); - } - - #region Event Handlers - - private void OnStartAttempt(RoundStartAttemptEvent ev) - { - TryRoundStartAttempt(ev, Loc.GetString("nukeops-title")); - } - - private void OnPlayersSpawning(RulePlayerSpawningEvent ev) - { - var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out _, out var nukeops, out _)) + var eligible = new List>(); + var eligibleQuery = EntityQueryEnumerator(); + while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member)) { - if (!SpawnMap((uid, nukeops))) - { - _sawmill.Info("Failed to load map for nukeops"); - continue; - } - - //Handle there being nobody readied up - if (ev.PlayerPool.Count == 0) + if (!_npcFaction.IsFactionHostile(component.Faction, eligibleUid, member)) continue; - var commanderEligible = _antagSelection.GetEligibleSessions(ev.PlayerPool, nukeops.CommanderSpawnDetails.AntagRoleProto); - var agentEligible = _antagSelection.GetEligibleSessions(ev.PlayerPool, nukeops.AgentSpawnDetails.AntagRoleProto); - var operativeEligible = _antagSelection.GetEligibleSessions(ev.PlayerPool, nukeops.OperativeSpawnDetails.AntagRoleProto); - //Calculate how large the nukeops team needs to be - var nukiesToSelect = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, nukeops.PlayersPerOperative, nukeops.MaxOps); - - //Select Nukies - //Select Commander, priority : commanderEligible, agentEligible, operativeEligible, all players - var selectedCommander = _antagSelection.ChooseAntags(1, commanderEligible, agentEligible, operativeEligible, ev.PlayerPool).FirstOrDefault(); - //Select Agent, priority : agentEligible, operativeEligible, all players - var selectedAgent = _antagSelection.ChooseAntags(1, agentEligible, operativeEligible, ev.PlayerPool).FirstOrDefault(); - //Select Operatives, priority : operativeEligible, all players - var selectedOperatives = _antagSelection.ChooseAntags(nukiesToSelect - 2, operativeEligible, ev.PlayerPool); - - //Create the team! - //If the session is null, they will be spawned as ghost roles (provided the cvar is set) - var operatives = new List { new NukieSpawn(selectedCommander, nukeops.CommanderSpawnDetails) }; - if (nukiesToSelect > 1) - operatives.Add(new NukieSpawn(selectedAgent, nukeops.AgentSpawnDetails)); - - for (var i = 0; i < nukiesToSelect - 2; i++) - { - //Use up all available sessions first, then spawn the rest as ghost roles (if enabled) - if (selectedOperatives.Count > i) - { - operatives.Add(new NukieSpawn(selectedOperatives[i], nukeops.OperativeSpawnDetails)); - } - else - { - operatives.Add(new NukieSpawn(null, nukeops.OperativeSpawnDetails)); - } - } - - SpawnOperatives(operatives, _cfg.GetCVar(CCVars.NukeopsSpawnGhostRoles), nukeops); + eligible.Add((eligibleUid, eligibleComp, member)); + } - foreach (var nukieSpawn in operatives) - { - if (nukieSpawn.Session == null) - continue; + if (eligible.Count == 0) + return; - GameTicker.PlayerJoinGame(nukieSpawn.Session); - } - } + component.TargetStation = RobustRandom.Pick(eligible); } - private void OnRoundEndText(RoundEndTextAppendEvent ev) + #region Event Handlers + protected override void AppendRoundEndText(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, + ref RoundEndTextAppendEvent args) { - var ruleQuery = QueryActiveRules(); - while (ruleQuery.MoveNext(out _, out _, out var nukeops, out _)) - { - var winText = Loc.GetString($"nukeops-{nukeops.WinType.ToString().ToLower()}"); - ev.AddLine(winText); + var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}"); + args.AddLine(winText); - foreach (var cond in nukeops.WinConditions) - { - var text = Loc.GetString($"nukeops-cond-{cond.ToString().ToLower()}"); - ev.AddLine(text); - } + foreach (var cond in component.WinConditions) + { + var text = Loc.GetString($"nukeops-cond-{cond.ToString().ToLower()}"); + args.AddLine(text); } - ev.AddLine(Loc.GetString("nukeops-list-start")); + args.AddLine(Loc.GetString("nukeops-list-start")); - var nukiesQuery = EntityQueryEnumerator(); - while (nukiesQuery.MoveNext(out var nukeopsUid, out _, out var mindContainer)) - { - if (!_mind.TryGetMind(nukeopsUid, out _, out var mind, mindContainer)) - continue; + var antags =_antag.GetAntagIdentifiers(uid); - ev.AddLine(mind.Session != null - ? Loc.GetString("nukeops-list-name-user", ("name", Name(nukeopsUid)), ("user", mind.Session.Name)) - : Loc.GetString("nukeops-list-name", ("name", Name(nukeopsUid)))); + foreach (var (_, sessionData, name) in antags) + { + args.AddLine(Loc.GetString("nukeops-list-name-user", ("name", name), ("user", sessionData.UserName))); } } @@ -224,10 +124,10 @@ private void OnNukeExploded(NukeExplodedEvent ev) { if (ev.OwningStation != null) { - if (ev.OwningStation == nukeops.NukieOutpost) + if (ev.OwningStation == GetOutpost(uid)) { nukeops.WinConditions.Add(WinCondition.NukeExplodedOnNukieOutpost); - SetWinType(uid, WinType.CrewMajor, nukeops); + SetWinType((uid, nukeops), WinType.CrewMajor); continue; } @@ -242,7 +142,7 @@ private void OnNukeExploded(NukeExplodedEvent ev) } nukeops.WinConditions.Add(WinCondition.NukeExplodedOnCorrectStation); - SetWinType(uid, WinType.OpsMajor, nukeops); + SetWinType((uid, nukeops), WinType.OpsMajor); correctStation = true; } @@ -263,19 +163,85 @@ private void OnNukeExploded(NukeExplodedEvent ev) private void OnRunLevelChanged(GameRunLevelChangedEvent ev) { + if (ev.New is not GameRunLevel.PostRound) + return; + var query = QueryActiveRules(); while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { - switch (ev.New) + OnRoundEnd((uid, nukeops)); + } + } + + private void OnRoundEnd(Entity ent) + { + // If the win condition was set to operative/crew major win, ignore. + if (ent.Comp.WinType == WinType.OpsMajor || ent.Comp.WinType == WinType.CrewMajor) + return; + + var nukeQuery = AllEntityQuery(); + var centcomms = _emergency.GetCentcommMaps(); + + while (nukeQuery.MoveNext(out var nuke, out var nukeTransform)) + { + if (nuke.Status != NukeStatus.ARMED) + continue; + + // UH OH + if (nukeTransform.MapUid != null && centcomms.Contains(nukeTransform.MapUid.Value)) { - case GameRunLevel.InRound: - OnRoundStart(uid, nukeops); - break; - case GameRunLevel.PostRound: - OnRoundEnd(uid, nukeops); - break; + ent.Comp.WinConditions.Add(WinCondition.NukeActiveAtCentCom); + SetWinType((ent, ent), WinType.OpsMajor); + return; } + + if (nukeTransform.GridUid == null || ent.Comp.TargetStation == null) + continue; + + if (!TryComp(ent.Comp.TargetStation.Value, out StationDataComponent? data)) + continue; + + foreach (var grid in data.Grids) + { + if (grid != nukeTransform.GridUid) + continue; + + ent.Comp.WinConditions.Add(WinCondition.NukeActiveInStation); + SetWinType(ent, WinType.OpsMajor); + return; + } + } + + if (_antag.AllAntagsAlive(ent.Owner)) + { + SetWinType(ent, WinType.OpsMinor); + ent.Comp.WinConditions.Add(WinCondition.AllNukiesAlive); + return; } + + ent.Comp.WinConditions.Add(_antag.AnyAliveAntags(ent.Owner) + ? WinCondition.SomeNukiesAlive + : WinCondition.AllNukiesDead); + + var diskAtCentCom = false; + var diskQuery = AllEntityQuery(); + while (diskQuery.MoveNext(out _, out var transform)) + { + diskAtCentCom = transform.MapUid != null && centcomms.Contains(transform.MapUid.Value); + + // TODO: The target station should be stored, and the nuke disk should store its original station. + // This is fine for now, because we can assume a single station in base SS14. + break; + } + + // If the disk is currently at Central Command, the crew wins - just slightly. + // This also implies that some nuclear operatives have died. + SetWinType(ent, diskAtCentCom + ? WinType.CrewMinor + : WinType.OpsMinor); + ent.Comp.WinConditions.Add(diskAtCentCom + ? WinCondition.NukeDiskOnCentCom + : WinCondition.NukeDiskNotOnCentCom); } private void OnNukeDisarm(NukeDisarmSuccessEvent ev) @@ -294,66 +260,31 @@ private void OnMobStateChanged(EntityUid uid, NukeOperativeComponent component, CheckRoundShouldEnd(); } - private void OnPlayersGhostSpawning(EntityUid uid, NukeOperativeComponent component, GhostRoleSpawnerUsedEvent args) + private void OnOperativeZombified(EntityUid uid, NukeOperativeComponent component, ref EntityZombifiedEvent args) { - var spawner = args.Spawner; - - if (!TryComp(spawner, out var nukeOpSpawner)) - return; - - HumanoidCharacterProfile? profile = null; - if (TryComp(args.Spawned, out ActorComponent? actor)) - profile = _prefs.GetPreferences(actor.PlayerSession.UserId).SelectedCharacter as HumanoidCharacterProfile; - - // TODO: this is kinda awful for multi-nukies - foreach (var nukeops in EntityQuery()) - { - SetupOperativeEntity(uid, nukeOpSpawner.OperativeName, nukeOpSpawner.SpawnDetails, profile); - - nukeops.OperativeMindPendingData.Add(uid, nukeOpSpawner.SpawnDetails.AntagRoleProto); - } + RemCompDeferred(uid, component); } - private void OnMindAdded(EntityUid uid, NukeOperativeComponent component, MindAddedMessage args) + private void OnMapInit(Entity ent, ref MapInitEvent args) { - if (!_mind.TryGetMind(uid, out var mindId, out var mind)) - return; + var map = Transform(ent).MapID; - var query = QueryActiveRules(); - while (query.MoveNext(out _, out _, out var nukeops, out _)) + var rules = EntityQueryEnumerator(); + while (rules.MoveNext(out var uid, out _, out var mapRule)) { - if (nukeops.OperativeMindPendingData.TryGetValue(uid, out var role) || !nukeops.SpawnOutpost || - nukeops.RoundEndBehavior == RoundEndBehavior.Nothing) - { - role ??= nukeops.OperativeSpawnDetails.AntagRoleProto; - _roles.MindAddRole(mindId, new NukeopsRoleComponent { PrototypeId = role }); - nukeops.OperativeMindPendingData.Remove(uid); - } - - if (mind.Session is not { } playerSession) - return; - - if (GameTicker.RunLevel != GameRunLevel.InRound) - return; - - if (nukeops.TargetStation != null && !string.IsNullOrEmpty(Name(nukeops.TargetStation.Value))) - { - NotifyNukie(playerSession, component, nukeops); - } + if (map != mapRule.Map) + continue; + ent.Comp.AssociatedRule = uid; + break; } } - private void OnOperativeZombified(EntityUid uid, NukeOperativeComponent component, ref EntityZombifiedEvent args) - { - RemCompDeferred(uid, component); - } - private void OnShuttleFTLAttempt(ref ConsoleFTLAttemptEvent ev) { var query = QueryActiveRules(); - while (query.MoveNext(out _, out _, out var nukeops, out _)) + while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { - if (ev.Uid != nukeops.NukieShuttle) + if (ev.Uid != GetShuttle((uid, nukeops))) continue; if (nukeops.WarDeclaredTime != null) @@ -397,12 +328,12 @@ private void OnWarDeclared(ref WarDeclaredEvent ev) { // TODO: this is VERY awful for multi-nukies var query = QueryActiveRules(); - while (query.MoveNext(out _, out _, out var nukeops, out _)) + while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { if (nukeops.WarDeclaredTime != null) continue; - if (Transform(ev.DeclaratorEntity).MapID != nukeops.NukiePlanet) + if (TryComp(uid, out var mapComp) && Transform(ev.DeclaratorEntity).MapID != mapComp.Map) continue; var newStatus = GetWarCondition(nukeops, ev.Status); @@ -413,7 +344,7 @@ private void OnWarDeclared(ref WarDeclaredEvent ev) var timeRemain = nukeops.WarNukieArriveDelay + Timing.CurTime; ev.DeclaratorEntity.Comp.ShuttleDisabledTime = timeRemain; - DistributeExtraTc(nukeops); + DistributeExtraTc((uid, nukeops)); } } } @@ -440,7 +371,7 @@ public WarConditionStatus GetWarCondition(NukeopsRuleComponent nukieRule, WarCon return WarConditionStatus.YesWar; } - private void DistributeExtraTc(NukeopsRuleComponent nukieRule) + private void DistributeExtraTc(Entity nukieRule) { var enumerator = EntityQueryEnumerator(); while (enumerator.MoveNext(out var uid, out var component)) @@ -448,161 +379,22 @@ private void DistributeExtraTc(NukeopsRuleComponent nukieRule) if (!_tag.HasTag(uid, NukeOpsUplinkTagPrototype)) continue; - if (!nukieRule.NukieOutpost.HasValue) + if (GetOutpost(nukieRule.Owner) is not { } outpost) continue; - if (Transform(uid).MapID != Transform(nukieRule.NukieOutpost.Value).MapID) // Will receive bonus TC only on their start outpost + if (Transform(uid).MapID != Transform(outpost).MapID) // Will receive bonus TC only on their start outpost continue; - _store.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.WarTCAmountPerNukie } }, uid, component); + _store.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.Comp.WarTcAmountPerNukie } }, uid, component); var msg = Loc.GetString("store-currency-war-boost-given", ("target", uid)); _popupSystem.PopupEntity(msg, uid); } } - private void OnRoundStart(EntityUid uid, NukeopsRuleComponent? component = null) + private void SetWinType(Entity ent, WinType type, bool endRound = true) { - if (!Resolve(uid, ref component)) - return; - - // TODO: This needs to try and target a Nanotrasen station. At the very least, - // we can only currently guarantee that NT stations are the only station to - // exist in the base game. - - var eligible = new List>(); - var eligibleQuery = EntityQueryEnumerator(); - while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member)) - { - if (!_npcFaction.IsFactionHostile(component.Faction, eligibleUid, member)) - continue; - - eligible.Add((eligibleUid, eligibleComp, member)); - } - - if (eligible.Count == 0) - return; - - component.TargetStation = RobustRandom.Pick(eligible); - component.OperationName = _randomMetadata.GetRandomFromSegments([OperationPrefixDataset, OperationSuffixDataset], " "); - - var filter = Filter.Empty(); - var query = EntityQueryEnumerator(); - while (query.MoveNext(out _, out var nukeops, out var actor)) - { - NotifyNukie(actor.PlayerSession, nukeops, component); - filter.AddPlayer(actor.PlayerSession); - } - } - - private void OnRoundEnd(EntityUid uid, NukeopsRuleComponent? component = null) - { - if (!Resolve(uid, ref component)) - return; - - // If the win condition was set to operative/crew major win, ignore. - if (component.WinType == WinType.OpsMajor || component.WinType == WinType.CrewMajor) - return; - - var nukeQuery = AllEntityQuery(); - var centcomms = _emergency.GetCentcommMaps(); - - while (nukeQuery.MoveNext(out var nuke, out var nukeTransform)) - { - if (nuke.Status != NukeStatus.ARMED) - continue; - - // UH OH - if (nukeTransform.MapUid != null && centcomms.Contains(nukeTransform.MapUid.Value)) - { - component.WinConditions.Add(WinCondition.NukeActiveAtCentCom); - SetWinType(uid, WinType.OpsMajor, component); - return; - } - - if (nukeTransform.GridUid == null || component.TargetStation == null) - continue; - - if (!TryComp(component.TargetStation.Value, out StationDataComponent? data)) - continue; - - foreach (var grid in data.Grids) - { - if (grid != nukeTransform.GridUid) - continue; - - component.WinConditions.Add(WinCondition.NukeActiveInStation); - SetWinType(uid, WinType.OpsMajor, component); - return; - } - } - - var allAlive = true; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var nukeopsUid, out _, out var mindContainer, out var mobState)) - { - // mind got deleted somehow so ignore it - if (!_mind.TryGetMind(nukeopsUid, out _, out var mind, mindContainer)) - continue; - - // check if player got gibbed or ghosted or something - count as dead - if (mind.OwnedEntity != null && - // if the player somehow isn't a mob anymore that also counts as dead - // have to be alive, not crit or dead - mobState.CurrentState is MobState.Alive) - { - continue; - } - - allAlive = false; - break; - } - - // If all nuke ops were alive at the end of the round, - // the nuke ops win. This is to prevent people from - // running away the moment nuke ops appear. - if (allAlive) - { - SetWinType(uid, WinType.OpsMinor, component); - component.WinConditions.Add(WinCondition.AllNukiesAlive); - return; - } - - component.WinConditions.Add(WinCondition.SomeNukiesAlive); - - var diskAtCentCom = false; - var diskQuery = AllEntityQuery(); - - while (diskQuery.MoveNext(out _, out var transform)) - { - diskAtCentCom = transform.MapUid != null && centcomms.Contains(transform.MapUid.Value); - - // TODO: The target station should be stored, and the nuke disk should store its original station. - // This is fine for now, because we can assume a single station in base SS14. - break; - } - - // If the disk is currently at Central Command, the crew wins - just slightly. - // This also implies that some nuclear operatives have died. - if (diskAtCentCom) - { - SetWinType(uid, WinType.CrewMinor, component); - component.WinConditions.Add(WinCondition.NukeDiskOnCentCom); - } - // Otherwise, the nuke ops win. - else - { - SetWinType(uid, WinType.OpsMinor, component); - component.WinConditions.Add(WinCondition.NukeDiskNotOnCentCom); - } - } - - private void SetWinType(EntityUid uid, WinType type, NukeopsRuleComponent? component = null, bool endRound = true) - { - if (!Resolve(uid, ref component)) - return; - - component.WinType = type; + ent.Comp.WinType = type; if (endRound && (type == WinType.CrewMajor || type == WinType.OpsMajor)) _roundEndSystem.EndRound(); @@ -613,243 +405,130 @@ private void CheckRoundShouldEnd() var query = QueryActiveRules(); while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { - if (nukeops.RoundEndBehavior == RoundEndBehavior.Nothing || nukeops.WinType == WinType.CrewMajor || nukeops.WinType == WinType.OpsMajor) - continue; - - // If there are any nuclear bombs that are active, immediately return. We're not over yet. - var armed = false; - foreach (var nuke in EntityQuery()) - { - if (nuke.Status == NukeStatus.ARMED) - { - armed = true; - break; - } - } - if (armed) - continue; - - MapId? shuttleMapId = Exists(nukeops.NukieShuttle) - ? Transform(nukeops.NukieShuttle.Value).MapID - : null; - - MapId? targetStationMap = null; - if (nukeops.TargetStation != null && TryComp(nukeops.TargetStation, out StationDataComponent? data)) - { - var grid = data.Grids.FirstOrNull(); - targetStationMap = grid != null - ? Transform(grid.Value).MapID - : null; - } - - // Check if there are nuke operatives still alive on the same map as the shuttle, - // or on the same map as the station. - // If there are, the round can continue. - var operatives = EntityQuery(true); - var operativesAlive = operatives - .Where(ent => - ent.Item3.MapID == shuttleMapId - || ent.Item3.MapID == targetStationMap) - .Any(ent => ent.Item2.CurrentState == MobState.Alive && ent.Item1.Running); - - if (operativesAlive) - continue; // There are living operatives than can access the shuttle, or are still on the station's map. - - // Check that there are spawns available and that they can access the shuttle. - var spawnsAvailable = EntityQuery(true).Any(); - if (spawnsAvailable && shuttleMapId == nukeops.NukiePlanet) - continue; // Ghost spawns can still access the shuttle. Continue the round. - - // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives, - // and there are no nuclear operatives on the target station's map. - nukeops.WinConditions.Add(spawnsAvailable - ? WinCondition.NukiesAbandoned - : WinCondition.AllNukiesDead); - - SetWinType(uid, WinType.CrewMajor, nukeops, false); - _roundEndSystem.DoRoundEndBehavior( - nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement); - - // prevent it called multiple times - nukeops.RoundEndBehavior = RoundEndBehavior.Nothing; - } - } - - private bool SpawnMap(Entity ent) - { - if (!ent.Comp.SpawnOutpost - || ent.Comp.NukiePlanet != null) - return true; - - ent.Comp.NukiePlanet = _mapManager.CreateMap(); - var gameMap = _prototypeManager.Index(ent.Comp.OutpostMapPrototype); - ent.Comp.NukieOutpost = GameTicker.LoadGameMap(gameMap, ent.Comp.NukiePlanet.Value, null)[0]; - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var grid, out _, out var shuttleTransform)) - { - if (shuttleTransform.MapID != ent.Comp.NukiePlanet) - continue; - - ent.Comp.NukieShuttle = grid; - break; + CheckRoundShouldEnd((uid, nukeops)); } - - return true; } - /// - /// Adds missing nuke operative components, equips starting gear and renames the entity. - /// - private void SetupOperativeEntity(EntityUid mob, string name, NukeopSpawnPreset spawnDetails, HumanoidCharacterProfile? profile) + private void CheckRoundShouldEnd(Entity ent) { - _metaData.SetEntityName(mob, name); - EnsureComp(mob); - - if (profile != null) - _humanoid.LoadProfile(mob, profile); - - var gear = _prototypeManager.Index(spawnDetails.GearProto); - _stationSpawning.EquipStartingGear(mob, gear, profile); + var nukeops = ent.Comp; - _npcFaction.RemoveFaction(mob, "NanoTrasen", false); - _npcFaction.AddFaction(mob, "Syndicate"); - } - - private void SpawnOperatives(List sessions, bool spawnGhostRoles, NukeopsRuleComponent component) - { - if (component.NukieOutpost is not { Valid: true } outpostUid) + if (nukeops.RoundEndBehavior == RoundEndBehavior.Nothing || nukeops.WinType == WinType.CrewMajor || nukeops.WinType == WinType.OpsMajor) return; - var spawns = new List(); - foreach (var (_, meta, xform) in EntityQuery(true)) - { - if (meta.EntityPrototype?.ID != component.SpawnPointProto.Id) - continue; - - if (xform.ParentUid != component.NukieOutpost) - continue; - - spawns.Add(xform.Coordinates); - break; - } - //Fallback, spawn at the centre of the map - if (spawns.Count == 0) + // If there are any nuclear bombs that are active, immediately return. We're not over yet. + foreach (var nuke in EntityQuery()) { - spawns.Add(Transform(outpostUid).Coordinates); - _sawmill.Warning($"Fell back to default spawn for nukies!"); + if (nuke.Status == NukeStatus.ARMED) + return; } - //Spawn the team - foreach (var nukieSession in sessions) - { - var name = $"{Loc.GetString(nukieSession.Type.NamePrefix)} {RobustRandom.PickAndTake(_prototypeManager.Index(nukieSession.Type.NameList).Values.ToList())}"; + var shuttle = GetShuttle((ent, ent)); - var nukeOpsAntag = _prototypeManager.Index(nukieSession.Type.AntagRoleProto); + MapId? shuttleMapId = Exists(shuttle) + ? Transform(shuttle.Value).MapID + : null; - //If a session is available, spawn mob and transfer mind into it - if (nukieSession.Session != null) - { - var profile = _prefs.GetPreferences(nukieSession.Session.UserId).SelectedCharacter as HumanoidCharacterProfile; - if (!_prototypeManager.TryIndex(profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species)) - { - species = _prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); - } - - var mob = Spawn(species.Prototype, RobustRandom.Pick(spawns)); - SetupOperativeEntity(mob, name, nukieSession.Type, profile); - - var newMind = _mind.CreateMind(nukieSession.Session.UserId, name); - _mind.SetUserId(newMind, nukieSession.Session.UserId); - _roles.MindAddRole(newMind, new NukeopsRoleComponent { PrototypeId = nukieSession.Type.AntagRoleProto }); - - _mind.TransferTo(newMind, mob); - } - //Otherwise, spawn as a ghost role - else if (spawnGhostRoles) - { - var spawnPoint = Spawn(component.GhostSpawnPointProto, RobustRandom.Pick(spawns)); - var ghostRole = EnsureComp(spawnPoint); - EnsureComp(spawnPoint); - ghostRole.RoleName = Loc.GetString(nukeOpsAntag.Name); - ghostRole.RoleDescription = Loc.GetString(nukeOpsAntag.Objective); - - var nukeOpSpawner = EnsureComp(spawnPoint); - nukeOpSpawner.OperativeName = name; - nukeOpSpawner.SpawnDetails = nukieSession.Type; - } + MapId? targetStationMap = null; + if (nukeops.TargetStation != null && TryComp(nukeops.TargetStation, out StationDataComponent? data)) + { + var grid = data.Grids.FirstOrNull(); + targetStationMap = grid != null + ? Transform(grid.Value).MapID + : null; } - } - /// - /// Display a greeting message and play a sound for a nukie - /// - private void NotifyNukie(ICommonSession session, NukeOperativeComponent nukeop, NukeopsRuleComponent nukeopsRule) - { - if (nukeopsRule.TargetStation is not { } station) - return; - - _antagSelection.SendBriefing(session, Loc.GetString("nukeops-welcome", ("station", station), ("name", nukeopsRule.OperationName)), Color.Red, nukeop.GreetSoundNotification); + // Check if there are nuke operatives still alive on the same map as the shuttle, + // or on the same map as the station. + // If there are, the round can continue. + var operatives = EntityQuery(true); + var operativesAlive = operatives + .Where(op => + op.Item3.MapID == shuttleMapId + || op.Item3.MapID == targetStationMap) + .Any(op => op.Item2.CurrentState == MobState.Alive && op.Item1.Running); + + if (operativesAlive) + return; // There are living operatives than can access the shuttle, or are still on the station's map. + + // Check that there are spawns available and that they can access the shuttle. + var spawnsAvailable = EntityQuery(true).Any(); + if (spawnsAvailable && CompOrNull(ent)?.Map == shuttleMapId) + return; // Ghost spawns can still access the shuttle. Continue the round. + + // The shuttle is inaccessible to both living nuke operatives and yet to spawn nuke operatives, + // and there are no nuclear operatives on the target station's map. + nukeops.WinConditions.Add(spawnsAvailable + ? WinCondition.NukiesAbandoned + : WinCondition.AllNukiesDead); + + SetWinType(ent, WinType.CrewMajor, false); + _roundEndSystem.DoRoundEndBehavior( + nukeops.RoundEndBehavior, nukeops.EvacShuttleTime, nukeops.RoundEndTextSender, nukeops.RoundEndTextShuttleCall, nukeops.RoundEndTextAnnouncement); + + // prevent it called multiple times + nukeops.RoundEndBehavior = RoundEndBehavior.Nothing; } - /// - /// Spawn nukie ghost roles if this gamerule was started mid round - /// - private void SpawnOperativesForGhostRoles(EntityUid uid, NukeopsRuleComponent? component = null) + // this should really go anywhere else but im tired. + private void OnAntagSelectEntity(Entity ent, ref AntagSelectEntityEvent args) { - if (!Resolve(uid, ref component)) + if (args.Handled) return; - if (!SpawnMap((uid, component))) + var profile = args.Session != null + ? _prefs.GetPreferences(args.Session.UserId).SelectedCharacter as HumanoidCharacterProfile + : HumanoidCharacterProfile.RandomWithSpecies(); + if (!_prototypeManager.TryIndex(profile?.Species ?? SharedHumanoidAppearanceSystem.DefaultSpecies, out SpeciesPrototype? species)) { - _sawmill.Info("Failed to load map for nukeops"); - return; + species = _prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); } - var numNukies = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, component.PlayersPerOperative, component.MaxOps); + args.Entity = Spawn(species.Prototype); + _humanoid.LoadProfile(args.Entity.Value, profile); + } - //Dont continue if we have no nukies to spawn - if (numNukies == 0) + private void OnAfterAntagEntSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) + { + if (ent.Comp.TargetStation is not { } station) return; - //Fill the ranks, commander first, then agent, then operatives - //TODO: Possible alternative team compositions? Like multiple commanders or agents - var operatives = new List(); - if (numNukies >= 1) - operatives.Add(new NukieSpawn(null, component.CommanderSpawnDetails)); - if (numNukies >= 2) - operatives.Add(new NukieSpawn(null, component.AgentSpawnDetails)); - if (numNukies >= 3) - { - for (var i = 2; i < numNukies; i++) - { - operatives.Add(new NukieSpawn(null, component.OperativeSpawnDetails)); - } - } - - SpawnOperatives(operatives, true, component); + _antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome", + ("station", station), + ("name", Name(ent))), + Color.Red, + ent.Comp.GreetSoundNotification); } - //For admins forcing someone to nukeOps. - public void MakeLoneNukie(EntityUid entity) + /// + /// Is this method the shitty glue holding together the last of my sanity? yes. + /// Do i have a better solution? not presently. + /// + private EntityUid? GetOutpost(Entity ent) { - if (!_mind.TryGetMind(entity, out var mindId, out var mindComponent)) - return; + if (!Resolve(ent, ref ent.Comp, false)) + return null; - //ok hardcoded value bad but so is everything else here - _roles.MindAddRole(mindId, new NukeopsRoleComponent { PrototypeId = NukeopsId }, mindComponent); - SetOutfitCommand.SetOutfit(entity, "SyndicateOperativeGearFull", EntityManager); + return ent.Comp.MapGrids.Where(e => HasComp(e) && !HasComp(e)).FirstOrNull(); } - private sealed class NukieSpawn + /// + /// Is this method the shitty glue holding together the last of my sanity? yes. + /// Do i have a better solution? not presently. + /// + private EntityUid? GetShuttle(Entity ent) { - public ICommonSession? Session { get; private set; } - public NukeopSpawnPreset Type { get; private set; } + if (!Resolve(ent, ref ent.Comp, false)) + return null; - public NukieSpawn(ICommonSession? session, NukeopSpawnPreset type) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) { - Session = session; - Type = type; + if (comp.AssociatedRule == ent.Owner) + return uid; } + + return null; } } diff --git a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs index 128f112304..e69de29bb2 100644 --- a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs @@ -1,321 +0,0 @@ -using System.Linq; -using System.Numerics; -using Content.Server.Administration.Commands; -using Content.Server.Cargo.Systems; -using Content.Server.Chat.Managers; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.NPC.Components; -using Content.Server.NPC.Systems; -using Content.Server.Preferences.Managers; -using Content.Server.Spawners.Components; -using Content.Server.Station.Components; -using Content.Server.Station.Systems; -using Content.Shared.CCVar; -using Content.Shared.Humanoid; -using Content.Shared.Humanoid.Prototypes; -using Content.Shared.Mind; -using Content.Shared.Preferences; -using Content.Shared.Roles; -using Robust.Server.GameObjects; -using Robust.Server.Maps; -using Robust.Server.Player; -using Robust.Shared.Audio; -using Robust.Shared.Audio.Systems; -using Robust.Shared.Configuration; -using Robust.Shared.Enums; -using Robust.Shared.Map; -using Robust.Shared.Map.Components; -using Robust.Shared.Player; -using Robust.Shared.Prototypes; -using Robust.Shared.Random; -using Robust.Shared.Utility; - -namespace Content.Server.GameTicking.Rules; - -/// -/// This handles the Pirates minor antag, which is designed to coincide with other modes on occasion. -/// -public sealed class PiratesRuleSystem : GameRuleSystem -{ - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IChatManager _chatManager = default!; - [Dependency] private readonly IMapManager _mapManager = default!; - [Dependency] private readonly IServerPreferencesManager _prefs = default!; - [Dependency] private readonly StationSpawningSystem _stationSpawningSystem = default!; - [Dependency] private readonly PricingSystem _pricingSystem = default!; - [Dependency] private readonly MapLoaderSystem _map = default!; - [Dependency] private readonly NamingSystem _namingSystem = default!; - [Dependency] private readonly NpcFactionSystem _npcFaction = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; - [Dependency] private readonly SharedAudioSystem _audioSystem = default!; - [Dependency] private readonly MetaDataSystem _metaData = default!; - - [ValidatePrototypeId] - private const string GameRuleId = "Pirates"; - - [ValidatePrototypeId] - private const string MobId = "MobHuman"; - - [ValidatePrototypeId] - private const string SpeciesId = "Human"; - - [ValidatePrototypeId] - private const string PirateFactionId = "Syndicate"; - - [ValidatePrototypeId] - private const string EnemyFactionId = "NanoTrasen"; - - [ValidatePrototypeId] - private const string GearId = "PirateGear"; - - [ValidatePrototypeId] - private const string SpawnPointId = "SpawnPointPirates"; - - /// - public override void Initialize() - { - base.Initialize(); - - SubscribeLocalEvent(OnPlayerSpawningEvent); - SubscribeLocalEvent(OnRoundEndTextEvent); - SubscribeLocalEvent(OnStartAttempt); - } - - private void OnRoundEndTextEvent(RoundEndTextAppendEvent ev) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var pirates, out var gameRule)) - { - if (Deleted(pirates.PirateShip)) - { - // Major loss, the ship somehow got annihilated. - ev.AddLine(Loc.GetString("pirates-no-ship")); - } - else - { - List<(double, EntityUid)> mostValuableThefts = new(); - - var comp1 = pirates; - var finalValue = _pricingSystem.AppraiseGrid(pirates.PirateShip, uid => - { - foreach (var mindId in comp1.Pirates) - { - if (TryComp(mindId, out MindComponent? mind) && mind.CurrentEntity == uid) - return false; // Don't appraise the pirates twice, we count them in separately. - } - - return true; - }, (uid, price) => - { - if (comp1.InitialItems.Contains(uid)) - return; - - mostValuableThefts.Add((price, uid)); - mostValuableThefts.Sort((i1, i2) => i2.Item1.CompareTo(i1.Item1)); - if (mostValuableThefts.Count > 5) - mostValuableThefts.Pop(); - }); - - foreach (var mindId in pirates.Pirates) - { - if (TryComp(mindId, out MindComponent? mind) && mind.CurrentEntity is not null) - finalValue += _pricingSystem.GetPrice(mind.CurrentEntity.Value); - } - - var score = finalValue - pirates.InitialShipValue; - - ev.AddLine(Loc.GetString("pirates-final-score", ("score", $"{score:F2}"))); - ev.AddLine(Loc.GetString("pirates-final-score-2", ("finalPrice", $"{finalValue:F2}"))); - - ev.AddLine(""); - ev.AddLine(Loc.GetString("pirates-most-valuable")); - - foreach (var (price, obj) in mostValuableThefts) - { - ev.AddLine(Loc.GetString("pirates-stolen-item-entry", ("entity", obj), ("credits", $"{price:F2}"))); - } - - if (mostValuableThefts.Count == 0) - ev.AddLine(Loc.GetString("pirates-stole-nothing")); - } - - ev.AddLine(""); - ev.AddLine(Loc.GetString("pirates-list-start")); - foreach (var pirate in pirates.Pirates) - { - if (TryComp(pirate, out MindComponent? mind)) - { - ev.AddLine($"- {mind.CharacterName} ({mind.Session?.Name})"); - } - } - } - } - - private void OnPlayerSpawningEvent(RulePlayerSpawningEvent ev) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var pirates, out var gameRule)) - { - // Forgive me for copy-pasting nukies. - if (!GameTicker.IsGameRuleAdded(uid, gameRule)) - return; - - pirates.Pirates.Clear(); - pirates.InitialItems.Clear(); - - // Between 1 and : needs at least n players per op. - var numOps = Math.Max(1, - (int) Math.Min( - Math.Floor((double) ev.PlayerPool.Count / _cfg.GetCVar(CCVars.PiratesPlayersPerOp)), - _cfg.GetCVar(CCVars.PiratesMaxOps))); - var ops = new ICommonSession[numOps]; - for (var i = 0; i < numOps; i++) - { - ops[i] = _random.PickAndTake(ev.PlayerPool); - } - - var map = "/Maps/Shuttles/pirate.yml"; - var xformQuery = GetEntityQuery(); - - var aabbs = EntityQuery().SelectMany(x => - x.Grids.Select(x => - xformQuery.GetComponent(x).WorldMatrix.TransformBox(Comp(x).LocalAABB))) - .ToArray(); - - var aabb = aabbs[0]; - - for (var i = 1; i < aabbs.Length; i++) - { - aabb.Union(aabbs[i]); - } - - // (Not commented?) - var a = MathF.Max(aabb.Height / 2f, aabb.Width / 2f) * 2.5f; - - var gridId = _map.LoadGrid(GameTicker.DefaultMap, map, new MapLoadOptions - { - Offset = aabb.Center + new Vector2(a, a), - LoadMap = false, - }); - - if (!gridId.HasValue) - { - Log.Error($"Gridid was null when loading \"{map}\", aborting."); - foreach (var session in ops) - { - ev.PlayerPool.Add(session); - } - - return; - } - - pirates.PirateShip = gridId.Value; - - // TODO: Loot table or something - var pirateGear = _prototypeManager.Index(GearId); // YARRR - - var spawns = new List(); - - // Forgive me for hardcoding prototypes - foreach (var (_, meta, xform) in - EntityQuery(true)) - { - if (meta.EntityPrototype?.ID != SpawnPointId || xform.ParentUid != pirates.PirateShip) - continue; - - spawns.Add(xform.Coordinates); - } - - if (spawns.Count == 0) - { - spawns.Add(Transform(pirates.PirateShip).Coordinates); - Log.Warning($"Fell back to default spawn for pirates!"); - } - - for (var i = 0; i < ops.Length; i++) - { - var sex = _random.Prob(0.5f) ? Sex.Male : Sex.Female; - var gender = sex == Sex.Male ? Gender.Male : Gender.Female; - - var name = _namingSystem.GetName(SpeciesId, gender); - - var session = ops[i]; - var newMind = _mindSystem.CreateMind(session.UserId, name); - _mindSystem.SetUserId(newMind, session.UserId); - - var mob = Spawn(MobId, _random.Pick(spawns)); - _metaData.SetEntityName(mob, name); - - _mindSystem.TransferTo(newMind, mob); - var profile = _prefs.GetPreferences(session.UserId).SelectedCharacter as HumanoidCharacterProfile; - _stationSpawningSystem.EquipStartingGear(mob, pirateGear, profile); - - _npcFaction.RemoveFaction(mob, EnemyFactionId, false); - _npcFaction.AddFaction(mob, PirateFactionId); - - pirates.Pirates.Add(newMind); - - // Notificate every player about a pirate antagonist role with sound - _audioSystem.PlayGlobal(pirates.PirateAlertSound, session); - - GameTicker.PlayerJoinGame(session); - } - - pirates.InitialShipValue = _pricingSystem.AppraiseGrid(pirates.PirateShip, uid => - { - pirates.InitialItems.Add(uid); - return true; - }); // Include the players in the appraisal. - } - } - - //Forcing one player to be a pirate. - public void MakePirate(EntityUid entity) - { - if (!_mindSystem.TryGetMind(entity, out var mindId, out var mind)) - return; - - SetOutfitCommand.SetOutfit(entity, GearId, EntityManager); - - var pirateRule = EntityQuery().FirstOrDefault(); - if (pirateRule == null) - { - //todo fuck me this shit is awful - GameTicker.StartGameRule(GameRuleId, out var ruleEntity); - pirateRule = Comp(ruleEntity); - } - - // Notificate every player about a pirate antagonist role with sound - if (mind.Session != null) - { - _audioSystem.PlayGlobal(pirateRule.PirateAlertSound, mind.Session); - } - } - - private void OnStartAttempt(RoundStartAttemptEvent ev) - { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var pirates, out var gameRule)) - { - if (!GameTicker.IsGameRuleActive(uid, gameRule)) - return; - - var minPlayers = _cfg.GetCVar(CCVars.PiratesMinPlayers); - if (!ev.Forced && ev.Players.Length < minPlayers) - { - _chatManager.SendAdminAnnouncement(Loc.GetString("nukeops-not-enough-ready-players", - ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers))); - ev.Cancel(); - return; - } - - if (ev.Players.Length == 0) - { - _chatManager.DispatchServerAnnouncement(Loc.GetString("nukeops-no-one-ready")); - ev.Cancel(); - } - } - } -} diff --git a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs index b11c28fb2b..5215da96aa 100644 --- a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Chat.Managers; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Systems; using Content.Shared.Chat; diff --git a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs index d20775c734..7e6901e6c4 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -16,7 +16,6 @@ using Content.Shared.Database; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; -using Content.Shared.Inventory; using Content.Shared.Mind; using Content.Shared.Mind.Components; using Content.Shared.Mindshield.Components; @@ -24,12 +23,11 @@ using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; using Content.Shared.Revolutionary.Components; -using Content.Shared.Roles; using Content.Shared.Stunnable; using Content.Shared.Zombies; using Robust.Shared.Prototypes; using Robust.Shared.Timing; -using System.Linq; +using Content.Server.GameTicking.Components; namespace Content.Server.GameTicking.Rules; @@ -40,7 +38,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem RevolutionaryNpcFaction = "Revolutionary"; @@ -60,23 +57,12 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem(OnStartAttempt); - SubscribeLocalEvent(OnPlayerJobAssigned); SubscribeLocalEvent(OnCommandMobStateChanged); SubscribeLocalEvent(OnHeadRevMobStateChanged); - SubscribeLocalEvent(OnRoundEndText); SubscribeLocalEvent(OnGetBriefing); SubscribeLocalEvent(OnPostFlash); } - //Set miniumum players - protected override void Added(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) - { - base.Added(uid, component, gameRule, args); - - gameRule.MinPlayers = component.MinPlayers; - } - protected override void Started(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { base.Started(uid, component, gameRule, args); @@ -98,40 +84,29 @@ protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent com } } - private void OnRoundEndText(RoundEndTextAppendEvent ev) + protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, + ref RoundEndTextAppendEvent args) { + base.AppendRoundEndText(uid, component, gameRule, ref args); + var revsLost = CheckRevsLose(); var commandLost = CheckCommandLose(); - var query = AllEntityQuery(); - while (query.MoveNext(out var headrev)) + // This is (revsLost, commandsLost) concatted together + // (moony wrote this comment idk what it means) + var index = (commandLost ? 1 : 0) | (revsLost ? 2 : 0); + args.AddLine(Loc.GetString(Outcomes[index])); + + var sessionData = _antag.GetAntagIdentifiers(uid); + args.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", sessionData.Count))); + foreach (var (mind, data, name) in sessionData) { - // This is (revsLost, commandsLost) concatted together - // (moony wrote this comment idk what it means) - var index = (commandLost ? 1 : 0) | (revsLost ? 2 : 0); - ev.AddLine(Loc.GetString(Outcomes[index])); - - ev.AddLine(Loc.GetString("rev-headrev-count", ("initialCount", headrev.HeadRevs.Count))); - foreach (var player in headrev.HeadRevs) - { - // TODO: when role entities are a thing this has to change - var count = CompOrNull(player.Value)?.ConvertedCount ?? 0; - - _mind.TryGetSession(player.Value, out var session); - var username = session?.Name; - if (username != null) - { - ev.AddLine(Loc.GetString("rev-headrev-name-user", - ("name", player.Key), - ("username", username), ("count", count))); - } - else - { - ev.AddLine(Loc.GetString("rev-headrev-name", - ("name", player.Key), ("count", count))); - } + var count = CompOrNull(mind)?.ConvertedCount ?? 0; + args.AddLine(Loc.GetString("rev-headrev-name-user", + ("name", name), + ("username", data.UserName), + ("count", count))); - // TODO: someone suggested listing all alive? revs maybe implement at some point - } + // TODO: someone suggested listing all alive? revs maybe implement at some point } } @@ -144,57 +119,6 @@ private void OnGetBriefing(EntityUid uid, RevolutionaryRoleComponent comp, ref G args.Append(Loc.GetString(head ? "head-rev-briefing" : "rev-briefing")); } - //Check for enough players to start rule - private void OnStartAttempt(RoundStartAttemptEvent ev) - { - TryRoundStartAttempt(ev, Loc.GetString("roles-antag-rev-name")); - } - - private void OnPlayerJobAssigned(RulePlayerJobsAssignedEvent ev) - { - var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out var activeGameRule, out var comp, out var gameRule)) - { - var eligiblePlayers = _antagSelection.GetEligiblePlayers(ev.Players, comp.HeadRevPrototypeId); - - if (eligiblePlayers.Count == 0) - continue; - - var headRevCount = _antagSelection.CalculateAntagCount(ev.Players.Length, comp.PlayersPerHeadRev, comp.MaxHeadRevs); - - var headRevs = _antagSelection.ChooseAntags(headRevCount, eligiblePlayers); - - GiveHeadRev(headRevs, comp.HeadRevPrototypeId, comp); - } - } - - private void GiveHeadRev(IEnumerable chosen, ProtoId antagProto, RevolutionaryRuleComponent comp) - { - foreach (var headRev in chosen) - GiveHeadRev(headRev, antagProto, comp); - } - private void GiveHeadRev(EntityUid chosen, ProtoId antagProto, RevolutionaryRuleComponent comp) - { - RemComp(chosen); - - var inCharacterName = MetaData(chosen).EntityName; - - if (!_mind.TryGetMind(chosen, out var mind, out _)) - return; - - if (!_role.MindHasRole(mind)) - { - _role.MindAddRole(mind, new RevolutionaryRoleComponent { PrototypeId = antagProto }, silent: true); - } - - comp.HeadRevs.Add(inCharacterName, mind); - _inventory.SpawnItemsOnEntity(chosen, comp.StartingGear); - var revComp = EnsureComp(chosen); - EnsureComp(chosen); - - _antagSelection.SendBriefing(chosen, Loc.GetString("head-rev-role-greeting"), Color.CornflowerBlue, revComp.RevStartSound); - } - /// /// Called when a Head Rev uses a flash in melee to convert somebody else. /// @@ -233,22 +157,7 @@ private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref Aft } if (mind?.Session != null) - _antagSelection.SendBriefing(mind.Session, Loc.GetString("rev-role-greeting"), Color.Red, revComp.RevStartSound); - } - - public void OnHeadRevAdmin(EntityUid entity) - { - if (HasComp(entity)) - return; - - var revRule = EntityQuery().FirstOrDefault(); - if (revRule == null) - { - GameTicker.StartGameRule("Revolutionary", out var ruleEnt); - revRule = Comp(ruleEnt); - } - - GiveHeadRev(entity, revRule.HeadRevPrototypeId, revRule); + _antag.SendBriefing(mind.Session, Loc.GetString("rev-role-greeting"), Color.Red, revComp.RevStartSound); } //TODO: Enemies of the revolution @@ -309,7 +218,7 @@ private bool CheckRevsLose() _popup.PopupEntity(Loc.GetString("rev-break-control", ("name", Identity.Entity(uid, EntityManager))), uid); _adminLogManager.Add(LogType.Mind, LogImpact.Medium, $"{ToPrettyString(uid)} was deconverted due to all Head Revolutionaries dying."); - if (!_mind.TryGetMind(uid, out var mindId, out var mind, mc)) + if (!_mind.TryGetMind(uid, out var mindId, out _, mc)) continue; // remove their antag role diff --git a/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs b/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs index 7755f684be..f09ed3ebc3 100644 --- a/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Shuttles.Systems; using Content.Server.Station.Components; diff --git a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs index a26a2d783c..c60670a3ad 100644 --- a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Sandbox; diff --git a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs index fa5f17b4f3..d5adb8fdb7 100644 --- a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs @@ -1,4 +1,5 @@ using Content.Server.Administration.Logs; +using Content.Server.GameTicking.Components; using Content.Server.Chat.Managers; using Content.Server.GameTicking.Presets; using Content.Server.GameTicking.Rules.Components; diff --git a/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs b/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs index 42e7e82335..4486ee40fb 100644 --- a/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs +++ b/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Shared.Storage; diff --git a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs index 32f9040f89..083085fa0d 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -3,118 +3,38 @@ using Content.Server.Mind; using Content.Server.Objectives; using Content.Server.Roles; -using Content.Shared.Antag; -using Content.Shared.CombatMode.Pacification; using Content.Shared.Humanoid; -using Content.Shared.Inventory; using Content.Shared.Mind; using Content.Shared.Objectives.Components; -using Content.Shared.Roles; using Robust.Shared.Random; -using System.Linq; namespace Content.Server.GameTicking.Rules; public sealed class ThiefRuleSystem : GameRuleSystem { [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; [Dependency] private readonly MindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roleSystem = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly ObjectivesSystem _objectives = default!; - [Dependency] private readonly InventorySystem _inventory = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayersSpawned); + SubscribeLocalEvent(AfterAntagSelected); SubscribeLocalEvent(OnGetBriefing); SubscribeLocalEvent(OnObjectivesTextGetInfo); } - private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) + private void AfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) { - var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out _, out var comp, out var gameRule)) - { - //Get all players eligible for this role, allow selecting existing antags - //TO DO: When voxes specifies are added, increase their chance of becoming a thief by 4 times >:) - var eligiblePlayers = _antagSelection.GetEligiblePlayers(ev.Players, comp.ThiefPrototypeId, acceptableAntags: AntagAcceptability.All, allowNonHumanoids: true); - - //Abort if there are none - if (eligiblePlayers.Count == 0) - { - Log.Warning($"No eligible thieves found, ending game rule {ToPrettyString(uid):rule}"); - GameTicker.EndGameRule(uid, gameRule); - continue; - } - - //Calculate number of thieves to choose - var thiefCount = _random.Next(1, comp.MaxAllowThief + 1); - - //Select our theives - var thieves = _antagSelection.ChooseAntags(thiefCount, eligiblePlayers); - - MakeThief(thieves, comp, comp.PacifistThieves); - } - } - - public void MakeThief(List players, ThiefRuleComponent thiefRule, bool addPacified) - { - foreach (var thief in players) - { - MakeThief(thief, thiefRule, addPacified); - } - } - - public void MakeThief(EntityUid thief, ThiefRuleComponent thiefRule, bool addPacified) - { - if (!_mindSystem.TryGetMind(thief, out var mindId, out var mind)) + if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind)) return; - if (HasComp(mindId)) - return; - - // Assign thief roles - _roleSystem.MindAddRole(mindId, new ThiefRoleComponent - { - PrototypeId = thiefRule.ThiefPrototypeId, - }, silent: true); - - //Add Pacified - //To Do: Long-term this should just be using the antag code to add components. - if (addPacified) //This check is important because some servers may want to disable the thief's pacifism. Do not remove. - { - EnsureComp(thief); - } - //Generate objectives - GenerateObjectives(mindId, mind, thiefRule); - - //Send briefing here to account for humanoid/animal - _antagSelection.SendBriefing(thief, MakeBriefing(thief), null, thiefRule.GreetingSound); - - // Give starting items - _inventory.SpawnItemsOnEntity(thief, thiefRule.StarterItems); - - thiefRule.ThievesMinds.Add(mindId); - } - - public void AdminMakeThief(EntityUid entity, bool addPacified) - { - var thiefRule = EntityQuery().FirstOrDefault(); - if (thiefRule == null) - { - GameTicker.StartGameRule("Thief", out var ruleEntity); - thiefRule = Comp(ruleEntity); - } - - if (HasComp(entity)) - return; - - MakeThief(entity, thiefRule, addPacified); + GenerateObjectives(mindId, mind, ent); + _antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null); } private void GenerateObjectives(EntityUid mindId, MindComponent mind, ThiefRuleComponent thiefRule) @@ -160,8 +80,7 @@ private void OnGetBriefing(Entity thief, ref GetBriefingEven private string MakeBriefing(EntityUid thief) { var isHuman = HasComp(thief); - var briefing = "\n"; - briefing = isHuman + var briefing = isHuman ? Loc.GetString("thief-role-greeting-human") : Loc.GetString("thief-role-greeting-animal"); @@ -169,9 +88,9 @@ private string MakeBriefing(EntityUid thief) return briefing; } - private void OnObjectivesTextGetInfo(Entity thiefs, ref ObjectivesTextGetInfoEvent args) + private void OnObjectivesTextGetInfo(Entity ent, ref ObjectivesTextGetInfoEvent args) { - args.Minds = thiefs.Comp.ThievesMinds; + args.Minds = _antag.GetAntagMindEntityUids(ent.Owner); args.AgentName = Loc.GetString("thief-round-end-agent-name"); } } diff --git a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs index fc9f0a9a9f..1cc5e57704 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -6,96 +6,61 @@ using Content.Server.PDA.Ringer; using Content.Server.Roles; using Content.Server.Traitor.Uplink; -using Content.Shared.CCVar; -using Content.Shared.Dataset; using Content.Shared.Mind; -using Content.Shared.Mobs.Systems; using Content.Shared.Objectives.Components; using Content.Shared.PDA; using Content.Shared.Roles; using Content.Shared.Roles.Jobs; -using Robust.Server.Player; -using Robust.Shared.Configuration; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Timing; using System.Linq; using System.Text; +using Content.Server.GameTicking.Components; +using Content.Server.Traitor.Components; namespace Content.Server.GameTicking.Rules; public sealed class TraitorRuleSystem : GameRuleSystem { - [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly NpcFactionSystem _npcFaction = default!; - [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly UplinkSystem _uplink = default!; [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly SharedRoleSystem _roleSystem = default!; [Dependency] private readonly SharedJobSystem _jobs = default!; [Dependency] private readonly ObjectivesSystem _objectives = default!; - [Dependency] private readonly IGameTiming _timing = default!; - private int PlayersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor); - private int MaxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors); + public const int MaxPicks = 20; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnStartAttempt); - SubscribeLocalEvent(OnPlayersSpawned); - SubscribeLocalEvent(HandleLatejoin); + SubscribeLocalEvent(AfterEntitySelected); SubscribeLocalEvent(OnObjectivesTextGetInfo); SubscribeLocalEvent(OnObjectivesTextPrepend); } - //Set min players on game rule protected override void Added(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) { base.Added(uid, component, gameRule, args); - - gameRule.MinPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers); - } - - protected override void Started(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) - { - base.Started(uid, component, gameRule, args); MakeCodewords(component); } - protected override void ActiveTick(EntityUid uid, TraitorRuleComponent component, GameRuleComponent gameRule, float frameTime) - { - base.ActiveTick(uid, component, gameRule, frameTime); - - if (component.SelectionStatus < TraitorRuleComponent.SelectionState.Started && component.AnnounceAt < _timing.CurTime) - { - DoTraitorStart(component); - component.SelectionStatus = TraitorRuleComponent.SelectionState.Started; - } - } - - /// - /// Check for enough players - /// - /// - private void OnStartAttempt(RoundStartAttemptEvent ev) + private void AfterEntitySelected(Entity ent, ref AfterAntagEntitySelectedEvent args) { - TryRoundStartAttempt(ev, Loc.GetString("traitor-title")); + MakeTraitor(args.EntityUid, ent); } private void MakeCodewords(TraitorRuleComponent component) { - var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount); - var adjectives = _prototypeManager.Index(component.CodewordAdjectives).Values; - var verbs = _prototypeManager.Index(component.CodewordVerbs).Values; + var adjectives = _prototypeManager.Index(component.CodewordAdjectives).Values; + var verbs = _prototypeManager.Index(component.CodewordVerbs).Values; var codewordPool = adjectives.Concat(verbs).ToList(); - var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count); + var finalCodewordCount = Math.Min(component.CodewordCount, codewordPool.Count); component.Codewords = new string[finalCodewordCount]; for (var i = 0; i < finalCodewordCount; i++) { @@ -103,66 +68,25 @@ private void MakeCodewords(TraitorRuleComponent component) } } - private void DoTraitorStart(TraitorRuleComponent component) - { - var eligiblePlayers = _antagSelection.GetEligiblePlayers(_playerManager.Sessions, component.TraitorPrototypeId); - - if (eligiblePlayers.Count == 0) - return; - - var traitorsToSelect = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, PlayersPerTraitor, MaxTraitors); - - var selectedTraitors = _antagSelection.ChooseAntags(traitorsToSelect, eligiblePlayers); - - MakeTraitor(selectedTraitors, component); - } - - private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) - { - //Start the timer - var query = QueryActiveRules(); - while (query.MoveNext(out _, out var comp, out var gameRuleComponent)) - { - var delay = TimeSpan.FromSeconds( - _cfg.GetCVar(CCVars.TraitorStartDelay) + - _random.NextFloat(0f, _cfg.GetCVar(CCVars.TraitorStartDelayVariance))); - - //Set the delay for choosing traitors - comp.AnnounceAt = _timing.CurTime + delay; - - comp.SelectionStatus = TraitorRuleComponent.SelectionState.ReadyToStart; - } - } - - public bool MakeTraitor(List traitors, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true) - { - foreach (var traitor in traitors) - { - MakeTraitor(traitor, component, giveUplink, giveObjectives); - } - - return true; - } - public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool giveUplink = true, bool giveObjectives = true) { //Grab the mind if it wasnt provided if (!_mindSystem.TryGetMind(traitor, out var mindId, out var mind)) return false; - if (HasComp(mindId)) + var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords))); + + if (TryComp(traitor, out var autoTraitorComponent)) { - Log.Error($"Player {mind.CharacterName} is already a traitor."); - return false; + giveUplink = autoTraitorComponent.GiveUplink; + giveObjectives = autoTraitorComponent.GiveObjectives; } - var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords))); - Note[]? code = null; if (giveUplink) { // Calculate the amount of currency on the uplink. - var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance); + var startingBalance = component.StartingBalance; if (_jobs.MindTryGetJob(mindId, out _, out var prototype)) startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0); @@ -180,19 +104,14 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#")))); } - _antagSelection.SendBriefing(traitor, GenerateBriefing(component.Codewords, code), null, component.GreetSoundNotification); + _antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code), null, component.GreetSoundNotification); component.TraitorMinds.Add(mindId); - // Assign traitor roles - _roleSystem.MindAddRole(mindId, new TraitorRoleComponent - { - PrototypeId = component.TraitorPrototypeId - }, mind, true); // Assign briefing _roleSystem.MindAddRole(mindId, new RoleBriefingComponent { - Briefing = briefing.ToString() + Briefing = briefing }, mind, true); // Change the faction @@ -202,11 +121,8 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool // Give traitors their objectives if (giveObjectives) { - var maxDifficulty = _cfg.GetCVar(CCVars.TraitorMaxDifficulty); - var maxPicks = _cfg.GetCVar(CCVars.TraitorMaxPicks); var difficulty = 0f; - Log.Debug($"Attempting {maxPicks} objective picks with {maxDifficulty} difficulty"); - for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++) + for (var pick = 0; pick < MaxPicks && component.MaxDifficulty > difficulty; pick++) { var objective = _objectives.GetRandomObjective(mindId, mind, component.ObjectiveGroup); if (objective == null) @@ -222,53 +138,9 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool return true; } - private void HandleLatejoin(PlayerSpawnCompleteEvent ev) - { - var query = QueryActiveRules(); - while (query.MoveNext(out _, out var comp, out _)) - { - if (comp.TotalTraitors >= MaxTraitors) - continue; - - if (!ev.LateJoin) - continue; - - if (!_antagSelection.IsPlayerEligible(ev.Player, comp.TraitorPrototypeId)) - continue; - - //If its before we have selected traitors, continue - if (comp.SelectionStatus < TraitorRuleComponent.SelectionState.Started) - continue; - - // the nth player we adjust our probabilities around - var target = PlayersPerTraitor * comp.TotalTraitors + 1; - var chance = 1f / PlayersPerTraitor; - - // If we have too many traitors, divide by how many players below target for next traitor we are. - if (ev.JoinOrder < target) - { - chance /= (target - ev.JoinOrder); - } - else // Tick up towards 100% chance. - { - chance *= ((ev.JoinOrder + 1) - target); - } - - if (chance > 1) - chance = 1; - - // Now that we've calculated our chance, roll and make them a traitor if we roll under. - // You get one shot. - if (_random.Prob(chance)) - { - MakeTraitor(ev.Mob, comp); - } - } - } - private void OnObjectivesTextGetInfo(EntityUid uid, TraitorRuleComponent comp, ref ObjectivesTextGetInfoEvent args) { - args.Minds = comp.TraitorMinds; + args.Minds = _antag.GetAntagMindEntityUids(uid); args.AgentName = Loc.GetString("traitor-round-end-agent-name"); } @@ -277,27 +149,6 @@ private void OnObjectivesTextPrepend(EntityUid uid, TraitorRuleComponent comp, r args.Text += "\n" + Loc.GetString("traitor-round-end-codewords", ("codewords", string.Join(", ", comp.Codewords))); } - /// - /// Start this game rule manually - /// - public TraitorRuleComponent StartGameRule() - { - var comp = EntityQuery().FirstOrDefault(); - if (comp == null) - { - GameTicker.StartGameRule("Traitor", out var ruleEntity); - comp = Comp(ruleEntity); - } - - return comp; - } - - public void MakeTraitorAdmin(EntityUid entity, bool giveUplink, bool giveObjectives) - { - var traitorRule = StartGameRule(); - MakeTraitor(entity, traitorRule, giveUplink, giveObjectives); - } - private string GenerateBriefing(string[] codewords, Note[]? uplinkCode) { var sb = new StringBuilder(); @@ -312,9 +163,11 @@ private string GenerateBriefing(string[] codewords, Note[]? uplinkCode) public List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind) { List<(EntityUid Id, MindComponent Mind)> allTraitors = new(); - foreach (var traitor in EntityQuery()) + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var traitor)) { - foreach (var role in GetOtherTraitorMindsAliveAndConnected(ourMind, traitor)) + foreach (var role in GetOtherTraitorMindsAliveAndConnected(ourMind, (uid, traitor))) { if (!allTraitors.Contains(role)) allTraitors.Add(role); @@ -324,20 +177,15 @@ private string GenerateBriefing(string[] codewords, Note[]? uplinkCode) return allTraitors; } - private List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind, TraitorRuleComponent component) + private List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind, Entity rule) { var traitors = new List<(EntityUid Id, MindComponent Mind)>(); - foreach (var traitor in component.TraitorMinds) + foreach (var mind in _antag.GetAntagMinds(rule.Owner)) { - if (TryComp(traitor, out MindComponent? mind) && - mind.OwnedEntity != null && - mind.Session != null && - mind != ourMind && - _mobStateSystem.IsAlive(mind.OwnedEntity.Value) && - mind.CurrentEntity == mind.OwnedEntity) - { - traitors.Add((traitor, mind)); - } + if (mind.Comp == ourMind) + continue; + + traitors.Add((mind, mind)); } return traitors; diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index e8dc37dc1e..34da72f311 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -1,46 +1,35 @@ -using Content.Server.Actions; using Content.Server.Antag; using Content.Server.Chat.Systems; using Content.Server.GameTicking.Rules.Components; using Content.Server.Popups; -using Content.Server.Roles; using Content.Server.RoundEnd; using Content.Server.Station.Components; using Content.Server.Station.Systems; using Content.Server.Zombies; -using Content.Shared.CCVar; using Content.Shared.Humanoid; using Content.Shared.Mind; using Content.Shared.Mobs; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; -using Content.Shared.Roles; using Content.Shared.Zombies; -using Robust.Server.Player; -using Robust.Shared.Configuration; using Robust.Shared.Player; -using Robust.Shared.Random; using Robust.Shared.Timing; using System.Globalization; using Content.Server.Announcements.Systems; +using Content.Server.GameTicking.Components; namespace Content.Server.GameTicking.Rules; public sealed class ZombieRuleSystem : GameRuleSystem { - [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly IConfigurationManager _cfg = default!; - [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly ChatSystem _chat = default!; [Dependency] private readonly RoundEndSystem _roundEnd = default!; [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly ActionsSystem _action = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly ZombieSystem _zombie = default!; [Dependency] private readonly SharedMindSystem _mindSystem = default!; - [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly StationSystem _station = default!; - [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly AnnouncerSystem _announcer = default!; @@ -48,67 +37,56 @@ public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnStartAttempt); - SubscribeLocalEvent(OnRoundEndText); SubscribeLocalEvent(OnZombifySelf); } - /// - /// Set the required minimum players for this gamemode to start - /// - protected override void Added(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) + protected override void AppendRoundEndText(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, + ref RoundEndTextAppendEvent args) { - base.Added(uid, component, gameRule, args); - - gameRule.MinPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers); - } - - private void OnRoundEndText(RoundEndTextAppendEvent ev) - { - foreach (var zombie in EntityQuery()) + base.AppendRoundEndText(uid, component, gameRule, ref args); + + // This is just the general condition thing used for determining the win/lose text + var fraction = GetInfectedFraction(true, true); + + if (fraction <= 0) + args.AddLine(Loc.GetString("zombie-round-end-amount-none")); + else if (fraction <= 0.25) + args.AddLine(Loc.GetString("zombie-round-end-amount-low")); + else if (fraction <= 0.5) + args.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture)))); + else if (fraction < 1) + args.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture)))); + else + args.AddLine(Loc.GetString("zombie-round-end-amount-all")); + + var antags = _antag.GetAntagIdentifiers(uid); + args.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", antags.Count))); + foreach (var (_, data, entName) in antags) { - // This is just the general condition thing used for determining the win/lose text - var fraction = GetInfectedFraction(true, true); - - if (fraction <= 0) - ev.AddLine(Loc.GetString("zombie-round-end-amount-none")); - else if (fraction <= 0.25) - ev.AddLine(Loc.GetString("zombie-round-end-amount-low")); - else if (fraction <= 0.5) - ev.AddLine(Loc.GetString("zombie-round-end-amount-medium", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture)))); - else if (fraction < 1) - ev.AddLine(Loc.GetString("zombie-round-end-amount-high", ("percent", Math.Round((fraction * 100), 2).ToString(CultureInfo.InvariantCulture)))); - else - ev.AddLine(Loc.GetString("zombie-round-end-amount-all")); + args.AddLine(Loc.GetString("zombie-round-end-user-was-initial", + ("name", entName), + ("username", data.UserName))); + } - ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", zombie.InitialInfectedNames.Count))); - foreach (var player in zombie.InitialInfectedNames) + var healthy = GetHealthyHumans(); + // Gets a bunch of the living players and displays them if they're under a threshold. + // InitialInfected is used for the threshold because it scales with the player count well. + if (healthy.Count <= 0 || healthy.Count > 2 * antags.Count) + return; + args.AddLine(""); + args.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", healthy.Count))); + foreach (var survivor in healthy) + { + var meta = MetaData(survivor); + var username = string.Empty; + if (_mindSystem.TryGetMind(survivor, out _, out var mind) && mind.Session != null) { - ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial", - ("name", player.Key), - ("username", player.Value))); + username = mind.Session.Name; } - var healthy = GetHealthyHumans(); - // Gets a bunch of the living players and displays them if they're under a threshold. - // InitialInfected is used for the threshold because it scales with the player count well. - if (healthy.Count <= 0 || healthy.Count > 2 * zombie.InitialInfectedNames.Count) - continue; - ev.AddLine(""); - ev.AddLine(Loc.GetString("zombie-round-end-survivor-count", ("count", healthy.Count))); - foreach (var survivor in healthy) - { - var meta = MetaData(survivor); - var username = string.Empty; - if (_mindSystem.TryGetMind(survivor, out _, out var mind) && mind.Session != null) - { - username = mind.Session.Name; - } - - ev.AddLine(Loc.GetString("zombie-round-end-user-was-survivor", - ("name", meta.EntityName), - ("username", username))); - } + args.AddLine(Loc.GetString("zombie-round-end-user-was-survivor", + ("name", meta.EntityName), + ("username", username))); } } @@ -138,38 +116,20 @@ private void CheckRoundEnd(ZombieRuleComponent zombieRuleComponent) _roundEnd.EndRound(); } - /// - /// Check we have enough players to start this game mode, if not - cancel and announce - /// - private void OnStartAttempt(RoundStartAttemptEvent ev) - { - TryRoundStartAttempt(ev, Loc.GetString("zombie-title")); - } - protected override void Started(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) { base.Started(uid, component, gameRule, args); - var delay = _random.Next(component.MinStartDelay, component.MaxStartDelay); - component.StartTime = _timing.CurTime + delay; + component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; } protected override void ActiveTick(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, float frameTime) { base.ActiveTick(uid, component, gameRule, frameTime); - - if (component.StartTime.HasValue && component.StartTime < _timing.CurTime) - { - InfectInitialPlayers(component); - component.StartTime = null; - component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; - } - - if (component.NextRoundEndCheck.HasValue && component.NextRoundEndCheck < _timing.CurTime) - { - CheckRoundEnd(component); - component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; - } + if (!component.NextRoundEndCheck.HasValue || component.NextRoundEndCheck > _timing.CurTime) + return; + CheckRoundEnd(component); + component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; } private void OnZombifySelf(EntityUid uid, PendingZombieComponent component, ZombifySelfActionEvent args) @@ -236,81 +196,4 @@ private List GetHealthyHumans(bool includeOffStation = true) } return healthy; } - - /// - /// Infects the first players with the passive zombie virus. - /// Also records their names for the end of round screen. - /// - /// - /// The reason this code is written separately is to facilitate - /// allowing this gamemode to be started midround. As such, it doesn't need - /// any information besides just running. - /// - private void InfectInitialPlayers(ZombieRuleComponent component) - { - //Get all players with initial infected enabled, and exclude those with the ZombieImmuneComponent and roles with CanBeAntag = False - var eligiblePlayers = _antagSelection.GetEligiblePlayers( - _playerManager.Sessions, - component.PatientZeroPrototypeId, - includeAllJobs: false, - customExcludeCondition: player => HasComp(player) || HasComp(player) - ); - - //And get all players, excluding ZombieImmune and roles with CanBeAntag = False - to fill any leftover initial infected slots - var allPlayers = _antagSelection.GetEligiblePlayers( - _playerManager.Sessions, - component.PatientZeroPrototypeId, - acceptableAntags: Shared.Antag.AntagAcceptability.All, - includeAllJobs: false , - ignorePreferences: true, - customExcludeCondition: HasComp - ); - - //If there are no players to choose, abort - if (allPlayers.Count == 0) - return; - - //How many initial infected should we select - var initialInfectedCount = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, component.PlayersPerInfected, component.MaxInitialInfected); - - //Choose the required number of initial infected from the eligible players, making up any shortfall by choosing from all players - var initialInfected = _antagSelection.ChooseAntags(initialInfectedCount, eligiblePlayers, allPlayers); - - //Make brain craving - MakeZombie(initialInfected, component); - - //Send the briefing, play greeting sound - _antagSelection.SendBriefing(initialInfected, Loc.GetString("zombie-patientzero-role-greeting"), Color.Plum, component.InitialInfectedSound); - } - - private void MakeZombie(List entities, ZombieRuleComponent component) - { - foreach (var entity in entities) - { - MakeZombie(entity, component); - } - } - private void MakeZombie(EntityUid entity, ZombieRuleComponent component) - { - if (!_mindSystem.TryGetMind(entity, out var mind, out var mindComponent)) - return; - - //Add the role to the mind silently (to avoid repeating job assignment) - _roles.MindAddRole(mind, new InitialInfectedRoleComponent { PrototypeId = component.PatientZeroPrototypeId }, silent: true); - EnsureComp(entity); - - //Add the zombie components and grace period - var pending = EnsureComp(entity); - pending.GracePeriod = _random.Next(component.MinInitialInfectedGrace, component.MaxInitialInfectedGrace); - EnsureComp(entity); - EnsureComp(entity); - - //Add the zombify action - _action.AddAction(entity, ref pending.Action, component.ZombifySelfActionPrototype, entity); - - //Get names for the round end screen, incase they leave mid-round - var inCharacterName = MetaData(entity).EntityName; - var accountName = mindComponent.Session == null ? string.Empty : mindComponent.Session.Name; - component.InitialInfectedNames.Add(inCharacterName, accountName); - } } diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index fa263e059d..bc39997735 100644 --- a/Content.Server/IoC/ServerContentIoC.cs +++ b/Content.Server/IoC/ServerContentIoC.cs @@ -5,10 +5,10 @@ using Content.Server.Afk; using Content.Server.Chat.Managers; using Content.Server.Connection; -using Content.Server.DiscordAuth; using Content.Server.JoinQueue; using Content.Server.Database; using Content.Server.Discord; +using Content.Server.DiscordAuth; using Content.Server.EUI; using Content.Server.GhostKick; using Content.Server.Info; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/FreeProberRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/FreeProberRule.cs index 46dff726e5..35bcb4c28b 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/FreeProberRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/FreeProberRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Robust.Shared.Map; using Robust.Shared.Random; using Content.Server.GameTicking.Rules.Components; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerEventSystem.cs b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerEventSystem.cs index 078826604e..bf32769c8f 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerEventSystem.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerEventSystem.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Psionics.Glimmer; using Content.Shared.Psionics.Glimmer; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerRandomSentienceRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerRandomSentienceRule.cs index c086462b40..578f8bf48b 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerRandomSentienceRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerRandomSentienceRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Ghost.Roles.Components; using Content.Server.Psionics; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerRevenantSpawnRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerRevenantSpawnRule.cs index 8bab321db7..152d6d9fe5 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerRevenantSpawnRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerRevenantSpawnRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Robust.Shared.Random; using Content.Server.GameTicking.Rules.Components; using Content.Server.Psionics.Glimmer; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs index 89b5a176f2..90061c501a 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/GlimmerWispSpawnRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Robust.Shared.Random; using Content.Server.GameTicking.Rules.Components; using Content.Server.NPC.Components; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs index 3be2eed638..871705b5e8 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/MassMindSwapRule.cs @@ -1,5 +1,6 @@ using Robust.Shared.Random; using Content.Server.Psionics.Abilities; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Psionics; using Content.Server.StationEvents.Components; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs index 7abbdcdab3..94a488bd84 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Random; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs index d7880af903..b1ec62d190 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericFryRule.cs @@ -3,6 +3,7 @@ using Robust.Shared.Player; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; +using Content.Server.GameTicking.Components; using Content.Shared.Construction.EntitySystems; using Content.Server.GameTicking.Rules.Components; using Content.Server.Popups; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs index 021c959102..84407954df 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericStormRule.cs @@ -1,5 +1,6 @@ using Robust.Shared.Random; using Content.Server.Psionics.Abilities; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.Psionics; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs index a11faa0693..ae85e16e9e 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/NoosphericZapRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Popups; using Content.Server.Psionics; diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs index 753b2e2572..4b9fb6ae53 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/PsionicCatGotYourTongueRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Robust.Shared.Random; using Robust.Shared.Player; using Content.Server.Psionics; diff --git a/Content.Server/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs index 20205b8b72..47fe4eb5f8 100644 --- a/Content.Server/Objectives/ObjectivesSystem.cs +++ b/Content.Server/Objectives/ObjectivesSystem.cs @@ -1,10 +1,7 @@ using Content.Server.GameTicking; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.Mind; using Content.Server.Shuttles.Systems; using Content.Shared.Cuffs.Components; using Content.Shared.Mind; -using Content.Shared.Mobs.Systems; using Content.Shared.Objectives.Components; using Content.Shared.Objectives.Systems; using Content.Shared.Random; @@ -12,7 +9,9 @@ using Robust.Shared.Prototypes; using Robust.Shared.Random; using System.Linq; +using Content.Server.GameTicking.Components; using System.Text; +using Robust.Server.Player; namespace Content.Server.Objectives; @@ -20,8 +19,8 @@ public sealed class ObjectivesSystem : SharedObjectivesSystem { [Dependency] private readonly GameTicker _gameTicker = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _player = default!; [Dependency] private readonly IRobustRandom _random = default!; - [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; public override void Initialize() @@ -179,7 +178,9 @@ private void AddSummary(StringBuilder result, string agent, List mind .ThenByDescending(x => x.completedObjectives); foreach (var (summary, _, _) in sortedAgents) + { result.AppendLine(summary); + } } public EntityUid? GetRandomObjective(EntityUid mindId, MindComponent mind, string objectiveGroupProto) @@ -244,8 +245,14 @@ private bool IsInCustody(EntityUid mindId, MindComponent? mind = null) return null; var name = mind.CharacterName; - _mind.TryGetSession(mindId, out var session); - var username = session?.Name; + var username = (string?) null; + + if (mind.OriginalOwnerUserId != null && + _player.TryGetPlayerData(mind.OriginalOwnerUserId.Value, out var sessionData)) + { + username = sessionData.UserName; + } + if (username != null) { diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs index 107d09c898..0e20f007d7 100644 --- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs @@ -17,6 +17,7 @@ using Robust.Shared.Utility; using System.Linq; using System.Diagnostics.CodeAnalysis; +using Content.Server.GameTicking.Components; namespace Content.Server.Power.EntitySystems; @@ -723,8 +724,8 @@ private void GetLoadsForNode(EntityUid uid, Node node, out List> GetSelectedProfilesForPlayers(List userIds); bool HavePreferencesLoaded(ICommonSession session); } diff --git a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs index e262fde64d..c3efe14be9 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -256,6 +256,20 @@ public PlayerPreferences GetPreferences(NetUserId userId) return prefs; } + /// + /// Retrieves preferences for the given username from storage or returns null. + /// Creates and saves default preferences if they are not found, then returns them. + /// + public PlayerPreferences? GetPreferencesOrNull(NetUserId? userId) + { + if (userId == null) + return null; + + if (_cachedPlayerPrefs.TryGetValue(userId.Value, out var pref)) + return pref.Prefs; + return null; + } + private async Task GetOrCreatePreferencesAsync(NetUserId userId) { var prefs = await _db.GetPlayerPreferencesAsync(userId); diff --git a/Content.Server/RandomMetadata/RandomMetadataSystem.cs b/Content.Server/RandomMetadata/RandomMetadataSystem.cs index c088d57fd9..0c254c52ac 100644 --- a/Content.Server/RandomMetadata/RandomMetadataSystem.cs +++ b/Content.Server/RandomMetadata/RandomMetadataSystem.cs @@ -1,4 +1,4 @@ -using Content.Shared.Dataset; +using Content.Shared.Dataset; using JetBrains.Annotations; using Robust.Shared.Prototypes; using Robust.Shared.Random; @@ -47,9 +47,12 @@ public string GetRandomFromSegments(List segments, string? separator) var outputSegments = new List(); foreach (var segment in segments) { - outputSegments.Add(_prototype.TryIndex(segment, out var proto) - ? Loc.GetString(_random.Pick(proto.Values)) - : Loc.GetString(segment)); + if (_prototype.TryIndex(segment, out var proto)) + outputSegments.Add(_random.Pick(proto.Values)); + else if (Loc.TryGetString(segment, out var localizedSegment)) + outputSegments.Add(localizedSegment); + else + outputSegments.Add(segment); } return string.Join(separator, outputSegments); } diff --git a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs index 506fd61d55..75f8618798 100644 --- a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs +++ b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs @@ -1,5 +1,6 @@ using System.Numerics; using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Spawners.Components; using JetBrains.Annotations; diff --git a/Content.Server/Station/Systems/StationSpawningSystem.cs b/Content.Server/Station/Systems/StationSpawningSystem.cs index 07837361d6..2fdfb0fa86 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -174,7 +174,7 @@ public EntityUid SpawnPlayerMob( if (prototype?.StartingGear != null) { var startingGear = _prototypeManager.Index(prototype.StartingGear); - EquipStartingGear(entity.Value, startingGear, profile); + EquipStartingGear(entity.Value, startingGear); if (profile != null) EquipIdCard(entity.Value, profile.Name, prototype, station); InternalEncryptionKeySpawner.TryInsertEncryptionKey(entity.Value, startingGear, EntityManager, profile); // Parkstation - IPC diff --git a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs index 0243a00c9a..b9eb3b7b09 100644 --- a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs @@ -1,5 +1,6 @@ using System.Linq; using Content.Server.Administration; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Components/LoneOpsSpawnRuleComponent.cs b/Content.Server/StationEvents/Components/LoneOpsSpawnRuleComponent.cs deleted file mode 100644 index 92911e0858..0000000000 --- a/Content.Server/StationEvents/Components/LoneOpsSpawnRuleComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Content.Server.StationEvents.Events; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - -namespace Content.Server.StationEvents.Components; - -[RegisterComponent, Access(typeof(LoneOpsSpawnRule))] -public sealed partial class LoneOpsSpawnRuleComponent : Component -{ - [DataField("loneOpsShuttlePath")] - public string LoneOpsShuttlePath = "Maps/Shuttles/striker.yml"; - - [DataField("gameRuleProto", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string GameRuleProto = "Nukeops"; - - [DataField("additionalRule")] - public EntityUid? AdditionalRule; -} diff --git a/Content.Server/StationEvents/Events/AnomalySpawnRule.cs b/Content.Server/StationEvents/Events/AnomalySpawnRule.cs index 4cd94d3e71..98d5aa76a6 100644 --- a/Content.Server/StationEvents/Events/AnomalySpawnRule.cs +++ b/Content.Server/StationEvents/Events/AnomalySpawnRule.cs @@ -1,4 +1,5 @@ using Content.Server.Anomaly; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs b/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs index b25c1d6561..29c1897657 100644 --- a/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs +++ b/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Random; using Content.Server.Announcements.Systems; diff --git a/Content.Server/StationEvents/Events/BluespaceLockerRule.cs b/Content.Server/StationEvents/Events/BluespaceLockerRule.cs index 709b750334..eef9850e73 100644 --- a/Content.Server/StationEvents/Events/BluespaceLockerRule.cs +++ b/Content.Server/StationEvents/Events/BluespaceLockerRule.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Server.Resist; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/BreakerFlipRule.cs b/Content.Server/StationEvents/Events/BreakerFlipRule.cs index e7574f27ad..3b2368556b 100644 --- a/Content.Server/StationEvents/Events/BreakerFlipRule.cs +++ b/Content.Server/StationEvents/Events/BreakerFlipRule.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; using Content.Server.Station.Components; diff --git a/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs b/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs index feb88d9b84..282e28e499 100644 --- a/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs +++ b/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Server.Station.Systems; diff --git a/Content.Server/StationEvents/Events/CargoGiftsRule.cs b/Content.Server/StationEvents/Events/CargoGiftsRule.cs index 80af23c6fa..62f01f58fe 100644 --- a/Content.Server/StationEvents/Events/CargoGiftsRule.cs +++ b/Content.Server/StationEvents/Events/CargoGiftsRule.cs @@ -2,6 +2,7 @@ using Content.Server.Cargo.Components; using Content.Server.Cargo.Systems; using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/ClericalErrorRule.cs b/Content.Server/StationEvents/Events/ClericalErrorRule.cs index dd4473952c..854ee685b3 100644 --- a/Content.Server/StationEvents/Events/ClericalErrorRule.cs +++ b/Content.Server/StationEvents/Events/ClericalErrorRule.cs @@ -1,4 +1,5 @@ -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.StationRecords; using Content.Server.StationRecords.Systems; diff --git a/Content.Server/StationEvents/Events/FalseAlarmRule.cs b/Content.Server/StationEvents/Events/FalseAlarmRule.cs index cd434a721b..2d129b3558 100644 --- a/Content.Server/StationEvents/Events/FalseAlarmRule.cs +++ b/Content.Server/StationEvents/Events/FalseAlarmRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using JetBrains.Annotations; diff --git a/Content.Server/StationEvents/Events/GasLeakRule.cs b/Content.Server/StationEvents/Events/GasLeakRule.cs index 68544e416c..1221612171 100644 --- a/Content.Server/StationEvents/Events/GasLeakRule.cs +++ b/Content.Server/StationEvents/Events/GasLeakRule.cs @@ -1,4 +1,5 @@ using Content.Server.Atmos.EntitySystems; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Audio; diff --git a/Content.Server/StationEvents/Events/ImmovableRodRule.cs b/Content.Server/StationEvents/Events/ImmovableRodRule.cs index a61c6b69e1..781d0368f4 100644 --- a/Content.Server/StationEvents/Events/ImmovableRodRule.cs +++ b/Content.Server/StationEvents/Events/ImmovableRodRule.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.ImmovableRod; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/IonStormRule.cs b/Content.Server/StationEvents/Events/IonStormRule.cs index cd3cd63ae8..8361cc6048 100644 --- a/Content.Server/StationEvents/Events/IonStormRule.cs +++ b/Content.Server/StationEvents/Events/IonStormRule.cs @@ -1,5 +1,5 @@ +using Content.Server.GameTicking.Components; using System.Linq; -using Content.Server.GameTicking.Rules.Components; using Content.Server.Silicons.Laws; using Content.Server.Station.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/KudzuGrowthRule.cs b/Content.Server/StationEvents/Events/KudzuGrowthRule.cs index 3fa12cd4e9..5b56e03846 100644 --- a/Content.Server/StationEvents/Events/KudzuGrowthRule.cs +++ b/Content.Server/StationEvents/Events/KudzuGrowthRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs b/Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs deleted file mode 100644 index 4b15e59099..0000000000 --- a/Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs +++ /dev/null @@ -1,47 +0,0 @@ -using Robust.Server.GameObjects; -using Robust.Server.Maps; -using Content.Server.GameTicking.Rules.Components; -using Content.Server.StationEvents.Components; -using Content.Server.RoundEnd; - -namespace Content.Server.StationEvents.Events; - -public sealed class LoneOpsSpawnRule : StationEventSystem -{ - [Dependency] private readonly MapLoaderSystem _map = default!; - - protected override void Started(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) - { - base.Started(uid, component, gameRule, args); - - // Loneops can only spawn if there is no nukeops active - if (GameTicker.IsGameRuleAdded()) - { - ForceEndSelf(uid, gameRule); - return; - } - - var shuttleMap = MapManager.CreateMap(); - var options = new MapLoadOptions - { - LoadMap = true, - }; - - _map.TryLoad(shuttleMap, component.LoneOpsShuttlePath, out _, options); - - var nukeopsEntity = GameTicker.AddGameRule(component.GameRuleProto); - component.AdditionalRule = nukeopsEntity; - var nukeopsComp = Comp(nukeopsEntity); - nukeopsComp.SpawnOutpost = false; - nukeopsComp.RoundEndBehavior = RoundEndBehavior.Nothing; - GameTicker.StartGameRule(nukeopsEntity); - } - - protected override void Ended(EntityUid uid, LoneOpsSpawnRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args) - { - base.Ended(uid, component, gameRule, args); - - if (component.AdditionalRule != null) - GameTicker.EndGameRule(component.AdditionalRule.Value); - } -} diff --git a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs index 4fc158f864..2239db7f70 100644 --- a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs +++ b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.Traits.Assorted; diff --git a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs index ad56479b37..455011259d 100644 --- a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs +++ b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs @@ -1,4 +1,5 @@ using System.Numerics; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Robust.Shared.Map; diff --git a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs b/Content.Server/StationEvents/Events/NinjaSpawnRule.cs index 8ad5c8602e..d9d68a386c 100644 --- a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs +++ b/Content.Server/StationEvents/Events/NinjaSpawnRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Ninja.Systems; using Content.Server.Station.Components; diff --git a/Content.Server/StationEvents/Events/PowerGridCheckRule.cs b/Content.Server/StationEvents/Events/PowerGridCheckRule.cs index 97e8948461..b0a0bbc9fe 100644 --- a/Content.Server/StationEvents/Events/PowerGridCheckRule.cs +++ b/Content.Server/StationEvents/Events/PowerGridCheckRule.cs @@ -1,4 +1,5 @@ using System.Threading; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Power.Components; using Content.Server.Power.EntitySystems; diff --git a/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs b/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs index c3cd719cc4..87d50fc8b2 100644 --- a/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs +++ b/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Server.Storage.Components; diff --git a/Content.Server/StationEvents/Events/RandomSentienceRule.cs b/Content.Server/StationEvents/Events/RandomSentienceRule.cs index f667ad7975..7b9173241f 100644 --- a/Content.Server/StationEvents/Events/RandomSentienceRule.cs +++ b/Content.Server/StationEvents/Events/RandomSentienceRule.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Ghost.Roles.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/RandomSpawnRule.cs b/Content.Server/StationEvents/Events/RandomSpawnRule.cs index c514acc623..77744d44e4 100644 --- a/Content.Server/StationEvents/Events/RandomSpawnRule.cs +++ b/Content.Server/StationEvents/Events/RandomSpawnRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/SolarFlareRule.cs b/Content.Server/StationEvents/Events/SolarFlareRule.cs index a4ec74b43b..0370b4ee61 100644 --- a/Content.Server/StationEvents/Events/SolarFlareRule.cs +++ b/Content.Server/StationEvents/Events/SolarFlareRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Radio; using Robust.Shared.Random; diff --git a/Content.Server/StationEvents/Events/StationEventSystem.cs b/Content.Server/StationEvents/Events/StationEventSystem.cs index 6de8024bd0..257babd0d2 100644 --- a/Content.Server/StationEvents/Events/StationEventSystem.cs +++ b/Content.Server/StationEvents/Events/StationEventSystem.cs @@ -1,5 +1,6 @@ using Content.Server.Administration.Logs; using Content.Server.Chat.Systems; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Systems; diff --git a/Content.Server/StationEvents/Events/VentClogRule.cs b/Content.Server/StationEvents/Events/VentClogRule.cs index e263a5f4f6..867f41dccc 100644 --- a/Content.Server/StationEvents/Events/VentClogRule.cs +++ b/Content.Server/StationEvents/Events/VentClogRule.cs @@ -6,6 +6,7 @@ using Robust.Shared.Random; using System.Linq; using Content.Server.Fluids.EntitySystems; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/StationEvents/Events/VentCrittersRule.cs b/Content.Server/StationEvents/Events/VentCrittersRule.cs index cdcf2bf6ff..c2605039bc 100644 --- a/Content.Server/StationEvents/Events/VentCrittersRule.cs +++ b/Content.Server/StationEvents/Events/VentCrittersRule.cs @@ -1,3 +1,4 @@ +using Content.Server.GameTicking.Components; using Content.Server.StationEvents.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; diff --git a/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs b/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs index e24578fdac..3619a6acaf 100644 --- a/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs @@ -1,4 +1,5 @@ using Content.Server.GameTicking; +using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; diff --git a/Content.Server/Traitor/Components/AutoTraitorComponent.cs b/Content.Server/Traitor/Components/AutoTraitorComponent.cs index ab4bee2f26..473441ccec 100644 --- a/Content.Server/Traitor/Components/AutoTraitorComponent.cs +++ b/Content.Server/Traitor/Components/AutoTraitorComponent.cs @@ -11,12 +11,12 @@ public sealed partial class AutoTraitorComponent : Component /// /// Whether to give the traitor an uplink or not. /// - [DataField("giveUplink"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public bool GiveUplink = true; /// /// Whether to give the traitor objectives or not. /// - [DataField("giveObjectives"), ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadWrite)] public bool GiveObjectives = true; } diff --git a/Content.Server/Traitor/Systems/AutoTraitorSystem.cs b/Content.Server/Traitor/Systems/AutoTraitorSystem.cs index 15deae2552..e9307effbc 100644 --- a/Content.Server/Traitor/Systems/AutoTraitorSystem.cs +++ b/Content.Server/Traitor/Systems/AutoTraitorSystem.cs @@ -1,6 +1,7 @@ -using Content.Server.GameTicking.Rules; +using Content.Server.Antag; using Content.Server.Traitor.Components; using Content.Shared.Mind.Components; +using Robust.Shared.Prototypes; namespace Content.Server.Traitor.Systems; @@ -9,7 +10,10 @@ namespace Content.Server.Traitor.Systems; /// public sealed class AutoTraitorSystem : EntitySystem { - [Dependency] private readonly TraitorRuleSystem _traitorRule = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; + + [ValidatePrototypeId] + private const string DefaultTraitorRule = "Traitor"; public override void Initialize() { @@ -20,44 +24,6 @@ public override void Initialize() private void OnMindAdded(EntityUid uid, AutoTraitorComponent comp, MindAddedMessage args) { - TryMakeTraitor(uid, comp); - } - - /// - /// Sets the GiveUplink field. - /// - public void SetGiveUplink(EntityUid uid, bool giveUplink, AutoTraitorComponent? comp = null) - { - if (!Resolve(uid, ref comp)) - return; - - comp.GiveUplink = giveUplink; - } - - /// - /// Sets the GiveObjectives field. - /// - public void SetGiveObjectives(EntityUid uid, bool giveObjectives, AutoTraitorComponent? comp = null) - { - if (!Resolve(uid, ref comp)) - return; - - comp.GiveObjectives = giveObjectives; - } - - /// - /// Checks if there is a mind, then makes it a traitor using the options. - /// - public bool TryMakeTraitor(EntityUid uid, AutoTraitorComponent? comp = null) - { - if (!Resolve(uid, ref comp)) - return false; - - //Start the rule if it has not already been started - var traitorRuleComponent = _traitorRule.StartGameRule(); - _traitorRule.MakeTraitor(uid, traitorRuleComponent, giveUplink: comp.GiveUplink, giveObjectives: comp.GiveObjectives); - // prevent spamming anything if it fails - RemComp(uid); - return true; + _antag.ForceMakeAntag(args.Mind.Comp.Session, DefaultTraitorRule); } } diff --git a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs index cdaed3f928..79192f6b49 100644 --- a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs +++ b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs @@ -83,12 +83,9 @@ public void Execute(IConsoleShell shell, string argStr, string[] args) uplinkEntity = eUid; } - // Get TC count - var tcCount = _cfgManager.GetCVar(CCVars.TraitorStartingBalance); - Logger.Debug(_entManager.ToPrettyString(user)); // Finally add uplink var uplinkSys = _entManager.System(); - if (!uplinkSys.AddUplink(user, FixedPoint2.New(tcCount), uplinkEntity: uplinkEntity)) + if (!uplinkSys.AddUplink(user, 20, uplinkEntity: uplinkEntity)) { shell.WriteLine(Loc.GetString("add-uplink-command-error-2")); } diff --git a/Content.Server/Zombies/PendingZombieComponent.cs b/Content.Server/Zombies/PendingZombieComponent.cs index a49b424c53..1bb0ef2872 100644 --- a/Content.Server/Zombies/PendingZombieComponent.cs +++ b/Content.Server/Zombies/PendingZombieComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Damage; +using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.Zombies; @@ -35,6 +36,21 @@ public sealed partial class PendingZombieComponent : Component [DataField("gracePeriod"), ViewVariables(VVAccess.ReadWrite)] public TimeSpan GracePeriod = TimeSpan.Zero; + /// + /// The minimum amount of time initial infected have before they start taking infection damage. + /// + [DataField] + public TimeSpan MinInitialInfectedGrace = TimeSpan.FromMinutes(12.5f); + + /// + /// The maximum amount of time initial infected have before they start taking damage. + /// + [DataField] + public TimeSpan MaxInitialInfectedGrace = TimeSpan.FromMinutes(15f); + + [DataField] + public EntProtoId ZombifySelfActionPrototype = "ActionTurnUndead"; + /// /// The chance each second that a warning will be shown. /// diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 080bef44e7..09c8fa26db 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -1,4 +1,5 @@ using System.Linq; +using Content.Server.Actions; using Content.Server.Body.Systems; using Content.Server.Chat; using Content.Server.Chat.Systems; @@ -30,6 +31,7 @@ public sealed partial class ZombieSystem : SharedZombieSystem [Dependency] private readonly BloodstreamSystem _bloodstream = default!; [Dependency] private readonly DamageableSystem _damageable = default!; [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly ActionsSystem _actions = default!; [Dependency] private readonly AutoEmoteSystem _autoEmote = default!; [Dependency] private readonly EmoteOnDamageSystem _emoteOnDamage = default!; [Dependency] private readonly MetaDataSystem _metaData = default!; @@ -74,6 +76,8 @@ private void OnPendingMapInit(EntityUid uid, PendingZombieComponent component, M } component.NextTick = _timing.CurTime + TimeSpan.FromSeconds(1f); + component.GracePeriod = _random.Next(component.MinInitialInfectedGrace, component.MaxInitialInfectedGrace); + _actions.AddAction(uid, ref component.Action, component.ZombifySelfActionPrototype); } public override void Update(float frameTime) diff --git a/Content.Shared/Antag/AntagAcceptability.cs b/Content.Shared/Antag/AntagAcceptability.cs index 98abe713eb..02d0b5f58f 100644 --- a/Content.Shared/Antag/AntagAcceptability.cs +++ b/Content.Shared/Antag/AntagAcceptability.cs @@ -20,3 +20,8 @@ public enum AntagAcceptability All } +public enum AntagSelectionTime : byte +{ + PrePlayerSpawn, + PostPlayerSpawn +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index e8a8f53e38..1eb6a16188 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -550,91 +550,6 @@ public static readonly CVarDef public static readonly CVarDef DiscordAuthApiKey = CVarDef.Create("discord.auth_api_key", "", CVar.SERVERONLY | CVar.CONFIDENTIAL); - - /* - * Suspicion - */ - - public static readonly CVarDef SuspicionMinPlayers = - CVarDef.Create("suspicion.min_players", 5); - - public static readonly CVarDef SuspicionMinTraitors = - CVarDef.Create("suspicion.min_traitors", 2); - - public static readonly CVarDef SuspicionPlayersPerTraitor = - CVarDef.Create("suspicion.players_per_traitor", 6); - - public static readonly CVarDef SuspicionStartingBalance = - CVarDef.Create("suspicion.starting_balance", 20); - - public static readonly CVarDef SuspicionMaxTimeSeconds = - CVarDef.Create("suspicion.max_time_seconds", 300); - - /* - * Traitor - */ - - public static readonly CVarDef TraitorMinPlayers = - CVarDef.Create("traitor.min_players", 5); - - public static readonly CVarDef TraitorMaxTraitors = - CVarDef.Create("traitor.max_traitors", 12); // Assuming average server maxes somewhere from like 50-80 people - - public static readonly CVarDef TraitorPlayersPerTraitor = - CVarDef.Create("traitor.players_per_traitor", 10); - - public static readonly CVarDef TraitorCodewordCount = - CVarDef.Create("traitor.codeword_count", 4); - - public static readonly CVarDef TraitorStartingBalance = - CVarDef.Create("traitor.starting_balance", 20); - - public static readonly CVarDef TraitorMaxDifficulty = - CVarDef.Create("traitor.max_difficulty", 5); - - public static readonly CVarDef TraitorMaxPicks = - CVarDef.Create("traitor.max_picks", 20); - - public static readonly CVarDef TraitorStartDelay = - CVarDef.Create("traitor.start_delay", 4f * 60f); - - public static readonly CVarDef TraitorStartDelayVariance = - CVarDef.Create("traitor.start_delay_variance", 3f * 60f); - - /* - * TraitorDeathMatch - */ - - public static readonly CVarDef TraitorDeathMatchStartingBalance = - CVarDef.Create("traitordm.starting_balance", 20); - - /* - * Zombie - */ - - public static readonly CVarDef ZombieMinPlayers = - CVarDef.Create("zombie.min_players", 20); - - /* - * Pirates - */ - - public static readonly CVarDef PiratesMinPlayers = - CVarDef.Create("pirates.min_players", 25); - - public static readonly CVarDef PiratesMaxOps = - CVarDef.Create("pirates.max_pirates", 6); - - public static readonly CVarDef PiratesPlayersPerOp = - CVarDef.Create("pirates.players_per_pirate", 5); - - /* - * Nukeops - */ - - public static readonly CVarDef NukeopsSpawnGhostRoles = - CVarDef.Create("nukeops.spawn_ghost_roles", false); - /* * Tips */ diff --git a/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs b/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs index e7a0eef80e..08ca204372 100644 --- a/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs +++ b/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs @@ -34,7 +34,7 @@ private void OnMapInit(EntityUid uid, LoadoutComponent component, MapInitEvent a return; var proto = _prototype.Index(_random.Pick(component.Prototypes)); - _station.EquipStartingGear(uid, proto, null); + _station.EquipStartingGear(uid, proto); } diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs index ece4b59e91..ce49f80af3 100644 --- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs @@ -328,8 +328,11 @@ public void SetScale(EntityUid uid, Vector2 scale, bool sync = true, HumanoidApp /// The mob's entity UID. /// The character profile to load. /// Humanoid component of the entity - public virtual void LoadProfile(EntityUid uid, HumanoidCharacterProfile profile, HumanoidAppearanceComponent? humanoid = null) + public virtual void LoadProfile(EntityUid uid, HumanoidCharacterProfile? profile, HumanoidAppearanceComponent? humanoid = null) { + if (profile == null) + return; + if (!Resolve(uid, ref humanoid)) { return; diff --git a/Content.Shared/Inventory/InventorySystem.Helpers.cs b/Content.Shared/Inventory/InventorySystem.Helpers.cs index 811387d375..7e325abe21 100644 --- a/Content.Shared/Inventory/InventorySystem.Helpers.cs +++ b/Content.Shared/Inventory/InventorySystem.Helpers.cs @@ -1,8 +1,6 @@ using System.Diagnostics.CodeAnalysis; -using System.Linq; using Content.Shared.Hands.Components; using Content.Shared.Storage.EntitySystems; -using Robust.Shared.Containers; using Robust.Shared.Prototypes; namespace Content.Shared.Inventory; @@ -96,7 +94,7 @@ bool DeleteItem() /// /// The entity that you want to spawn an item on /// A list of prototype IDs that you want to spawn in the bag. - public void SpawnItemsOnEntity(EntityUid entity, List items) + public void SpawnItemsOnEntity(EntityUid entity, List items) { foreach (var item in items) { diff --git a/Content.Shared/NukeOps/NukeOperativeComponent.cs b/Content.Shared/NukeOps/NukeOperativeComponent.cs index cdbefece9d..d19f0ae3e9 100644 --- a/Content.Shared/NukeOps/NukeOperativeComponent.cs +++ b/Content.Shared/NukeOps/NukeOperativeComponent.cs @@ -13,14 +13,9 @@ namespace Content.Shared.NukeOps; [RegisterComponent, NetworkedComponent] public sealed partial class NukeOperativeComponent : Component { - /// - /// Path to antagonist alert sound. - /// - [DataField("greetSoundNotification")] - public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/nukeops_start.ogg"); /// - /// + /// /// [DataField("syndStatusIcon", customTypeSerializer: typeof(PrototypeIdSerializer))] public string SyndStatusIcon = "SyndicateFaction"; diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index e8053e4c67..94ad32164b 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -1,3 +1,4 @@ +using System.Linq; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Mind; @@ -62,6 +63,64 @@ protected void SubscribeAntagEvents() where T : AntagonistRoleComponent _antagTypes.Add(typeof(T)); } + public void MindAddRoles(EntityUid mindId, ComponentRegistry components, MindComponent? mind = null, bool silent = false) + { + if (!Resolve(mindId, ref mind)) + return; + + EntityManager.AddComponents(mindId, components); + var antagonist = false; + foreach (var compReg in components.Values) + { + var compType = compReg.Component.GetType(); + + var comp = EntityManager.ComponentFactory.GetComponent(compType); + if (IsAntagonistRole(comp.GetType())) + { + antagonist = true; + break; + } + } + + var mindEv = new MindRoleAddedEvent(silent); + RaiseLocalEvent(mindId, ref mindEv); + + var message = new RoleAddedEvent(mindId, mind, antagonist, silent); + if (mind.OwnedEntity != null) + { + RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + } + + _adminLogger.Add(LogType.Mind, LogImpact.Low, + $"Role components {string.Join(components.Keys.ToString(), ", ")} added to mind of {_minds.MindOwnerLoggingString(mind)}"); + } + + public void MindAddRole(EntityUid mindId, Component component, MindComponent? mind = null, bool silent = false) + { + if (!Resolve(mindId, ref mind)) + return; + + if (HasComp(mindId, component.GetType())) + { + throw new ArgumentException($"We already have this role: {component}"); + } + + EntityManager.AddComponent(mindId, component); + var antagonist = IsAntagonistRole(component.GetType()); + + var mindEv = new MindRoleAddedEvent(silent); + RaiseLocalEvent(mindId, ref mindEv); + + var message = new RoleAddedEvent(mindId, mind, antagonist, silent); + if (mind.OwnedEntity != null) + { + RaiseLocalEvent(mind.OwnedEntity.Value, message, true); + } + + _adminLogger.Add(LogType.Mind, LogImpact.Low, + $"'Role {component}' added to mind of {_minds.MindOwnerLoggingString(mind)}"); + } + /// /// Gives this mind a new role. /// @@ -177,6 +236,11 @@ public bool IsAntagonistRole() return _antagTypes.Contains(typeof(T)); } + public bool IsAntagonistRole(Type component) + { + return _antagTypes.Contains(component); + } + /// /// Play a sound for the mind, if it has a session attached. /// Use this for role greeting sounds. diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index 715ee2a149..59c875f30e 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -1,16 +1,17 @@ using Content.Shared.Hands.Components; using Content.Shared.Hands.EntitySystems; using Content.Shared.Inventory; -using Content.Shared.Preferences; using Content.Shared.Roles; using Content.Shared.Storage; using Content.Shared.Storage.EntitySystems; using Robust.Shared.Collections; +using Robust.Shared.Prototypes; namespace Content.Shared.Station; public abstract class SharedStationSpawningSystem : EntitySystem { + [Dependency] protected readonly IPrototypeManager PrototypeManager = default!; [Dependency] protected readonly InventorySystem InventorySystem = default!; [Dependency] private readonly SharedHandsSystem _handsSystem = default!; [Dependency] private readonly SharedStorageSystem _storage = default!; @@ -21,14 +22,27 @@ public abstract class SharedStationSpawningSystem : EntitySystem /// /// Entity to load out. /// Starting gear to use. - /// Character profile to use, if any. - public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile) + public void EquipStartingGear(EntityUid entity, ProtoId? startingGear) { + PrototypeManager.TryIndex(startingGear, out var gearProto); + EquipStartingGear(entity, gearProto); + } + + /// + /// Equips starting gear onto the given entity. + /// + /// Entity to load out. + /// Starting gear to use. + public void EquipStartingGear(EntityUid entity, StartingGearPrototype? startingGear) + { + if (startingGear == null) + return; + if (InventorySystem.TryGetSlots(entity, out var slotDefinitions)) { foreach (var slot in slotDefinitions) { - var equipmentStr = startingGear.GetGear(slot.Name, profile); + var equipmentStr = startingGear.GetGear(slot.Name, null); if (string.IsNullOrEmpty(equipmentStr)) continue; diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-pirates.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-pirates.ftl deleted file mode 100644 index 941643dd9a..0000000000 --- a/Resources/Locale/en-US/game-ticking/game-presets/preset-pirates.ftl +++ /dev/null @@ -1,10 +0,0 @@ -pirates-title = Privateers -pirates-description = A group of privateers has approached your lowly station. Hostile or not, their sole goal is to end the round with as many knicknacks on their ship as they can get. - -pirates-no-ship = Through unknown circumstances, the privateer's ship was completely and utterly destroyed. No score. -pirates-final-score = The privateers successfully obtained {$score} spesos worth -pirates-final-score-2 = of knicknacks, with a total of {$finalPrice} spesos. -pirates-list-start = The privateers were: -pirates-most-valuable = The most valuable stolen items were: -pirates-stolen-item-entry = {$entity} ({$credits} spesos) -pirates-stole-nothing = - The pirates stole absolutely nothing at all. Point and laugh. diff --git a/Resources/Maps/Shuttles/striker.yml b/Resources/Maps/Shuttles/striker.yml index 35b6178bd4..88b113d7fd 100644 --- a/Resources/Maps/Shuttles/striker.yml +++ b/Resources/Maps/Shuttles/striker.yml @@ -1,2389 +1,2389 @@ -meta: - format: 6 - postmapinit: false -tilemap: - 0: Space - 29: FloorDark - 84: FloorShuttleRed - 104: FloorTechMaint - 105: FloorTechMaint2 - 118: FloorWood - 120: Lattice - 121: Plating -entities: -- proto: "" - entities: - - uid: 325 - components: - - type: MetaData - - type: Transform - pos: 0.5638949,0.47865233 - parent: invalid - - type: MapGrid - chunks: - -1,-1: - ind: -1,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAdgAAAAAAdgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAdgAAAAADdgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAHQAAAAADHQAAAAADHQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaQAAAAAAaQAAAAAAHQAAAAABHQAAAAABHQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAHQAAAAABHQAAAAACHQAAAAAB - version: 6 - 0,-1: - ind: 0,-1 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdgAAAAACeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdgAAAAABeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAAAHQAAAAABeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAABHQAAAAABHQAAAAABeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAADHQAAAAACeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - -1,0: - ind: -1,0 - tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAVAAAAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - 0,0: - ind: 0,0 - tiles: VAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA - version: 6 - - type: Broadphase - - type: Physics - bodyStatus: InAir - angularDamping: 0.05 - linearDamping: 0.05 - fixedRotation: False - bodyType: Dynamic - - type: Fixtures - fixtures: {} - - type: OccluderTree - - type: Shuttle - - type: Gravity - gravityShakeSound: !type:SoundPathSpecifier - path: /Audio/Effects/alert.ogg - - type: DecalGrid - chunkCollection: - version: 2 - nodes: - - node: - color: '#FFFFFFFF' - id: BrickTileDarkCornerNe - decals: - 11: 1,-1 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkCornerNw - decals: - 5: -3,-1 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkCornerSe - decals: - 4: 1,-3 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkCornerSw - decals: - 3: -3,-3 - - node: - color: '#FFFFFFFF' - id: BrickTileDarkLineS - decals: - 0: -1,-3 - 1: -2,-3 - 2: 0,-3 - - node: - color: '#7F1C1FFF' - id: BrickTileWhiteCornerNe - decals: - 13: 1,-1 - - node: - color: '#7F1C1FFF' - id: BrickTileWhiteCornerNw - decals: - 12: -3,-1 - - node: - color: '#7F1C1FFF' - id: BrickTileWhiteCornerSe - decals: - 9: 1,-3 - - node: - color: '#7F1C1FFF' - id: BrickTileWhiteCornerSw - decals: - 10: -3,-3 - - node: - color: '#7F1C1FFF' - id: BrickTileWhiteLineS - decals: - 6: -2,-3 - 7: -1,-3 - 8: 0,-3 - - node: - color: '#FFFFFFFF' - id: Delivery - decals: - 23: 2,-2 - 24: -4,-2 - - node: - color: '#FFFFFFFF' - id: WarnLineE - decals: - 14: 1,-2 - - node: - color: '#FFFFFFFF' - id: WarnLineS - decals: - 16: -3,-2 - - node: - color: '#FFFFFFFF' - id: WarnLineW - decals: - 15: -1,-1 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinLineN - decals: - 17: -1,-5 - 18: 0,-5 - 19: -2,-5 - - node: - color: '#FFFFFFFF' - id: WoodTrimThinLineS - decals: - 20: -2,-6 - 21: -1,-6 - 22: 0,-6 - - type: GridAtmosphere - version: 2 - data: - tiles: - -1,-1: - 0: 65535 - 0,-1: - 0: 65535 - -2,-1: - 0: 52424 - -1,-3: - 0: 65280 - -1,-2: - 0: 65535 - 0,-3: - 0: 30464 - 0,-2: - 0: 30583 - -2,0: - 0: 8 - -1,0: - 0: 3839 - 0,0: - 0: 895 - uniqueMixes: - - volume: 2500 - temperature: 293.15 - moles: - - 21.824879 - - 82.10312 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - - 0 - chunkSize: 4 - - type: GasTileOverlay - - type: RadiationGridResistance - - type: GravityShake - shakeTimes: 10 - - type: SpreaderGrid - - type: GridPathfinding -- proto: AirCanister - entities: - - uid: 91 - components: - - type: Transform - pos: -0.5,-8.5 - parent: 325 - - type: AtmosDevice - joinedGrid: 325 -- proto: AirlockExternalShuttleSyndicateLocked - entities: - - uid: 142 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -4.5,-1.5 - parent: 325 -- proto: AirlockSyndicateLocked - entities: - - uid: 20 - components: - - type: Transform - pos: -0.5,-3.5 - parent: 325 - - uid: 88 - components: - - type: Transform - pos: -0.5,-6.5 - parent: 325 -- proto: APCBasic - entities: - - uid: 107 - components: - - type: Transform - pos: 0.5,-6.5 - parent: 325 - - type: PowerNetworkBattery - loadingNetworkDemand: 15107 - currentReceiving: 15106.935 - currentSupply: 15107 - supplyRampPosition: 0.064453125 -- proto: AtmosDeviceFanTiny - entities: - - uid: 6 - components: - - type: Transform - pos: -3.5,-1.5 - parent: 325 -- proto: Bed - entities: - - uid: 76 - components: - - type: Transform - pos: 0.5,-5.5 - parent: 325 -- proto: BedsheetSyndie - entities: - - uid: 164 - components: - - type: Transform - pos: 0.5,-5.5 - parent: 325 -- proto: BlastDoorOpen - entities: - - uid: 190 - components: - - type: Transform - pos: 1.5,-5.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 331 - - type: DeviceLinkSink - links: - - 205 - - uid: 191 - components: - - type: Transform - pos: 1.5,-4.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 332 - - type: DeviceLinkSink - links: - - 205 - - uid: 192 - components: - - type: Transform - pos: -2.5,-5.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 333 - - type: DeviceLinkSink - links: - - 205 - - uid: 193 - components: - - type: Transform - pos: -2.5,-4.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 334 - - type: DeviceLinkSink - links: - - 205 - - uid: 196 - components: - - type: Transform - pos: 3.5,-1.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 337 - - type: DeviceLinkSink - links: - - 205 - - uid: 198 - components: - - type: Transform - pos: -1.5,1.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 339 - - type: DeviceLinkSink - links: - - 205 - - uid: 199 - components: - - type: Transform - pos: -1.5,2.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 340 - - type: DeviceLinkSink - links: - - 205 - - uid: 200 - components: - - type: Transform - pos: -0.5,2.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 341 - - type: DeviceLinkSink - links: - - 205 - - uid: 201 - components: - - type: Transform - pos: 0.5,2.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 342 - - type: DeviceLinkSink - links: - - 205 - - uid: 202 - components: - - type: Transform - pos: 0.5,1.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 343 - - type: DeviceLinkSink - links: - - 205 -- proto: BoxMRE - entities: - - uid: 320 - components: - - type: Transform - pos: 0.70504504,-7.29326 - parent: 325 -- proto: CableApcExtension - entities: - - uid: 120 - components: - - type: Transform - pos: 0.5,-6.5 - parent: 325 - - uid: 121 - components: - - type: Transform - pos: -0.5,-6.5 - parent: 325 - - uid: 122 - components: - - type: Transform - pos: -0.5,-7.5 - parent: 325 - - uid: 123 - components: - - type: Transform - pos: -0.5,-8.5 - parent: 325 - - uid: 124 - components: - - type: Transform - pos: -1.5,-8.5 - parent: 325 - - uid: 125 - components: - - type: Transform - pos: 0.5,-8.5 - parent: 325 - - uid: 126 - components: - - type: Transform - pos: 1.5,-8.5 - parent: 325 - - uid: 127 - components: - - type: Transform - pos: -2.5,-8.5 - parent: 325 - - uid: 128 - components: - - type: Transform - pos: -3.5,-8.5 - parent: 325 - - uid: 129 - components: - - type: Transform - pos: -3.5,-7.5 - parent: 325 - - uid: 130 - components: - - type: Transform - pos: 2.5,-8.5 - parent: 325 - - uid: 131 - components: - - type: Transform - pos: 2.5,-7.5 - parent: 325 - - uid: 132 - components: - - type: Transform - pos: -0.5,-5.5 - parent: 325 - - uid: 133 - components: - - type: Transform - pos: -0.5,-4.5 - parent: 325 - - uid: 134 - components: - - type: Transform - pos: -0.5,-3.5 - parent: 325 - - uid: 135 - components: - - type: Transform - pos: -0.5,-2.5 - parent: 325 - - uid: 136 - components: - - type: Transform - pos: -0.5,-1.5 - parent: 325 - - uid: 137 - components: - - type: Transform - pos: -0.5,-0.5 - parent: 325 - - uid: 138 - components: - - type: Transform - pos: -0.5,0.5 - parent: 325 - - uid: 139 - components: - - type: Transform - pos: -0.5,1.5 - parent: 325 - - uid: 140 - components: - - type: Transform - pos: -0.5,2.5 - parent: 325 - - uid: 141 - components: - - type: Transform - pos: -1.5,1.5 - parent: 325 - - uid: 143 - components: - - type: Transform - pos: 0.5,1.5 - parent: 325 - - uid: 145 - components: - - type: Transform - pos: -1.5,-1.5 - parent: 325 - - uid: 146 - components: - - type: Transform - pos: -2.5,-1.5 - parent: 325 - - uid: 147 - components: - - type: Transform - pos: -3.5,-1.5 - parent: 325 - - uid: 148 - components: - - type: Transform - pos: -4.5,-1.5 - parent: 325 - - uid: 149 - components: - - type: Transform - pos: 0.5,-1.5 - parent: 325 - - uid: 150 - components: - - type: Transform - pos: 1.5,-1.5 - parent: 325 - - uid: 151 - components: - - type: Transform - pos: 2.5,-1.5 - parent: 325 - - uid: 152 - components: - - type: Transform - pos: 3.5,-1.5 - parent: 325 - - uid: 153 - components: - - type: Transform - pos: 0.5,-4.5 - parent: 325 - - uid: 154 - components: - - type: Transform - pos: 1.5,-4.5 - parent: 325 - - uid: 155 - components: - - type: Transform - pos: 1.5,-5.5 - parent: 325 - - uid: 156 - components: - - type: Transform - pos: -1.5,-4.5 - parent: 325 - - uid: 157 - components: - - type: Transform - pos: -2.5,-4.5 - parent: 325 - - uid: 158 - components: - - type: Transform - pos: -2.5,-5.5 - parent: 325 -- proto: CableHV - entities: - - uid: 111 - components: - - type: Transform - pos: 1.5,-7.5 - parent: 325 - - uid: 112 - components: - - type: Transform - pos: 0.5,-7.5 - parent: 325 - - uid: 113 - components: - - type: Transform - pos: -0.5,-7.5 - parent: 325 - - uid: 114 - components: - - type: Transform - pos: -1.5,-7.5 - parent: 325 - - uid: 115 - components: - - type: Transform - pos: -2.5,-7.5 - parent: 325 - - uid: 116 - components: - - type: Transform - pos: -1.5,-6.5 - parent: 325 -- proto: CableHVStack1 - entities: - - uid: 235 - components: - - type: Transform - parent: 41 - - type: Stack - count: 10 - - type: Physics - canCollide: False - - uid: 239 - components: - - type: Transform - parent: 56 - - type: Stack - count: 10 - - type: Physics - canCollide: False -- proto: CableMV - entities: - - uid: 117 - components: - - type: Transform - pos: -1.5,-6.5 - parent: 325 - - uid: 118 - components: - - type: Transform - pos: -0.5,-6.5 - parent: 325 - - uid: 119 - components: - - type: Transform - pos: 0.5,-6.5 - parent: 325 -- proto: CapacitorStockPart - entities: - - uid: 233 - components: - - type: Transform - parent: 41 - - type: Physics - canCollide: False - - uid: 234 - components: - - type: Transform - parent: 41 - - type: Physics - canCollide: False - - uid: 237 - components: - - type: Transform - parent: 56 - - type: Physics - canCollide: False - - uid: 238 - components: - - type: Transform - parent: 56 - - type: Physics - canCollide: False - - uid: 241 - components: - - type: Transform - parent: 58 - - type: Physics - canCollide: False - - uid: 242 - components: - - type: Transform - parent: 58 - - type: Physics - canCollide: False - - uid: 243 - components: - - type: Transform - parent: 58 - - type: Physics - canCollide: False - - uid: 254 - components: - - type: Transform - parent: 95 - - type: Physics - canCollide: False - - uid: 261 - components: - - type: Transform - parent: 96 - - type: Physics - canCollide: False - - uid: 268 - components: - - type: Transform - parent: 97 - - type: Physics - canCollide: False - - uid: 275 - components: - - type: Transform - parent: 98 - - type: Physics - canCollide: False - - uid: 282 - components: - - type: Transform - parent: 99 - - type: Physics - canCollide: False - - uid: 289 - components: - - type: Transform - parent: 100 - - type: Physics - canCollide: False - - uid: 296 - components: - - type: Transform - parent: 101 - - type: Physics - canCollide: False - - uid: 303 - components: - - type: Transform - parent: 102 - - type: Physics - canCollide: False -- proto: Carpet - entities: - - uid: 74 - components: - - type: Transform - pos: -0.5,-4.5 - parent: 325 - - uid: 89 - components: - - type: Transform - pos: -0.5,-5.5 - parent: 325 -- proto: Catwalk - entities: - - uid: 159 - components: - - type: Transform - pos: -1.5,-7.5 - parent: 325 - - uid: 160 - components: - - type: Transform - pos: -0.5,-7.5 - parent: 325 - - uid: 161 - components: - - type: Transform - pos: 0.5,-7.5 - parent: 325 -- proto: ChairOfficeDark - entities: - - uid: 93 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -1.5,-2.5 - parent: 325 -- proto: ChairPilotSeat - entities: - - uid: 78 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -0.5,0.5 - parent: 325 -- proto: ComputerIFFSyndicate - entities: - - uid: 40 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 0.5,0.5 - parent: 325 -- proto: ComputerShuttleSyndie - entities: - - uid: 64 - components: - - type: Transform - pos: -0.5,1.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 245 -- proto: CyberPen - entities: - - uid: 77 - components: - - type: Transform - pos: -1.1813428,-5.15565 - parent: 325 -- proto: DoorElectronics - entities: - - uid: 331 - components: - - type: Transform - parent: 190 - - type: Physics - canCollide: False - - uid: 332 - components: - - type: Transform - parent: 191 - - type: Physics - canCollide: False - - uid: 333 - components: - - type: Transform - parent: 192 - - type: Physics - canCollide: False - - uid: 334 - components: - - type: Transform - parent: 193 - - type: Physics - canCollide: False - - uid: 337 - components: - - type: Transform - parent: 196 - - type: Physics - canCollide: False - - uid: 339 - components: - - type: Transform - parent: 198 - - type: Physics - canCollide: False - - uid: 340 - components: - - type: Transform - parent: 199 - - type: Physics - canCollide: False - - uid: 341 - components: - - type: Transform - parent: 200 - - type: Physics - canCollide: False - - uid: 342 - components: - - type: Transform - parent: 201 - - type: Physics - canCollide: False - - uid: 343 - components: - - type: Transform - parent: 202 - - type: Physics - canCollide: False - - uid: 346 - components: - - type: Transform - parent: 206 - - type: Physics - canCollide: False -- proto: DresserFilled - entities: - - uid: 85 - components: - - type: Transform - pos: 0.5,-4.5 - parent: 325 -- proto: DrinkNukieCan - entities: - - uid: 144 - components: - - type: Transform - pos: -2.6964839,-2.109029 - parent: 325 -- proto: FaxMachineSyndie - entities: - - uid: 46 - components: - - type: Transform - pos: -1.5,-5.5 - parent: 325 - - type: FaxMachine - name: Striker -- proto: filingCabinetRandom - entities: - - uid: 75 - components: - - type: Transform - pos: -1.5,-4.5 - parent: 325 -- proto: Firelock - entities: - - uid: 224 - components: - - type: Transform - pos: -0.5,-3.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 350 - - type: DeviceNetwork - address: 44a24659 - receiveFrequency: 1621 - - uid: 225 - components: - - type: Transform - pos: -0.5,-6.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 351 - - type: DeviceNetwork - address: 6fdb75cf - receiveFrequency: 1621 -- proto: FirelockElectronics - entities: - - uid: 350 - components: - - type: Transform - parent: 224 - - type: Physics - canCollide: False - - uid: 351 - components: - - type: Transform - parent: 225 - - type: Physics - canCollide: False -- proto: FoodBoxDonut - entities: - - uid: 87 - components: - - type: Transform - pos: -2.470145,-2.3953476 - parent: 325 -- proto: GasPipeFourway - entities: - - uid: 216 - components: - - type: Transform - pos: -0.5,-1.5 - parent: 325 -- proto: GasPipeStraight - entities: - - uid: 211 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -0.5,-6.5 - parent: 325 - - uid: 213 - components: - - type: Transform - pos: -0.5,-4.5 - parent: 325 - - uid: 214 - components: - - type: Transform - pos: -0.5,-3.5 - parent: 325 - - uid: 215 - components: - - type: Transform - pos: -0.5,-2.5 - parent: 325 - - uid: 217 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -0.5,-0.5 - parent: 325 -- proto: GasPipeTJunction - entities: - - uid: 210 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -0.5,-7.5 - parent: 325 - - uid: 212 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -0.5,-5.5 - parent: 325 -- proto: GasPort - entities: - - uid: 59 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -0.5,-8.5 - parent: 325 - - type: AtmosDevice - joinedGrid: 325 -- proto: GasVentPump - entities: - - uid: 218 - components: - - type: Transform - pos: -0.5,0.5 - parent: 325 - - type: DeviceNetwork - address: Vnt-5f41a0ae - transmitFrequency: 1621 - receiveFrequency: 1621 - - type: AtmosDevice - joinedGrid: 325 - - uid: 219 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -1.5,-1.5 - parent: 325 - - type: DeviceNetwork - address: Vnt-129c27d2 - transmitFrequency: 1621 - receiveFrequency: 1621 - - type: AtmosDevice - joinedGrid: 325 - - uid: 220 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 0.5,-1.5 - parent: 325 - - type: DeviceNetwork - address: Vnt-11c4609d - transmitFrequency: 1621 - receiveFrequency: 1621 - - type: AtmosDevice - joinedGrid: 325 - - uid: 221 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 0.5,-5.5 - parent: 325 - - type: DeviceNetwork - address: Vnt-6859729f - transmitFrequency: 1621 - receiveFrequency: 1621 - - type: AtmosDevice - joinedGrid: 325 - - uid: 222 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -1.5,-7.5 - parent: 325 - - type: DeviceNetwork - address: Vnt-19d24c7f - transmitFrequency: 1621 - receiveFrequency: 1621 - - type: AtmosDevice - joinedGrid: 325 -- proto: GeneratorBasic15kW - entities: - - uid: 41 - components: - - type: Transform - pos: -2.5,-7.5 - parent: 325 - - type: PowerSupplier - supplyRampPosition: 7552.5303 - - type: ContainerContainer - containers: - machine_board: !type:Container - ents: - - 232 - machine_parts: !type:Container - ents: - - 233 - - 234 - - 235 - - uid: 56 - components: - - type: Transform - pos: 1.5,-7.5 - parent: 325 - - type: PowerSupplier - supplyRampPosition: 7552.5303 - - type: ContainerContainer - containers: - machine_board: !type:Container - ents: - - 236 - machine_parts: !type:Container - ents: - - 237 - - 238 - - 239 -- proto: GravityGeneratorMini - entities: - - uid: 57 - components: - - type: Transform - pos: -1.5,-8.5 - parent: 325 -- proto: Grille - entities: - - uid: 1 - components: - - type: Transform - pos: -0.5,2.5 - parent: 325 - - uid: 2 - components: - - type: Transform - pos: -1.5,2.5 - parent: 325 - - uid: 3 - components: - - type: Transform - pos: -1.5,1.5 - parent: 325 - - uid: 4 - components: - - type: Transform - pos: 0.5,2.5 - parent: 325 - - uid: 5 - components: - - type: Transform - pos: 0.5,1.5 - parent: 325 - - uid: 21 - components: - - type: Transform - pos: 3.5,-1.5 - parent: 325 - - uid: 50 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -2.5,-5.5 - parent: 325 - - uid: 51 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -2.5,-4.5 - parent: 325 - - uid: 52 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,-5.5 - parent: 325 - - uid: 53 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,-4.5 - parent: 325 -- proto: Gyroscope - entities: - - uid: 58 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 0.5,-8.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 240 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 241 - - 242 - - 243 - - 244 -- proto: GyroscopeMachineCircuitboard - entities: - - uid: 240 - components: - - type: Transform - parent: 58 - - type: Physics - canCollide: False -- proto: MedkitCombatFilled - entities: - - uid: 19 - components: - - type: Transform - pos: 1.48298,-0.3211529 - parent: 325 -- proto: MicroManipulatorStockPart - entities: - - uid: 250 - components: - - type: Transform - parent: 95 - - type: Physics - canCollide: False - - uid: 251 - components: - - type: Transform - parent: 95 - - type: Physics - canCollide: False - - uid: 252 - components: - - type: Transform - parent: 95 - - type: Physics - canCollide: False - - uid: 253 - components: - - type: Transform - parent: 95 - - type: Physics - canCollide: False - - uid: 257 - components: - - type: Transform - parent: 96 - - type: Physics - canCollide: False - - uid: 258 - components: - - type: Transform - parent: 96 - - type: Physics - canCollide: False - - uid: 259 - components: - - type: Transform - parent: 96 - - type: Physics - canCollide: False - - uid: 260 - components: - - type: Transform - parent: 96 - - type: Physics - canCollide: False - - uid: 264 - components: - - type: Transform - parent: 97 - - type: Physics - canCollide: False - - uid: 265 - components: - - type: Transform - parent: 97 - - type: Physics - canCollide: False - - uid: 266 - components: - - type: Transform - parent: 97 - - type: Physics - canCollide: False - - uid: 267 - components: - - type: Transform - parent: 97 - - type: Physics - canCollide: False - - uid: 271 - components: - - type: Transform - parent: 98 - - type: Physics - canCollide: False - - uid: 272 - components: - - type: Transform - parent: 98 - - type: Physics - canCollide: False - - uid: 273 - components: - - type: Transform - parent: 98 - - type: Physics - canCollide: False - - uid: 274 - components: - - type: Transform - parent: 98 - - type: Physics - canCollide: False - - uid: 278 - components: - - type: Transform - parent: 99 - - type: Physics - canCollide: False - - uid: 279 - components: - - type: Transform - parent: 99 - - type: Physics - canCollide: False - - uid: 280 - components: - - type: Transform - parent: 99 - - type: Physics - canCollide: False - - uid: 281 - components: - - type: Transform - parent: 99 - - type: Physics - canCollide: False - - uid: 285 - components: - - type: Transform - parent: 100 - - type: Physics - canCollide: False - - uid: 286 - components: - - type: Transform - parent: 100 - - type: Physics - canCollide: False - - uid: 287 - components: - - type: Transform - parent: 100 - - type: Physics - canCollide: False - - uid: 288 - components: - - type: Transform - parent: 100 - - type: Physics - canCollide: False - - uid: 292 - components: - - type: Transform - parent: 101 - - type: Physics - canCollide: False - - uid: 293 - components: - - type: Transform - parent: 101 - - type: Physics - canCollide: False - - uid: 294 - components: - - type: Transform - parent: 101 - - type: Physics - canCollide: False - - uid: 295 - components: - - type: Transform - parent: 101 - - type: Physics - canCollide: False - - uid: 299 - components: - - type: Transform - parent: 102 - - type: Physics - canCollide: False - - uid: 300 - components: - - type: Transform - parent: 102 - - type: Physics - canCollide: False - - uid: 301 - components: - - type: Transform - parent: 102 - - type: Physics - canCollide: False - - uid: 302 - components: - - type: Transform - parent: 102 - - type: Physics - canCollide: False -- proto: Mirror - entities: - - uid: 321 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -2.5,-3.5 - parent: 325 -- proto: NitrogenTankFilled - entities: - - uid: 105 - components: - - type: Transform - pos: 1.373605,-0.2749618 - parent: 325 -- proto: NukeCodePaper - entities: - - uid: 323 - components: - - type: Transform - pos: 1.561105,-2.5567772 - parent: 325 -- proto: PinpointerNuclear - entities: - - uid: 162 - components: - - type: Transform - pos: 1.3790641,-2.3161128 - parent: 325 - - type: Physics - canCollide: False -- proto: PlasmaReinforcedWindowDirectional - entities: - - uid: 104 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 0.5,-0.5 - parent: 325 - - uid: 109 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,-0.5 - parent: 325 -- proto: PlushieNuke - entities: - - uid: 47 - components: - - type: Transform - pos: 0.5061571,-5.233775 - parent: 325 -- proto: PortableGeneratorSuperPacmanMachineCircuitboard - entities: - - uid: 232 - components: - - type: Transform - parent: 41 - - type: Physics - canCollide: False - - uid: 236 - components: - - type: Transform - parent: 56 - - type: Physics - canCollide: False -- proto: PosterContrabandC20r - entities: - - uid: 24 - components: - - type: Transform - pos: 2.5,-2.5 - parent: 325 -- proto: PosterContrabandEnergySwords - entities: - - uid: 227 - components: - - type: Transform - pos: -2.5,-6.5 - parent: 325 -- proto: PosterContrabandNuclearDeviceInformational - entities: - - uid: 228 - components: - - type: Transform - pos: -2.5,0.5 - parent: 325 -- proto: PosterContrabandSyndicateRecruitment - entities: - - uid: 229 - components: - - type: Transform - pos: 0.5,-3.5 - parent: 325 -- proto: Poweredlight - entities: - - uid: 94 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 0.5,0.5 - parent: 325 - - uid: 110 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -2.5,-2.5 - parent: 325 -- proto: PoweredlightLED - entities: - - uid: 182 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,-5.5 - parent: 325 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 183 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -3.5,-5.5 - parent: 325 - - type: ApcPowerReceiver - powerLoad: 0 - - uid: 184 - components: - - type: Transform - pos: -1.5,-7.5 - parent: 325 - - type: ApcPowerReceiver - powerLoad: 0 -- proto: PoweredSmallLight - entities: - - uid: 204 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,-5.5 - parent: 325 - - type: ApcPowerReceiver - powerLoad: 0 -- proto: Rack - entities: - - uid: 83 - components: - - type: Transform - pos: 1.5,-0.5 - parent: 325 - - uid: 84 - components: - - type: Transform - pos: 1.5,-2.5 - parent: 325 -- proto: ReinforcedPlasmaWindow - entities: - - uid: 14 - components: - - type: Transform - pos: -1.5,1.5 - parent: 325 - - uid: 15 - components: - - type: Transform - pos: -1.5,2.5 - parent: 325 - - uid: 16 - components: - - type: Transform - pos: -0.5,2.5 - parent: 325 - - uid: 17 - components: - - type: Transform - pos: 0.5,2.5 - parent: 325 - - uid: 18 - components: - - type: Transform - pos: 0.5,1.5 - parent: 325 - - uid: 26 - components: - - type: Transform - pos: 3.5,-1.5 - parent: 325 - - uid: 42 - components: - - type: Transform - pos: 1.5,-4.5 - parent: 325 - - uid: 70 - components: - - type: Transform - pos: 1.5,-5.5 - parent: 325 - - uid: 71 - components: - - type: Transform - pos: -2.5,-4.5 - parent: 325 - - uid: 72 - components: - - type: Transform - pos: -2.5,-5.5 - parent: 325 -- proto: RemoteSignaller - entities: - - uid: 176 - components: - - type: Transform - pos: 1.3427892,-2.379079 - parent: 325 - - type: Physics - canCollide: False -- proto: SheetGlass1 - entities: - - uid: 244 - components: - - type: Transform - parent: 58 - - type: Stack - count: 2 - - type: Physics - canCollide: False -- proto: SheetSteel1 - entities: - - uid: 255 - components: - - type: Transform - parent: 95 - - type: Stack - count: 5 - - type: Physics - canCollide: False - - uid: 262 - components: - - type: Transform - parent: 96 - - type: Stack - count: 5 - - type: Physics - canCollide: False - - uid: 269 - components: - - type: Transform - parent: 97 - - type: Stack - count: 5 - - type: Physics - canCollide: False - - uid: 276 - components: - - type: Transform - parent: 98 - - type: Stack - count: 5 - - type: Physics - canCollide: False - - uid: 283 - components: - - type: Transform - parent: 99 - - type: Stack - count: 5 - - type: Physics - canCollide: False - - uid: 290 - components: - - type: Transform - parent: 100 - - type: Stack - count: 5 - - type: Physics - canCollide: False - - uid: 297 - components: - - type: Transform - parent: 101 - - type: Stack - count: 5 - - type: Physics - canCollide: False - - uid: 304 - components: - - type: Transform - parent: 102 - - type: Stack - count: 5 - - type: Physics - canCollide: False -- proto: SignalButton - entities: - - uid: 205 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,-3.5 - parent: 325 - - type: DeviceLinkSource - linkedPorts: - 193: - - Pressed: Toggle - 192: - - Pressed: Toggle - 190: - - Pressed: Toggle - 191: - - Pressed: Toggle - 196: - - Pressed: Toggle - 202: - - Pressed: Toggle - 201: - - Pressed: Toggle - 200: - - Pressed: Toggle - 199: - - Pressed: Toggle - 198: - - Pressed: Toggle -- proto: SignSpace - entities: - - uid: 230 - components: - - type: Transform - pos: -3.5,-0.5 - parent: 325 -- proto: SoapSyndie - entities: - - uid: 90 - components: - - type: Transform - pos: 0.5436061,-7.5129323 - parent: 325 -- proto: SpawnPointLoneNukeOperative - entities: - - uid: 322 - components: - - type: Transform - pos: -0.5,-4.5 - parent: 325 -- proto: StealthBox - entities: - - uid: 106 - components: - - type: Transform - pos: 0.49860507,-2.4513345 - parent: 325 - - type: Stealth - enabled: False - - type: EntityStorage - open: True -- proto: SubstationWallBasic - entities: - - uid: 103 - components: - - type: Transform - pos: -1.5,-6.5 - parent: 325 - - type: PowerNetworkBattery - loadingNetworkDemand: 15106.935 - currentReceiving: 15105.06 - currentSupply: 15106.935 - supplyRampPosition: 1.875 -- proto: SuitStorageSyndie - entities: - - uid: 67 - components: - - type: Transform - pos: 2.5,-1.5 - parent: 325 -- proto: SyndicateCommsComputerCircuitboard - entities: - - uid: 246 - components: - - type: Transform - parent: 65 - - type: Physics - canCollide: False -- proto: SyndicateComputerComms - entities: - - uid: 65 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -1.5,0.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 246 -- proto: SyndicateIDCard - entities: - - uid: 324 - components: - - type: Transform - pos: 1.57673,-2.3849022 - parent: 325 -- proto: SyndicateShuttleConsoleCircuitboard - entities: - - uid: 245 - components: - - type: Transform - parent: 64 - - type: Physics - canCollide: False -- proto: Table - entities: - - uid: 165 - components: - - type: Transform - pos: -2.5,-2.5 - parent: 325 -- proto: TableWood - entities: - - uid: 45 - components: - - type: Transform - pos: -1.5,-5.5 - parent: 325 -- proto: Thruster - entities: - - uid: 95 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -3.5,-9.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 249 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 250 - - 251 - - 252 - - 253 - - 254 - - 255 - - uid: 96 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 2.5,-9.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 256 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 257 - - 258 - - 259 - - 260 - - 261 - - 262 - - uid: 97 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,-4.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 263 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 264 - - 265 - - 266 - - 267 - - 268 - - 269 - - uid: 98 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,-5.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 270 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 271 - - 272 - - 273 - - 274 - - 275 - - 276 - - uid: 99 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -3.5,-4.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 277 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 278 - - 279 - - 280 - - 281 - - 282 - - 283 - - uid: 100 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -3.5,-5.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 284 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 285 - - 286 - - 287 - - 288 - - 289 - - 290 - - uid: 101 - components: - - type: Transform - pos: -3.5,1.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 291 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 292 - - 293 - - 294 - - 295 - - 296 - - 297 - - uid: 102 - components: - - type: Transform - pos: 2.5,1.5 - parent: 325 - - type: ContainerContainer - containers: - machine_board: !type:Container - showEnts: False - occludes: True - ents: - - 298 - machine_parts: !type:Container - showEnts: False - occludes: True - ents: - - 299 - - 300 - - 301 - - 302 - - 303 - - 304 -- proto: ThrusterMachineCircuitboard - entities: - - uid: 249 - components: - - type: Transform - parent: 95 - - type: Physics - canCollide: False - - uid: 256 - components: - - type: Transform - parent: 96 - - type: Physics - canCollide: False - - uid: 263 - components: - - type: Transform - parent: 97 - - type: Physics - canCollide: False - - uid: 270 - components: - - type: Transform - parent: 98 - - type: Physics - canCollide: False - - uid: 277 - components: - - type: Transform - parent: 99 - - type: Physics - canCollide: False - - uid: 284 - components: - - type: Transform - parent: 100 - - type: Physics - canCollide: False - - uid: 291 - components: - - type: Transform - parent: 101 - - type: Physics - canCollide: False - - uid: 298 - components: - - type: Transform - parent: 102 - - type: Physics - canCollide: False -- proto: ToolboxSyndicateFilled - entities: - - uid: 177 - components: - - type: Transform - pos: 1.5699697,-0.44908836 - parent: 325 - - type: Physics - canCollide: False -- proto: ToyFigurineNukie - entities: - - uid: 10 - components: - - type: Transform - pos: -2.3371089,-2.140279 - parent: 325 -- proto: VendingMachineSyndieDrobe - entities: - - uid: 163 - components: - - type: Transform - pos: -2.5,-0.5 - parent: 325 -- proto: WallPlastitanium - entities: - - uid: 7 - components: - - type: Transform - pos: -2.5,0.5 - parent: 325 - - uid: 8 - components: - - type: Transform - pos: -3.5,0.5 - parent: 325 - - uid: 9 - components: - - type: Transform - pos: -3.5,-0.5 - parent: 325 - - uid: 11 - components: - - type: Transform - pos: 1.5,0.5 - parent: 325 - - uid: 12 - components: - - type: Transform - pos: 2.5,0.5 - parent: 325 - - uid: 13 - components: - - type: Transform - pos: -4.5,-0.5 - parent: 325 - - uid: 22 - components: - - type: Transform - pos: 3.5,-0.5 - parent: 325 - - uid: 25 - components: - - type: Transform - pos: 3.5,-2.5 - parent: 325 - - uid: 27 - components: - - type: Transform - pos: -3.5,-2.5 - parent: 325 - - uid: 28 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -3.5,-6.5 - parent: 325 - - uid: 29 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,-6.5 - parent: 325 - - uid: 30 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -2.5,-6.5 - parent: 325 - - uid: 31 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -3.5,-8.5 - parent: 325 - - uid: 32 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -2.5,-8.5 - parent: 325 - - uid: 33 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,-6.5 - parent: 325 - - uid: 34 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,-3.5 - parent: 325 - - uid: 35 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -2.5,-9.5 - parent: 325 - - uid: 36 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,-8.5 - parent: 325 - - uid: 37 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,-9.5 - parent: 325 - - uid: 38 - components: - - type: Transform - pos: -4.5,-2.5 - parent: 325 - - uid: 39 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,-3.5 - parent: 325 - - uid: 44 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 2.5,-8.5 - parent: 325 - - uid: 48 - components: - - type: Transform - pos: 2.5,-7.5 - parent: 325 - - uid: 49 - components: - - type: Transform - pos: -3.5,-7.5 - parent: 325 - - uid: 54 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -2.5,-3.5 - parent: 325 - - uid: 55 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: -3.5,-3.5 - parent: 325 - - uid: 60 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,-6.5 - parent: 325 - - uid: 61 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 0.5,-6.5 - parent: 325 - - uid: 62 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -1.5,-3.5 - parent: 325 - - uid: 63 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 0.5,-3.5 - parent: 325 - - uid: 66 - components: - - type: Transform - pos: 0.5,-9.5 - parent: 325 - - uid: 69 - components: - - type: Transform - pos: -2.5,1.5 - parent: 325 - - uid: 73 - components: - - type: Transform - pos: 1.5,1.5 - parent: 325 - - uid: 80 - components: - - type: Transform - pos: 2.5,-2.5 - parent: 325 - - uid: 81 - components: - - type: Transform - pos: 2.5,-0.5 - parent: 325 - - uid: 92 - components: - - type: Transform - pos: -1.5,-9.5 - parent: 325 - - uid: 108 - components: - - type: Transform - pos: -0.5,-9.5 - parent: 325 -- proto: WallPlastitaniumDiagonal - entities: - - uid: 23 - components: - - type: Transform - pos: -4.5,0.5 - parent: 325 - - uid: 43 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 3.5,0.5 - parent: 325 - - uid: 68 - components: - - type: Transform - rot: -1.5707963267948966 rad - pos: 1.5,2.5 - parent: 325 - - uid: 79 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -4.5,-3.5 - parent: 325 - - uid: 82 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: 3.5,-3.5 - parent: 325 - - uid: 86 - components: - - type: Transform - pos: -2.5,2.5 - parent: 325 -- proto: WindoorSecure - entities: - - uid: 166 - components: - - type: Transform - rot: 1.5707963267948966 rad - pos: -3.5,-1.5 - parent: 325 - - uid: 206 - components: - - type: Transform - rot: 3.141592653589793 rad - pos: -0.5,-0.5 - parent: 325 - - type: ContainerContainer - containers: - board: !type:Container - showEnts: False - occludes: True - ents: - - 346 -- proto: YellowOxygenTankFilled - entities: - - uid: 167 - components: - - type: Transform - pos: 1.60798,-0.3062118 - parent: 325 -... +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 29: FloorDark + 84: FloorShuttleRed + 104: FloorTechMaint + 105: FloorTechMaint2 + 118: FloorWood + 120: Lattice + 121: Plating +entities: +- proto: "" + entities: + - uid: 325 + components: + - type: MetaData + - type: Transform + pos: 0.5638949,0.47865233 + parent: invalid + - type: MapGrid + chunks: + -1,-1: + ind: -1,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAaAAAAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAdgAAAAAAdgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAdgAAAAADdgAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAaQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAHQAAAAADHQAAAAADHQAAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaQAAAAAAaQAAAAAAHQAAAAABHQAAAAABHQAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAHQAAAAABHQAAAAACHQAAAAAB + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAaAAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdgAAAAACeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAdgAAAAABeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAAAHQAAAAABeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAABHQAAAAABHQAAAAABeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHQAAAAADHQAAAAACeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + -1,0: + ind: -1,0 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAVAAAAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAVAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeAAAAAAAeQAAAAAAeQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,0: + ind: 0,0 + tiles: VAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAeQAAAAAAeAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: OccluderTree + - type: Shuttle + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: + - node: + color: '#FFFFFFFF' + id: BrickTileDarkCornerNe + decals: + 11: 1,-1 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkCornerNw + decals: + 5: -3,-1 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkCornerSe + decals: + 4: 1,-3 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkCornerSw + decals: + 3: -3,-3 + - node: + color: '#FFFFFFFF' + id: BrickTileDarkLineS + decals: + 0: -1,-3 + 1: -2,-3 + 2: 0,-3 + - node: + color: '#7F1C1FFF' + id: BrickTileWhiteCornerNe + decals: + 13: 1,-1 + - node: + color: '#7F1C1FFF' + id: BrickTileWhiteCornerNw + decals: + 12: -3,-1 + - node: + color: '#7F1C1FFF' + id: BrickTileWhiteCornerSe + decals: + 9: 1,-3 + - node: + color: '#7F1C1FFF' + id: BrickTileWhiteCornerSw + decals: + 10: -3,-3 + - node: + color: '#7F1C1FFF' + id: BrickTileWhiteLineS + decals: + 6: -2,-3 + 7: -1,-3 + 8: 0,-3 + - node: + color: '#FFFFFFFF' + id: Delivery + decals: + 23: 2,-2 + 24: -4,-2 + - node: + color: '#FFFFFFFF' + id: WarnLineE + decals: + 14: 1,-2 + - node: + color: '#FFFFFFFF' + id: WarnLineS + decals: + 16: -3,-2 + - node: + color: '#FFFFFFFF' + id: WarnLineW + decals: + 15: -1,-1 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinLineN + decals: + 17: -1,-5 + 18: 0,-5 + 19: -2,-5 + - node: + color: '#FFFFFFFF' + id: WoodTrimThinLineS + decals: + 20: -2,-6 + 21: -1,-6 + 22: 0,-6 + - type: GridAtmosphere + version: 2 + data: + tiles: + -1,-1: + 0: 65535 + 0,-1: + 0: 65535 + -2,-1: + 0: 52424 + -1,-3: + 0: 65280 + -1,-2: + 0: 65535 + 0,-3: + 0: 30464 + 0,-2: + 0: 30583 + -2,0: + 0: 8 + -1,0: + 0: 3839 + 0,0: + 0: 895 + uniqueMixes: + - volume: 2500 + temperature: 293.15 + moles: + - 21.824879 + - 82.10312 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + chunkSize: 4 + - type: GasTileOverlay + - type: RadiationGridResistance + - type: GravityShake + shakeTimes: 10 + - type: SpreaderGrid + - type: GridPathfinding +- proto: AirCanister + entities: + - uid: 91 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 325 + - type: AtmosDevice + joinedGrid: 325 +- proto: AirlockExternalShuttleSyndicateLocked + entities: + - uid: 142 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -4.5,-1.5 + parent: 325 +- proto: AirlockSyndicateLocked + entities: + - uid: 20 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 325 + - uid: 88 + components: + - type: Transform + pos: -0.5,-6.5 + parent: 325 +- proto: APCBasic + entities: + - uid: 107 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 325 + - type: PowerNetworkBattery + loadingNetworkDemand: 15107 + currentReceiving: 15106.935 + currentSupply: 15107 + supplyRampPosition: 0.064453125 +- proto: AtmosDeviceFanTiny + entities: + - uid: 6 + components: + - type: Transform + pos: -3.5,-1.5 + parent: 325 +- proto: Bed + entities: + - uid: 76 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 325 +- proto: BedsheetSyndie + entities: + - uid: 164 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 325 +- proto: BlastDoorOpen + entities: + - uid: 190 + components: + - type: Transform + pos: 1.5,-5.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 331 + - type: DeviceLinkSink + links: + - 205 + - uid: 191 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 332 + - type: DeviceLinkSink + links: + - 205 + - uid: 192 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 333 + - type: DeviceLinkSink + links: + - 205 + - uid: 193 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 334 + - type: DeviceLinkSink + links: + - 205 + - uid: 196 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 337 + - type: DeviceLinkSink + links: + - 205 + - uid: 198 + components: + - type: Transform + pos: -1.5,1.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 339 + - type: DeviceLinkSink + links: + - 205 + - uid: 199 + components: + - type: Transform + pos: -1.5,2.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 340 + - type: DeviceLinkSink + links: + - 205 + - uid: 200 + components: + - type: Transform + pos: -0.5,2.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 341 + - type: DeviceLinkSink + links: + - 205 + - uid: 201 + components: + - type: Transform + pos: 0.5,2.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 342 + - type: DeviceLinkSink + links: + - 205 + - uid: 202 + components: + - type: Transform + pos: 0.5,1.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 343 + - type: DeviceLinkSink + links: + - 205 +- proto: BoxMRE + entities: + - uid: 320 + components: + - type: Transform + pos: 0.70504504,-7.29326 + parent: 325 +- proto: CableApcExtension + entities: + - uid: 120 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 325 + - uid: 121 + components: + - type: Transform + pos: -0.5,-6.5 + parent: 325 + - uid: 122 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 325 + - uid: 123 + components: + - type: Transform + pos: -0.5,-8.5 + parent: 325 + - uid: 124 + components: + - type: Transform + pos: -1.5,-8.5 + parent: 325 + - uid: 125 + components: + - type: Transform + pos: 0.5,-8.5 + parent: 325 + - uid: 126 + components: + - type: Transform + pos: 1.5,-8.5 + parent: 325 + - uid: 127 + components: + - type: Transform + pos: -2.5,-8.5 + parent: 325 + - uid: 128 + components: + - type: Transform + pos: -3.5,-8.5 + parent: 325 + - uid: 129 + components: + - type: Transform + pos: -3.5,-7.5 + parent: 325 + - uid: 130 + components: + - type: Transform + pos: 2.5,-8.5 + parent: 325 + - uid: 131 + components: + - type: Transform + pos: 2.5,-7.5 + parent: 325 + - uid: 132 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 325 + - uid: 133 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 325 + - uid: 134 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 325 + - uid: 135 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 325 + - uid: 136 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 325 + - uid: 137 + components: + - type: Transform + pos: -0.5,-0.5 + parent: 325 + - uid: 138 + components: + - type: Transform + pos: -0.5,0.5 + parent: 325 + - uid: 139 + components: + - type: Transform + pos: -0.5,1.5 + parent: 325 + - uid: 140 + components: + - type: Transform + pos: -0.5,2.5 + parent: 325 + - uid: 141 + components: + - type: Transform + pos: -1.5,1.5 + parent: 325 + - uid: 143 + components: + - type: Transform + pos: 0.5,1.5 + parent: 325 + - uid: 145 + components: + - type: Transform + pos: -1.5,-1.5 + parent: 325 + - uid: 146 + components: + - type: Transform + pos: -2.5,-1.5 + parent: 325 + - uid: 147 + components: + - type: Transform + pos: -3.5,-1.5 + parent: 325 + - uid: 148 + components: + - type: Transform + pos: -4.5,-1.5 + parent: 325 + - uid: 149 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 325 + - uid: 150 + components: + - type: Transform + pos: 1.5,-1.5 + parent: 325 + - uid: 151 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 325 + - uid: 152 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 325 + - uid: 153 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 325 + - uid: 154 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 325 + - uid: 155 + components: + - type: Transform + pos: 1.5,-5.5 + parent: 325 + - uid: 156 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 325 + - uid: 157 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 325 + - uid: 158 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 325 +- proto: CableHV + entities: + - uid: 111 + components: + - type: Transform + pos: 1.5,-7.5 + parent: 325 + - uid: 112 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 325 + - uid: 113 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 325 + - uid: 114 + components: + - type: Transform + pos: -1.5,-7.5 + parent: 325 + - uid: 115 + components: + - type: Transform + pos: -2.5,-7.5 + parent: 325 + - uid: 116 + components: + - type: Transform + pos: -1.5,-6.5 + parent: 325 +- proto: CableHVStack1 + entities: + - uid: 235 + components: + - type: Transform + parent: 41 + - type: Stack + count: 10 + - type: Physics + canCollide: False + - uid: 239 + components: + - type: Transform + parent: 56 + - type: Stack + count: 10 + - type: Physics + canCollide: False +- proto: CableMV + entities: + - uid: 117 + components: + - type: Transform + pos: -1.5,-6.5 + parent: 325 + - uid: 118 + components: + - type: Transform + pos: -0.5,-6.5 + parent: 325 + - uid: 119 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 325 +- proto: CapacitorStockPart + entities: + - uid: 233 + components: + - type: Transform + parent: 41 + - type: Physics + canCollide: False + - uid: 234 + components: + - type: Transform + parent: 41 + - type: Physics + canCollide: False + - uid: 237 + components: + - type: Transform + parent: 56 + - type: Physics + canCollide: False + - uid: 238 + components: + - type: Transform + parent: 56 + - type: Physics + canCollide: False + - uid: 241 + components: + - type: Transform + parent: 58 + - type: Physics + canCollide: False + - uid: 242 + components: + - type: Transform + parent: 58 + - type: Physics + canCollide: False + - uid: 243 + components: + - type: Transform + parent: 58 + - type: Physics + canCollide: False + - uid: 254 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - uid: 261 + components: + - type: Transform + parent: 96 + - type: Physics + canCollide: False + - uid: 268 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - uid: 275 + components: + - type: Transform + parent: 98 + - type: Physics + canCollide: False + - uid: 282 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - uid: 289 + components: + - type: Transform + parent: 100 + - type: Physics + canCollide: False + - uid: 296 + components: + - type: Transform + parent: 101 + - type: Physics + canCollide: False + - uid: 303 + components: + - type: Transform + parent: 102 + - type: Physics + canCollide: False +- proto: Carpet + entities: + - uid: 74 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 325 + - uid: 89 + components: + - type: Transform + pos: -0.5,-5.5 + parent: 325 +- proto: Catwalk + entities: + - uid: 159 + components: + - type: Transform + pos: -1.5,-7.5 + parent: 325 + - uid: 160 + components: + - type: Transform + pos: -0.5,-7.5 + parent: 325 + - uid: 161 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 325 +- proto: ChairOfficeDark + entities: + - uid: 93 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -1.5,-2.5 + parent: 325 +- proto: ChairPilotSeat + entities: + - uid: 78 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,0.5 + parent: 325 +- proto: ComputerIFFSyndicate + entities: + - uid: 40 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,0.5 + parent: 325 +- proto: ComputerShuttleSyndie + entities: + - uid: 64 + components: + - type: Transform + pos: -0.5,1.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 245 +- proto: CyberPen + entities: + - uid: 77 + components: + - type: Transform + pos: -1.1813428,-5.15565 + parent: 325 +- proto: DoorElectronics + entities: + - uid: 331 + components: + - type: Transform + parent: 190 + - type: Physics + canCollide: False + - uid: 332 + components: + - type: Transform + parent: 191 + - type: Physics + canCollide: False + - uid: 333 + components: + - type: Transform + parent: 192 + - type: Physics + canCollide: False + - uid: 334 + components: + - type: Transform + parent: 193 + - type: Physics + canCollide: False + - uid: 337 + components: + - type: Transform + parent: 196 + - type: Physics + canCollide: False + - uid: 339 + components: + - type: Transform + parent: 198 + - type: Physics + canCollide: False + - uid: 340 + components: + - type: Transform + parent: 199 + - type: Physics + canCollide: False + - uid: 341 + components: + - type: Transform + parent: 200 + - type: Physics + canCollide: False + - uid: 342 + components: + - type: Transform + parent: 201 + - type: Physics + canCollide: False + - uid: 343 + components: + - type: Transform + parent: 202 + - type: Physics + canCollide: False + - uid: 346 + components: + - type: Transform + parent: 206 + - type: Physics + canCollide: False +- proto: DresserFilled + entities: + - uid: 85 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 325 +- proto: DrinkNukieCan + entities: + - uid: 144 + components: + - type: Transform + pos: -2.6964839,-2.109029 + parent: 325 +- proto: FaxMachineSyndie + entities: + - uid: 46 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 325 + - type: FaxMachine + name: Striker +- proto: filingCabinetRandom + entities: + - uid: 75 + components: + - type: Transform + pos: -1.5,-4.5 + parent: 325 +- proto: Firelock + entities: + - uid: 224 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 350 + - type: DeviceNetwork + address: 44a24659 + receiveFrequency: 1621 + - uid: 225 + components: + - type: Transform + pos: -0.5,-6.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 351 + - type: DeviceNetwork + address: 6fdb75cf + receiveFrequency: 1621 +- proto: FirelockElectronics + entities: + - uid: 350 + components: + - type: Transform + parent: 224 + - type: Physics + canCollide: False + - uid: 351 + components: + - type: Transform + parent: 225 + - type: Physics + canCollide: False +- proto: FoodBoxDonut + entities: + - uid: 87 + components: + - type: Transform + pos: -2.470145,-2.3953476 + parent: 325 +- proto: GasPipeFourway + entities: + - uid: 216 + components: + - type: Transform + pos: -0.5,-1.5 + parent: 325 +- proto: GasPipeStraight + entities: + - uid: 211 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-6.5 + parent: 325 + - uid: 213 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 325 + - uid: 214 + components: + - type: Transform + pos: -0.5,-3.5 + parent: 325 + - uid: 215 + components: + - type: Transform + pos: -0.5,-2.5 + parent: 325 + - uid: 217 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-0.5 + parent: 325 +- proto: GasPipeTJunction + entities: + - uid: 210 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -0.5,-7.5 + parent: 325 + - uid: 212 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -0.5,-5.5 + parent: 325 +- proto: GasPort + entities: + - uid: 59 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-8.5 + parent: 325 + - type: AtmosDevice + joinedGrid: 325 +- proto: GasVentPump + entities: + - uid: 218 + components: + - type: Transform + pos: -0.5,0.5 + parent: 325 + - type: DeviceNetwork + address: Vnt-5f41a0ae + transmitFrequency: 1621 + receiveFrequency: 1621 + - type: AtmosDevice + joinedGrid: 325 + - uid: 219 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-1.5 + parent: 325 + - type: DeviceNetwork + address: Vnt-129c27d2 + transmitFrequency: 1621 + receiveFrequency: 1621 + - type: AtmosDevice + joinedGrid: 325 + - uid: 220 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-1.5 + parent: 325 + - type: DeviceNetwork + address: Vnt-11c4609d + transmitFrequency: 1621 + receiveFrequency: 1621 + - type: AtmosDevice + joinedGrid: 325 + - uid: 221 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-5.5 + parent: 325 + - type: DeviceNetwork + address: Vnt-6859729f + transmitFrequency: 1621 + receiveFrequency: 1621 + - type: AtmosDevice + joinedGrid: 325 + - uid: 222 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,-7.5 + parent: 325 + - type: DeviceNetwork + address: Vnt-19d24c7f + transmitFrequency: 1621 + receiveFrequency: 1621 + - type: AtmosDevice + joinedGrid: 325 +- proto: GeneratorBasic15kW + entities: + - uid: 41 + components: + - type: Transform + pos: -2.5,-7.5 + parent: 325 + - type: PowerSupplier + supplyRampPosition: 7552.5303 + - type: ContainerContainer + containers: + machine_board: !type:Container + ents: + - 232 + machine_parts: !type:Container + ents: + - 233 + - 234 + - 235 + - uid: 56 + components: + - type: Transform + pos: 1.5,-7.5 + parent: 325 + - type: PowerSupplier + supplyRampPosition: 7552.5303 + - type: ContainerContainer + containers: + machine_board: !type:Container + ents: + - 236 + machine_parts: !type:Container + ents: + - 237 + - 238 + - 239 +- proto: GravityGeneratorMini + entities: + - uid: 57 + components: + - type: Transform + pos: -1.5,-8.5 + parent: 325 +- proto: Grille + entities: + - uid: 1 + components: + - type: Transform + pos: -0.5,2.5 + parent: 325 + - uid: 2 + components: + - type: Transform + pos: -1.5,2.5 + parent: 325 + - uid: 3 + components: + - type: Transform + pos: -1.5,1.5 + parent: 325 + - uid: 4 + components: + - type: Transform + pos: 0.5,2.5 + parent: 325 + - uid: 5 + components: + - type: Transform + pos: 0.5,1.5 + parent: 325 + - uid: 21 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 325 + - uid: 50 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-5.5 + parent: 325 + - uid: 51 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-4.5 + parent: 325 + - uid: 52 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-5.5 + parent: 325 + - uid: 53 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-4.5 + parent: 325 +- proto: Gyroscope + entities: + - uid: 58 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,-8.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 240 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 241 + - 242 + - 243 + - 244 +- proto: GyroscopeMachineCircuitboard + entities: + - uid: 240 + components: + - type: Transform + parent: 58 + - type: Physics + canCollide: False +- proto: MedkitCombatFilled + entities: + - uid: 19 + components: + - type: Transform + pos: 1.48298,-0.3211529 + parent: 325 +- proto: MicroManipulatorStockPart + entities: + - uid: 250 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - uid: 251 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - uid: 252 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - uid: 253 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - uid: 257 + components: + - type: Transform + parent: 96 + - type: Physics + canCollide: False + - uid: 258 + components: + - type: Transform + parent: 96 + - type: Physics + canCollide: False + - uid: 259 + components: + - type: Transform + parent: 96 + - type: Physics + canCollide: False + - uid: 260 + components: + - type: Transform + parent: 96 + - type: Physics + canCollide: False + - uid: 264 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - uid: 265 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - uid: 266 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - uid: 267 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - uid: 271 + components: + - type: Transform + parent: 98 + - type: Physics + canCollide: False + - uid: 272 + components: + - type: Transform + parent: 98 + - type: Physics + canCollide: False + - uid: 273 + components: + - type: Transform + parent: 98 + - type: Physics + canCollide: False + - uid: 274 + components: + - type: Transform + parent: 98 + - type: Physics + canCollide: False + - uid: 278 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - uid: 279 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - uid: 280 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - uid: 281 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - uid: 285 + components: + - type: Transform + parent: 100 + - type: Physics + canCollide: False + - uid: 286 + components: + - type: Transform + parent: 100 + - type: Physics + canCollide: False + - uid: 287 + components: + - type: Transform + parent: 100 + - type: Physics + canCollide: False + - uid: 288 + components: + - type: Transform + parent: 100 + - type: Physics + canCollide: False + - uid: 292 + components: + - type: Transform + parent: 101 + - type: Physics + canCollide: False + - uid: 293 + components: + - type: Transform + parent: 101 + - type: Physics + canCollide: False + - uid: 294 + components: + - type: Transform + parent: 101 + - type: Physics + canCollide: False + - uid: 295 + components: + - type: Transform + parent: 101 + - type: Physics + canCollide: False + - uid: 299 + components: + - type: Transform + parent: 102 + - type: Physics + canCollide: False + - uid: 300 + components: + - type: Transform + parent: 102 + - type: Physics + canCollide: False + - uid: 301 + components: + - type: Transform + parent: 102 + - type: Physics + canCollide: False + - uid: 302 + components: + - type: Transform + parent: 102 + - type: Physics + canCollide: False +- proto: Mirror + entities: + - uid: 321 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-3.5 + parent: 325 +- proto: NitrogenTankFilled + entities: + - uid: 105 + components: + - type: Transform + pos: 1.373605,-0.2749618 + parent: 325 +- proto: NukeCodePaper + entities: + - uid: 323 + components: + - type: Transform + pos: 1.561105,-2.5567772 + parent: 325 +- proto: PinpointerNuclear + entities: + - uid: 162 + components: + - type: Transform + pos: 1.3790641,-2.3161128 + parent: 325 + - type: Physics + canCollide: False +- proto: PlasmaReinforcedWindowDirectional + entities: + - uid: 104 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-0.5 + parent: 325 + - uid: 109 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-0.5 + parent: 325 +- proto: PlushieNuke + entities: + - uid: 47 + components: + - type: Transform + pos: 0.5061571,-5.233775 + parent: 325 +- proto: PortableGeneratorSuperPacmanMachineCircuitboard + entities: + - uid: 232 + components: + - type: Transform + parent: 41 + - type: Physics + canCollide: False + - uid: 236 + components: + - type: Transform + parent: 56 + - type: Physics + canCollide: False +- proto: PosterContrabandC20r + entities: + - uid: 24 + components: + - type: Transform + pos: 2.5,-2.5 + parent: 325 +- proto: PosterContrabandEnergySwords + entities: + - uid: 227 + components: + - type: Transform + pos: -2.5,-6.5 + parent: 325 +- proto: PosterContrabandNuclearDeviceInformational + entities: + - uid: 228 + components: + - type: Transform + pos: -2.5,0.5 + parent: 325 +- proto: PosterContrabandSyndicateRecruitment + entities: + - uid: 229 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 325 +- proto: Poweredlight + entities: + - uid: 94 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 0.5,0.5 + parent: 325 + - uid: 110 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -2.5,-2.5 + parent: 325 +- proto: PoweredlightLED + entities: + - uid: 182 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-5.5 + parent: 325 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 183 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,-5.5 + parent: 325 + - type: ApcPowerReceiver + powerLoad: 0 + - uid: 184 + components: + - type: Transform + pos: -1.5,-7.5 + parent: 325 + - type: ApcPowerReceiver + powerLoad: 0 +- proto: PoweredSmallLight + entities: + - uid: 204 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-5.5 + parent: 325 + - type: ApcPowerReceiver + powerLoad: 0 +- proto: Rack + entities: + - uid: 83 + components: + - type: Transform + pos: 1.5,-0.5 + parent: 325 + - uid: 84 + components: + - type: Transform + pos: 1.5,-2.5 + parent: 325 +- proto: ReinforcedPlasmaWindow + entities: + - uid: 14 + components: + - type: Transform + pos: -1.5,1.5 + parent: 325 + - uid: 15 + components: + - type: Transform + pos: -1.5,2.5 + parent: 325 + - uid: 16 + components: + - type: Transform + pos: -0.5,2.5 + parent: 325 + - uid: 17 + components: + - type: Transform + pos: 0.5,2.5 + parent: 325 + - uid: 18 + components: + - type: Transform + pos: 0.5,1.5 + parent: 325 + - uid: 26 + components: + - type: Transform + pos: 3.5,-1.5 + parent: 325 + - uid: 42 + components: + - type: Transform + pos: 1.5,-4.5 + parent: 325 + - uid: 70 + components: + - type: Transform + pos: 1.5,-5.5 + parent: 325 + - uid: 71 + components: + - type: Transform + pos: -2.5,-4.5 + parent: 325 + - uid: 72 + components: + - type: Transform + pos: -2.5,-5.5 + parent: 325 +- proto: RemoteSignaller + entities: + - uid: 176 + components: + - type: Transform + pos: 1.3427892,-2.379079 + parent: 325 + - type: Physics + canCollide: False +- proto: SheetGlass1 + entities: + - uid: 244 + components: + - type: Transform + parent: 58 + - type: Stack + count: 2 + - type: Physics + canCollide: False +- proto: SheetSteel1 + entities: + - uid: 255 + components: + - type: Transform + parent: 95 + - type: Stack + count: 5 + - type: Physics + canCollide: False + - uid: 262 + components: + - type: Transform + parent: 96 + - type: Stack + count: 5 + - type: Physics + canCollide: False + - uid: 269 + components: + - type: Transform + parent: 97 + - type: Stack + count: 5 + - type: Physics + canCollide: False + - uid: 276 + components: + - type: Transform + parent: 98 + - type: Stack + count: 5 + - type: Physics + canCollide: False + - uid: 283 + components: + - type: Transform + parent: 99 + - type: Stack + count: 5 + - type: Physics + canCollide: False + - uid: 290 + components: + - type: Transform + parent: 100 + - type: Stack + count: 5 + - type: Physics + canCollide: False + - uid: 297 + components: + - type: Transform + parent: 101 + - type: Stack + count: 5 + - type: Physics + canCollide: False + - uid: 304 + components: + - type: Transform + parent: 102 + - type: Stack + count: 5 + - type: Physics + canCollide: False +- proto: SignalButton + entities: + - uid: 205 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-3.5 + parent: 325 + - type: DeviceLinkSource + linkedPorts: + 193: + - Pressed: Toggle + 192: + - Pressed: Toggle + 190: + - Pressed: Toggle + 191: + - Pressed: Toggle + 196: + - Pressed: Toggle + 202: + - Pressed: Toggle + 201: + - Pressed: Toggle + 200: + - Pressed: Toggle + 199: + - Pressed: Toggle + 198: + - Pressed: Toggle +- proto: SignSpace + entities: + - uid: 230 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 325 +- proto: SoapSyndie + entities: + - uid: 90 + components: + - type: Transform + pos: 0.5436061,-7.5129323 + parent: 325 +- proto: SpawnPointNukies + entities: + - uid: 322 + components: + - type: Transform + pos: -0.5,-4.5 + parent: 325 +- proto: StealthBox + entities: + - uid: 106 + components: + - type: Transform + pos: 0.49860507,-2.4513345 + parent: 325 + - type: Stealth + enabled: False + - type: EntityStorage + open: True +- proto: SubstationWallBasic + entities: + - uid: 103 + components: + - type: Transform + pos: -1.5,-6.5 + parent: 325 + - type: PowerNetworkBattery + loadingNetworkDemand: 15106.935 + currentReceiving: 15105.06 + currentSupply: 15106.935 + supplyRampPosition: 1.875 +- proto: SuitStorageSyndie + entities: + - uid: 67 + components: + - type: Transform + pos: 2.5,-1.5 + parent: 325 +- proto: SyndicateCommsComputerCircuitboard + entities: + - uid: 246 + components: + - type: Transform + parent: 65 + - type: Physics + canCollide: False +- proto: SyndicateComputerComms + entities: + - uid: 65 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -1.5,0.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 246 +- proto: SyndicateIDCard + entities: + - uid: 324 + components: + - type: Transform + pos: 1.57673,-2.3849022 + parent: 325 +- proto: SyndicateShuttleConsoleCircuitboard + entities: + - uid: 245 + components: + - type: Transform + parent: 64 + - type: Physics + canCollide: False +- proto: Table + entities: + - uid: 165 + components: + - type: Transform + pos: -2.5,-2.5 + parent: 325 +- proto: TableWood + entities: + - uid: 45 + components: + - type: Transform + pos: -1.5,-5.5 + parent: 325 +- proto: Thruster + entities: + - uid: 95 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -3.5,-9.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 249 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 250 + - 251 + - 252 + - 253 + - 254 + - 255 + - uid: 96 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 2.5,-9.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 256 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 257 + - 258 + - 259 + - 260 + - 261 + - 262 + - uid: 97 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-4.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 263 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 264 + - 265 + - 266 + - 267 + - 268 + - 269 + - uid: 98 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-5.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 270 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 271 + - 272 + - 273 + - 274 + - 275 + - 276 + - uid: 99 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-4.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 277 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 278 + - 279 + - 280 + - 281 + - 282 + - 283 + - uid: 100 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-5.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 284 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 285 + - 286 + - 287 + - 288 + - 289 + - 290 + - uid: 101 + components: + - type: Transform + pos: -3.5,1.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 291 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 292 + - 293 + - 294 + - 295 + - 296 + - 297 + - uid: 102 + components: + - type: Transform + pos: 2.5,1.5 + parent: 325 + - type: ContainerContainer + containers: + machine_board: !type:Container + showEnts: False + occludes: True + ents: + - 298 + machine_parts: !type:Container + showEnts: False + occludes: True + ents: + - 299 + - 300 + - 301 + - 302 + - 303 + - 304 +- proto: ThrusterMachineCircuitboard + entities: + - uid: 249 + components: + - type: Transform + parent: 95 + - type: Physics + canCollide: False + - uid: 256 + components: + - type: Transform + parent: 96 + - type: Physics + canCollide: False + - uid: 263 + components: + - type: Transform + parent: 97 + - type: Physics + canCollide: False + - uid: 270 + components: + - type: Transform + parent: 98 + - type: Physics + canCollide: False + - uid: 277 + components: + - type: Transform + parent: 99 + - type: Physics + canCollide: False + - uid: 284 + components: + - type: Transform + parent: 100 + - type: Physics + canCollide: False + - uid: 291 + components: + - type: Transform + parent: 101 + - type: Physics + canCollide: False + - uid: 298 + components: + - type: Transform + parent: 102 + - type: Physics + canCollide: False +- proto: ToolboxSyndicateFilled + entities: + - uid: 177 + components: + - type: Transform + pos: 1.5699697,-0.44908836 + parent: 325 + - type: Physics + canCollide: False +- proto: ToyFigurineNukie + entities: + - uid: 10 + components: + - type: Transform + pos: -2.3371089,-2.140279 + parent: 325 +- proto: VendingMachineSyndieDrobe + entities: + - uid: 163 + components: + - type: Transform + pos: -2.5,-0.5 + parent: 325 +- proto: WallPlastitanium + entities: + - uid: 7 + components: + - type: Transform + pos: -2.5,0.5 + parent: 325 + - uid: 8 + components: + - type: Transform + pos: -3.5,0.5 + parent: 325 + - uid: 9 + components: + - type: Transform + pos: -3.5,-0.5 + parent: 325 + - uid: 11 + components: + - type: Transform + pos: 1.5,0.5 + parent: 325 + - uid: 12 + components: + - type: Transform + pos: 2.5,0.5 + parent: 325 + - uid: 13 + components: + - type: Transform + pos: -4.5,-0.5 + parent: 325 + - uid: 22 + components: + - type: Transform + pos: 3.5,-0.5 + parent: 325 + - uid: 25 + components: + - type: Transform + pos: 3.5,-2.5 + parent: 325 + - uid: 27 + components: + - type: Transform + pos: -3.5,-2.5 + parent: 325 + - uid: 28 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-6.5 + parent: 325 + - uid: 29 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-6.5 + parent: 325 + - uid: 30 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-6.5 + parent: 325 + - uid: 31 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-8.5 + parent: 325 + - uid: 32 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-8.5 + parent: 325 + - uid: 33 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-6.5 + parent: 325 + - uid: 34 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-3.5 + parent: 325 + - uid: 35 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-9.5 + parent: 325 + - uid: 36 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-8.5 + parent: 325 + - uid: 37 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,-9.5 + parent: 325 + - uid: 38 + components: + - type: Transform + pos: -4.5,-2.5 + parent: 325 + - uid: 39 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-3.5 + parent: 325 + - uid: 44 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 2.5,-8.5 + parent: 325 + - uid: 48 + components: + - type: Transform + pos: 2.5,-7.5 + parent: 325 + - uid: 49 + components: + - type: Transform + pos: -3.5,-7.5 + parent: 325 + - uid: 54 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -2.5,-3.5 + parent: 325 + - uid: 55 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: -3.5,-3.5 + parent: 325 + - uid: 60 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-6.5 + parent: 325 + - uid: 61 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-6.5 + parent: 325 + - uid: 62 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -1.5,-3.5 + parent: 325 + - uid: 63 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 0.5,-3.5 + parent: 325 + - uid: 66 + components: + - type: Transform + pos: 0.5,-9.5 + parent: 325 + - uid: 69 + components: + - type: Transform + pos: -2.5,1.5 + parent: 325 + - uid: 73 + components: + - type: Transform + pos: 1.5,1.5 + parent: 325 + - uid: 80 + components: + - type: Transform + pos: 2.5,-2.5 + parent: 325 + - uid: 81 + components: + - type: Transform + pos: 2.5,-0.5 + parent: 325 + - uid: 92 + components: + - type: Transform + pos: -1.5,-9.5 + parent: 325 + - uid: 108 + components: + - type: Transform + pos: -0.5,-9.5 + parent: 325 +- proto: WallPlastitaniumDiagonal + entities: + - uid: 23 + components: + - type: Transform + pos: -4.5,0.5 + parent: 325 + - uid: 43 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 3.5,0.5 + parent: 325 + - uid: 68 + components: + - type: Transform + rot: -1.5707963267948966 rad + pos: 1.5,2.5 + parent: 325 + - uid: 79 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -4.5,-3.5 + parent: 325 + - uid: 82 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: 3.5,-3.5 + parent: 325 + - uid: 86 + components: + - type: Transform + pos: -2.5,2.5 + parent: 325 +- proto: WindoorSecure + entities: + - uid: 166 + components: + - type: Transform + rot: 1.5707963267948966 rad + pos: -3.5,-1.5 + parent: 325 + - uid: 206 + components: + - type: Transform + rot: 3.141592653589793 rad + pos: -0.5,-0.5 + parent: 325 + - type: ContainerContainer + containers: + board: !type:Container + showEnts: False + occludes: True + ents: + - 346 +- proto: YellowOxygenTankFilled + entities: + - uid: 167 + components: + - type: Transform + pos: 1.60798,-0.3062118 + parent: 325 +... diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index 712dfcf3a0..0b09e0e4c9 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -90,8 +90,7 @@ - !type:DepartmentTimeRequirement # DeltaV - Security dept time requirement department: Security time: 36000 # DeltaV - 10 hours - - type: GhostRoleMobSpawner - prototype: MobHumanLoneNuclearOperative + - type: GhostRoleAntagSpawner - type: Sprite sprite: Markers/jobs.rsi layers: diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index ca88511744..284ed00652 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -441,11 +441,28 @@ weight: 2 duration: 1 - type: ZombieRule - minStartDelay: 0 #let them know immediately - maxStartDelay: 10 - maxInitialInfected: 2 - minInitialInfectedGrace: 300 - maxInitialInfectedGrace: 450 + - type: AntagSelection + definitions: + - prefRoles: [ InitialInfected ] + max: 3 + playerRatio: 10 + blacklist: + components: + - ZombieImmune + - InitialInfectedExempt + briefing: + text: zombie-patientzero-role-greeting + color: Plum + sound: "/Audio/Ambience/Antag/zombie_start.ogg" + components: + - type: PendingZombie #less time to prepare than normal + minInitialInfectedGrace: 300 + maxInitialInfectedGrace: 450 + - type: ZombifyOnDeath + - type: IncurableZombie + mindComponents: + - type: InitialInfectedRole + prototype: InitialInfected - type: entity id: LoneOpsSpawn @@ -458,7 +475,29 @@ minimumPlayers: 15 reoccurrenceDelay: 45 duration: 1 - - type: LoneOpsSpawnRule + - type: LoadMapRule + mapPath: /Maps/Shuttles/striker.yml + - type: NukeopsRule + roundEndBehavior: Nothing + - type: AntagSelection + definitions: + - spawnerPrototype: SpawnPointLoneNukeOperative + min: 1 + max: 1 + pickPlayer: false + startingGear: SyndicateLoneOperativeGearFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - SyndicateNamesPrefix + - SyndicateNamesNormal + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: Nukeops - type: entity id: MassHallucinations diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index 37fc4b44cd..bb870f6007 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -34,6 +34,23 @@ id: Thief components: - type: ThiefRule + - type: AntagSelection + definitions: + - prefRoles: [ Thief ] + maxRange: + min: 1 + max: 3 + playerRatio: 1 + allowNonHumans: true + multiAntagSetting: All + startingGear: ThiefGear + components: + - type: Pacified + mindComponents: + - type: ThiefRole + prototype: Thief + briefing: + sound: "/Audio/Misc/thief_greeting.ogg" - type: entity noSpawn: true diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index e046b871fa..9b6a902649 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -70,31 +70,114 @@ components: - type: GameRule minPlayers: 35 + - type: RandomMetadata #this generates the random operation name cuz it's cool. + nameSegments: + - operationPrefix + - operationSuffix - type: NukeopsRule - faction: Syndicate - -- type: entity - id: Pirates - parent: BaseGameRule - noSpawn: true - components: - - type: PiratesRule + - type: LoadMapRule + gameMap: NukieOutpost + - type: AntagSelection + selectionTime: PrePlayerSpawn + definitions: + - prefRoles: [ NukeopsCommander ] + fallbackRoles: [ Nukeops, NukeopsMedic ] + max: 1 + playerRatio: 10 + startingGear: SyndicateCommanderGearFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-commander + - SyndicateNamesElite + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: NukeopsCommander + - prefRoles: [ NukeopsMedic ] + fallbackRoles: [ Nukeops, NukeopsCommander ] + max: 1 + playerRatio: 10 + startingGear: SyndicateOperativeMedicFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-agent + - SyndicateNamesNormal + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: NukeopsMedic + - prefRoles: [ Nukeops ] + fallbackRoles: [ NukeopsCommander, NukeopsMedic ] + min: 0 + max: 3 + playerRatio: 10 + startingGear: SyndicateOperativeGearFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-operator + - SyndicateNamesNormal + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: Nukeops - type: entity id: Traitor parent: BaseGameRule noSpawn: true components: + - type: GameRule + minPlayers: 5 + delay: + min: 240 + max: 420 - type: TraitorRule + - type: AntagSelection + definitions: + - prefRoles: [ Traitor ] + max: 12 + playerRatio: 10 + lateJoinAdditional: true + mindComponents: + - type: TraitorRole + prototype: Traitor - type: entity id: Revolutionary parent: BaseGameRule noSpawn: true components: + - type: GameRule + minPlayers: 15 - type: RevolutionaryRule - maxHeadRevs: 2 # DeltaV - playersPerHeadRev: 30 # DeltaV - need highpop readied up for multiple headrevs + - type: AntagSelection + definitions: + - prefRoles: [ HeadRev ] + max: 2 + playerRatio: 20 # WD + briefing: + text: head-rev-role-greeting + color: CornflowerBlue + sound: "/Audio/Ambience/Antag/headrev_start.ogg" + startingGear: HeadRevGear + components: + - type: Revolutionary + - type: HeadRevolutionary + mindComponents: + - type: RevolutionaryRole + prototype: HeadRev - type: entity id: Sandbox @@ -115,7 +198,32 @@ parent: BaseGameRule noSpawn: true components: + - type: GameRule + minPlayers: 20 + delay: + min: 600 + max: 900 - type: ZombieRule + - type: AntagSelection + definitions: + - prefRoles: [ InitialInfected ] + max: 6 + playerRatio: 10 + blacklist: + components: + - ZombieImmune + - InitialInfectedExempt + briefing: + text: zombie-patientzero-role-greeting + color: Plum + sound: "/Audio/Ambience/Antag/zombie_start.ogg" + components: + - type: PendingZombie + - type: ZombifyOnDeath + - type: IncurableZombie + mindComponents: + - type: InitialInfectedRole + prototype: InitialInfected # event schedulers - type: entity @@ -160,7 +268,6 @@ - id: BasicTrashVariationPass - id: SolidWallRustingVariationPass - id: ReinforcedWallRustingVariationPass - - id: CutWireVariationPass - id: BasicPuddleMessVariationPass prob: 0.99 orGroup: puddleMess diff --git a/Resources/Prototypes/Roles/Antags/nukeops.yml b/Resources/Prototypes/Roles/Antags/nukeops.yml index 6e58b1c9ba..1f803dd681 100644 --- a/Resources/Prototypes/Roles/Antags/nukeops.yml +++ b/Resources/Prototypes/Roles/Antags/nukeops.yml @@ -30,13 +30,23 @@ antagonist: true setPreference: true objective: roles-antag-nuclear-operative-commander-objective - # requirements: - # - !type:OverallPlaytimeRequirement - # time: 216000 # DeltaV - 60 hours - # - !type:DepartmentTimeRequirement # DeltaV - Security dept time requirement - # department: Security - # time: 36000 # DeltaV - 10 hours - # - !type:DepartmentTimeRequirement # DeltaV - Command dept time requirement - # department: Command - # time: 36000 # DeltaV - 10 hours - # - !type:WhitelistRequirement # DeltaV - Whitelist requirement + +#Lone Operative Gear +- type: startingGear + id: SyndicateLoneOperativeGearFull + equipment: + jumpsuit: ClothingUniformJumpsuitOperative + back: ClothingBackpackDuffelSyndicateOperative + mask: ClothingMaskGasSyndicate + eyes: ClothingEyesHudSyndicate + ears: ClothingHeadsetAltSyndicate + gloves: ClothingHandsGlovesCombat + outerClothing: ClothingOuterHardsuitSyndie + shoes: ClothingShoesBootsCombatFilled + id: SyndiPDA + pocket1: DoubleEmergencyOxygenTankFilled + pocket2: BaseUplinkRadio40TC + belt: ClothingBeltMilitaryWebbing + innerClothingSkirt: ClothingUniformJumpskirtOperative + satchel: ClothingBackpackDuffelSyndicateOperative + duffelbag: ClothingBackpackDuffelSyndicateOperative diff --git a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml index 434a7c1083..b2bcd8bcb4 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml @@ -293,8 +293,18 @@ #Head Rev Gear - type: startingGear id: HeadRevGear - equipment: - pocket2: Flash + storage: + back: + - Flash + - ClothingEyesGlassesSunglasses + +#Thief Gear +- type: startingGear + id: ThiefGear + storage: + back: + - ToolboxThief + - ClothingHandsChameleonThief #Gladiator with spear - type: startingGear diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index dadfc3134c..6986b646d7 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -175,15 +175,3 @@ - Zombie - BasicStationEventScheduler - BasicRoundstartVariation - -- type: gamePreset - id: Pirates - alias: - - pirates - name: pirates-title - description: pirates-description - showInVote: false - rules: - - Pirates - - BasicStationEventScheduler - - BasicRoundstartVariation From 3bfb11f752dda78c12c081c2f9a5540d921d36a9 Mon Sep 17 00:00:00 2001 From: Kira Gbedan <161087999+Verbalase@users.noreply.github.com> Date: Mon, 8 Jul 2024 02:40:03 -0500 Subject: [PATCH 02/20] Cherry-Pick Antag Refactor (#734) This cherry-pick's Wizden's Antag Refactor, which is needed for all future antag updates, as well as for me to cherry-pick over Cultists(Who are going to need some editing to fit the antag refactor), Changelings, Heretics, and Wizards. I actually selected the White-Dream-Public version of the Antag Refactor, due to it having commits made tailored to our repository, so it comes prepackaged with all the changes necessary for our repo-specific content. https://github.com/frosty-dev/ss14-wwdp/pull/10 Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> Co-authored-by: ThereDrD <88589686+ThereDrD0@users.noreply.github.com> Co-authored-by: Jeff Co-authored-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> Co-authored-by: Timemaster99 Co-authored-by: luckywill339@gmail.com Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com> Co-authored-by: Azzy --- Content.Client/Content.Client.csproj | 8 + .../Changeling/ChangelingSystem.cs | 36 + .../Changeling/UI/TransformMenu.xaml | 11 + .../Changeling/UI/TransformMenu.xaml.cs | 26 + .../Systems/AdminVerbSystem.Antags.cs | 15 + .../Changeling/ChangelingSystem.cs | 1104 +++++++++++++++++ .../GameTicking/Rules/ChangelingRuleSystem.cs | 116 ++ .../Components/ChangelingRuleComponent.cs | 27 + .../Components/AbsorbConditionComponent.cs | 11 + .../ImpersonateConditionComponent.cs | 29 + .../Components/StealDNAConditionComponent.cs | 11 + .../Systems/ChangelingObjectiveSystem.cs | 32 + .../Systems/ImpersonateConditionSystem.cs | 60 + .../Roles/ChangelingRoleComponent.cs | 8 + .../Actions/ActionContainerSystem.cs | 28 + Content.Shared/Content.Shared.csproj | 3 + .../Changeling/AbsorbableComponent.cs | 12 + .../Changeling/AbsorbedComponent.cs | 13 + .../Goobstation/Changeling/AbsorbedSystem.cs | 18 + .../Changeling/Changeling.Actions.cs | 68 + .../Changeling/Changeling.DoAfter.cs | 7 + .../Changeling/ChangelingComponent.cs | 161 +++ .../Changeling/HivemindComponent.cs | 12 + .../Ambience/Antag/changeling_start.ogg | Bin 0 -> 120933 bytes .../Changeling/Effects/changeling_shriek.ogg | Bin 0 -> 28562 bytes .../Goobstation/Changeling/alerts/alerts.ftl | 2 + .../Changeling/objectives/changeling.ftl | 7 + .../Goobstation/Changeling/radio_channels.ftl | 1 + .../Changeling/store/categories.ftl | 4 + .../changeling/abilities/changeling.ftl | 61 + .../changeling/administration/antag.ftl | 3 + .../game-presets/preset-changeling.ftl | 19 + .../changeling/prototypes/roles/antags.ftl | 2 + .../changeling/store/changeling-catalog.ftl | 151 +++ .../Goobstation/changeling/store/currency.ftl | 1 + .../Entities/Mobs/Species/vulpkanin.yml | 1 + .../Entities/Mobs/Species/arachnid.yml | 1 + .../Prototypes/Entities/Mobs/Species/base.yml | 4 + .../Entities/Mobs/Species/diona.yml | 1 + .../Entities/Mobs/Species/dwarf.yml | 1 + .../Entities/Mobs/Species/gingerbread.yml | 1 + .../Entities/Mobs/Species/harpy.yml | 1 + .../Entities/Mobs/Species/human.yml | 1 + .../Prototypes/Entities/Mobs/Species/moth.yml | 1 + .../Entities/Mobs/Species/reptilian.yml | 1 + .../Entities/Mobs/Species/slime.yml | 1 + .../Prototypes/Entities/Mobs/Species/vox.yml | 1 + .../Prototypes/Goobstation/Alerts/alerts.yml | 0 .../Changeling/Actions/changeling.yml | 552 +++++++++ .../Changeling/Alerts/changeling.yml | 17 + .../Changeling/Catalog/changeling_catalog.yml | 341 +++++ .../Clothing/Head/hardsuit-helmets.yml | 16 + .../Entities/Clothing/Head/helmets.yml | 19 + .../Entities/Clothing/OuterClothing/armor.yml | 28 + .../Clothing/OuterClothing/hardsuits.yml | 30 + .../Entities/Objects/Shields/shields.yml | 31 + .../Weapons/Melee/changeling_armblade.yml | 48 + .../Weapons/Throwable/throwing_stars.yml | 12 + .../Changeling/GameRules/roundstart.yml | 20 + .../Changeling/Objectives/changeling.yml | 57 + .../Changeling/Objectives/objectiveGroups.yml | 16 + .../Changeling/Roles/Antags/changeling.yml | 6 + .../Changeling/StatusIcon/antag.yml | 10 + .../Changeling/Store/categories.yml | 16 + .../Goobstation/Changeling/Store/currency.yml | 4 + .../Goobstation/Changeling/ai_factions.yml | 7 + .../Changeling/arm_blade.rsi/icon.png | Bin 0 -> 876 bytes .../Changeling/arm_blade.rsi/inhand-left.png | Bin 0 -> 1425 bytes .../Changeling/arm_blade.rsi/inhand-right.png | Bin 0 -> 1443 bytes .../Changeling/arm_blade.rsi/meta.json | 34 + .../Changeling/bone_shard.rsi/icon.png | Bin 0 -> 4334 bytes .../Changeling/bone_shard.rsi/meta.json | 14 + .../changeling_abilities.rsi/absorb_dna.png | Bin 0 -> 607 bytes .../anatomic_panacea.png | Bin 0 -> 768 bytes .../apex_predator.png | Bin 0 -> 672 bytes .../changeling_abilities.rsi/armblade.png | Bin 0 -> 707 bytes .../augmented_eyesight.png | Bin 0 -> 701 bytes .../changeling_abilities.rsi/biodegrade.png | Bin 0 -> 563 bytes .../changeling_abilities.rsi/bone_shard.png | Bin 0 -> 712 bytes .../chameleon_skin.png | Bin 0 -> 689 bytes .../chitinous_armor.png | Bin 0 -> 675 bytes .../changeling_abilities.rsi/contort_body.png | Bin 0 -> 502 bytes .../epinephrine_overdose.png | Bin 0 -> 639 bytes .../evolution_menu.png | Bin 0 -> 673 bytes .../changeling_abilities.rsi/fleshmend.png | Bin 0 -> 675 bytes .../hivemind_access.png | Bin 0 -> 634 bytes .../changeling_abilities.rsi/last_resort.png | Bin 0 -> 821 bytes .../changeling_abilities.rsi/lesser_form.png | Bin 0 -> 686 bytes .../changeling_abilities.rsi/meta.json | 113 ++ .../organic_shield.png | Bin 0 -> 594 bytes .../shriek_dissonant.png | Bin 0 -> 626 bytes .../shriek_resonant.png | Bin 0 -> 605 bytes .../space_adaptation.png | Bin 0 -> 662 bytes .../changeling_abilities.rsi/stasis_enter.png | Bin 0 -> 747 bytes .../changeling_abilities.rsi/stasis_exit.png | Bin 0 -> 767 bytes .../sting_armblade.png | Bin 0 -> 672 bytes .../changeling_abilities.rsi/sting_blind.png | Bin 0 -> 606 bytes .../changeling_abilities.rsi/sting_cryo.png | Bin 0 -> 654 bytes .../sting_extractdna.png | Bin 0 -> 555 bytes .../sting_lethargic.png | Bin 0 -> 647 bytes .../changeling_abilities.rsi/sting_mute.png | Bin 0 -> 609 bytes .../sting_transform.png | Bin 0 -> 711 bytes .../strained_muscles.png | Bin 0 -> 676 bytes .../changeling_abilities.rsi/swap_forms.png | Bin 0 -> 778 bytes .../changeling_abilities.rsi/tentacle.png | Bin 0 -> 691 bytes .../changeling_abilities.rsi/transform.png | Bin 0 -> 654 bytes .../transform_cycle.png | Bin 0 -> 768 bytes .../Changeling/changeling_chemicals.rsi/0.png | Bin 0 -> 1252 bytes .../Changeling/changeling_chemicals.rsi/1.png | Bin 0 -> 1254 bytes .../changeling_chemicals.rsi/10.png | Bin 0 -> 1389 bytes .../changeling_chemicals.rsi/11.png | Bin 0 -> 1387 bytes .../changeling_chemicals.rsi/12.png | Bin 0 -> 1397 bytes .../changeling_chemicals.rsi/13.png | Bin 0 -> 1401 bytes .../changeling_chemicals.rsi/14.png | Bin 0 -> 1393 bytes .../changeling_chemicals.rsi/15.png | Bin 0 -> 1396 bytes .../changeling_chemicals.rsi/16.png | Bin 0 -> 1363 bytes .../Changeling/changeling_chemicals.rsi/2.png | Bin 0 -> 1276 bytes .../Changeling/changeling_chemicals.rsi/3.png | Bin 0 -> 1301 bytes .../Changeling/changeling_chemicals.rsi/4.png | Bin 0 -> 1323 bytes .../Changeling/changeling_chemicals.rsi/5.png | Bin 0 -> 1332 bytes .../Changeling/changeling_chemicals.rsi/6.png | Bin 0 -> 1348 bytes .../Changeling/changeling_chemicals.rsi/7.png | Bin 0 -> 1369 bytes .../Changeling/changeling_chemicals.rsi/8.png | Bin 0 -> 1388 bytes .../Changeling/changeling_chemicals.rsi/9.png | Bin 0 -> 1404 bytes .../changeling_chemicals.rsi/meta.json | 62 + .../ling_armor.rsi/equipped-OUTERCLOTHING.png | Bin 0 -> 2340 bytes .../Changeling/ling_armor.rsi/icon.png | Bin 0 -> 1020 bytes .../Changeling/ling_armor.rsi/meta.json | 18 + .../ling_armor_helmet.rsi/equipped-HELMET.png | Bin 0 -> 837 bytes .../Changeling/ling_armor_helmet.rsi/icon.png | Bin 0 -> 309 bytes .../ling_armor_helmet.rsi/meta.json | 18 + .../equipped-OUTERCLOTHING.png | Bin 0 -> 1590 bytes .../Changeling/ling_spacesuit.rsi/icon.png | Bin 0 -> 687 bytes .../Changeling/ling_spacesuit.rsi/meta.json | 18 + .../equipped-HELMET.png | Bin 0 -> 864 bytes .../ling_spacesuit_helmet.rsi/icon.png | Bin 0 -> 309 bytes .../ling_spacesuit_helmet.rsi/meta.json | 18 + .../Changeling/shields.rsi/ling-icon.png | Bin 0 -> 241 bytes .../shields.rsi/ling-inhand-left.png | Bin 0 -> 1020 bytes .../shields.rsi/ling-inhand-right.png | Bin 0 -> 951 bytes .../Changeling/shields.rsi/meta.json | 22 + .../Misc/job_icons.rsi/Changeling.png | Bin 0 -> 177 bytes .../Interface/Misc/job_icons.rsi/meta.json | 6 + 143 files changed, 3635 insertions(+) create mode 100644 Content.Client/Goobstation/Changeling/ChangelingSystem.cs create mode 100644 Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml create mode 100644 Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml.cs create mode 100644 Content.Server/Goobstation/Changeling/ChangelingSystem.cs create mode 100644 Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs create mode 100644 Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs create mode 100644 Content.Server/Goobstation/Objectives/Components/AbsorbConditionComponent.cs create mode 100644 Content.Server/Goobstation/Objectives/Components/ImpersonateConditionComponent.cs create mode 100644 Content.Server/Goobstation/Objectives/Components/StealDNAConditionComponent.cs create mode 100644 Content.Server/Goobstation/Objectives/Systems/ChangelingObjectiveSystem.cs create mode 100644 Content.Server/Goobstation/Objectives/Systems/ImpersonateConditionSystem.cs create mode 100644 Content.Server/Goobstation/Roles/ChangelingRoleComponent.cs create mode 100644 Content.Shared/Goobstation/Changeling/AbsorbableComponent.cs create mode 100644 Content.Shared/Goobstation/Changeling/AbsorbedComponent.cs create mode 100644 Content.Shared/Goobstation/Changeling/AbsorbedSystem.cs create mode 100644 Content.Shared/Goobstation/Changeling/Changeling.Actions.cs create mode 100644 Content.Shared/Goobstation/Changeling/Changeling.DoAfter.cs create mode 100644 Content.Shared/Goobstation/Changeling/ChangelingComponent.cs create mode 100644 Content.Shared/Goobstation/Changeling/HivemindComponent.cs create mode 100644 Resources/Audio/Goobstation/Ambience/Antag/changeling_start.ogg create mode 100644 Resources/Audio/Goobstation/Changeling/Effects/changeling_shriek.ogg create mode 100644 Resources/Locale/en-US/Goobstation/Changeling/alerts/alerts.ftl create mode 100644 Resources/Locale/en-US/Goobstation/Changeling/objectives/changeling.ftl create mode 100644 Resources/Locale/en-US/Goobstation/Changeling/radio_channels.ftl create mode 100644 Resources/Locale/en-US/Goobstation/Changeling/store/categories.ftl create mode 100644 Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl create mode 100644 Resources/Locale/en-US/Goobstation/changeling/administration/antag.ftl create mode 100644 Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl create mode 100644 Resources/Locale/en-US/Goobstation/changeling/prototypes/roles/antags.ftl create mode 100644 Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl create mode 100644 Resources/Locale/en-US/Goobstation/changeling/store/currency.ftl create mode 100644 Resources/Prototypes/Goobstation/Alerts/alerts.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Alerts/changeling.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Catalog/changeling_catalog.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/Head/hardsuit-helmets.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/Head/helmets.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/OuterClothing/armor.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/OuterClothing/hardsuits.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Shields/shields.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Weapons/Melee/changeling_armblade.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Weapons/Throwable/throwing_stars.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Objectives/objectiveGroups.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/StatusIcon/antag.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Store/categories.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Store/currency.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/ai_factions.yml create mode 100644 Resources/Textures/Goobstation/Changeling/arm_blade.rsi/icon.png create mode 100644 Resources/Textures/Goobstation/Changeling/arm_blade.rsi/inhand-left.png create mode 100644 Resources/Textures/Goobstation/Changeling/arm_blade.rsi/inhand-right.png create mode 100644 Resources/Textures/Goobstation/Changeling/arm_blade.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/bone_shard.rsi/icon.png create mode 100644 Resources/Textures/Goobstation/Changeling/bone_shard.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/absorb_dna.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/anatomic_panacea.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/apex_predator.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/armblade.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/augmented_eyesight.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/biodegrade.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/bone_shard.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/chameleon_skin.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/chitinous_armor.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/contort_body.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/epinephrine_overdose.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/evolution_menu.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/fleshmend.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/hivemind_access.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/last_resort.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/lesser_form.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/organic_shield.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/shriek_dissonant.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/shriek_resonant.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/space_adaptation.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_enter.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_exit.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_armblade.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_blind.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_cryo.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_extractdna.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_lethargic.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_mute.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_transform.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/strained_muscles.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/swap_forms.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/tentacle.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/transform.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/transform_cycle.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/0.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/1.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/10.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/11.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/12.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/13.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/14.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/15.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/16.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/2.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/3.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/4.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/5.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/6.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/7.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/8.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/9.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/ling_armor.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Goobstation/Changeling/ling_armor.rsi/icon.png create mode 100644 Resources/Textures/Goobstation/Changeling/ling_armor.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/ling_armor_helmet.rsi/equipped-HELMET.png create mode 100644 Resources/Textures/Goobstation/Changeling/ling_armor_helmet.rsi/icon.png create mode 100644 Resources/Textures/Goobstation/Changeling/ling_armor_helmet.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/ling_spacesuit.rsi/equipped-OUTERCLOTHING.png create mode 100644 Resources/Textures/Goobstation/Changeling/ling_spacesuit.rsi/icon.png create mode 100644 Resources/Textures/Goobstation/Changeling/ling_spacesuit.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/ling_spacesuit_helmet.rsi/equipped-HELMET.png create mode 100644 Resources/Textures/Goobstation/Changeling/ling_spacesuit_helmet.rsi/icon.png create mode 100644 Resources/Textures/Goobstation/Changeling/ling_spacesuit_helmet.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/shields.rsi/ling-icon.png create mode 100644 Resources/Textures/Goobstation/Changeling/shields.rsi/ling-inhand-left.png create mode 100644 Resources/Textures/Goobstation/Changeling/shields.rsi/ling-inhand-right.png create mode 100644 Resources/Textures/Goobstation/Changeling/shields.rsi/meta.json create mode 100644 Resources/Textures/Interface/Misc/job_icons.rsi/Changeling.png diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index c1958acba7..f15d8e262d 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -33,4 +33,12 @@ + + + + + + MSBuild:Compile + + diff --git a/Content.Client/Goobstation/Changeling/ChangelingSystem.cs b/Content.Client/Goobstation/Changeling/ChangelingSystem.cs new file mode 100644 index 0000000000..e838809e92 --- /dev/null +++ b/Content.Client/Goobstation/Changeling/ChangelingSystem.cs @@ -0,0 +1,36 @@ +using Content.Client.Alerts; +using Content.Client.UserInterface.Systems.Alerts.Controls; +using Content.Shared.Changeling; +using Content.Shared.StatusIcon.Components; +using Robust.Shared.Prototypes; + +namespace Content.Client.Changeling; + +public sealed partial class ChangelingSystem : EntitySystem +{ + + [Dependency] private readonly IPrototypeManager _prototype = default!; + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnUpdateAlert); + SubscribeLocalEvent(GetChanglingIcon); + } + + private void OnUpdateAlert(EntityUid uid, ChangelingComponent comp, ref UpdateAlertSpriteEvent args) + { + if (args.Alert.AlertKey.AlertType != "Chemicals") + return; + + var chemicalsNormalised = (int) (comp.Chemicals / comp.MaxChemicals * 16); // hardcoded because uhh umm + var sprite = args.SpriteViewEnt.Comp; + sprite.LayerSetState(AlertVisualLayers.Base, $"{chemicalsNormalised}"); + } + + private void GetChanglingIcon(Entity ent, ref GetStatusIconsEvent args) + { + if (HasComp(ent) && _prototype.TryIndex(ent.Comp.StatusIcon, out var iconPrototype)) + args.StatusIcons.Add(iconPrototype); + } +} diff --git a/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml b/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml new file mode 100644 index 0000000000..77b874af22 --- /dev/null +++ b/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml @@ -0,0 +1,11 @@ + + + + + diff --git a/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml.cs b/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml.cs new file mode 100644 index 0000000000..b626605046 --- /dev/null +++ b/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml.cs @@ -0,0 +1,26 @@ +using Content.Client.UserInterface.Controls; +using Robust.Client.AutoGenerated; +using Robust.Client.GameObjects; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.Changeling.UI; + +[GenerateTypedNameReferences] +public sealed partial class TransformMenu : RadialMenu +{ + [Dependency] private readonly EntityManager _entity = default!; + + private readonly SpriteSystem _sprite = default!; + + public TransformMenu() + { + IoCManager.InjectDependencies(this); + RobustXamlLoader.Load(this); + + _sprite = _entity.System(); + + var main = FindControl("Main"); + + // TODO: transform radial menu + } +} diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index df77a3a1a7..87840be089 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -132,5 +132,20 @@ private void AddAntagVerbs(GetVerbsEvent args) Message = Loc.GetString("admin-verb-make-thief"), }; args.Verbs.Add(thief); + + // Goobstation - changelings + Verb ling = new() + { + Text = Loc.GetString("admin-verb-text-make-changeling"), + Category = VerbCategory.Antag, + Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Goobstation/Changeling/changeling_abilities.rsi"), "transform"), + Act = () => + { + _antag.ForceMakeAntag(targetPlayer, "Changeling"); + }, + Impact = LogImpact.High, + Message = Loc.GetString("admin-verb-make-changeling"), + }; + args.Verbs.Add(ling); } } diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs new file mode 100644 index 0000000000..daa1d9b2dc --- /dev/null +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -0,0 +1,1104 @@ +using Content.Server.DoAfter; +using Content.Server.Forensics; +using Content.Server.Polymorph.Systems; +using Content.Server.Popups; +using Content.Server.Store.Systems; +using Content.Server.Zombies; +using Content.Shared.Alert; +using Content.Shared.Changeling; +using Content.Shared.Chemistry.Components; +using Content.Shared.Cuffs.Components; +using Content.Shared.DoAfter; +using Content.Shared.FixedPoint; +using Content.Shared.Humanoid; +using Content.Shared.IdentityManagement; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Systems; +using Content.Shared.Nutrition.Components; +using Content.Shared.Store.Components; +using Robust.Server.Audio; +using Robust.Shared.Audio; +using Robust.Shared.Random; +using Content.Shared.Popups; +using Content.Shared.Damage; +using Robust.Shared.Prototypes; +using Content.Shared.Damage.Prototypes; +using Content.Server.Body.Systems; +using Content.Shared.Actions; +using Content.Shared.Polymorph; +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Serialization.Manager; +using Content.Server.Actions; +using Content.Server.Humanoid; +using Content.Server.Polymorph.Components; +using Content.Shared.Chemistry.EntitySystems; +using Content.Shared.Camera; +using Robust.Server.Player; +using Content.Server.Flash; +using Content.Server.Emp; +using Robust.Server.GameObjects; +using Content.Shared.Hands.EntitySystems; +using Content.Shared.Inventory; +using Content.Shared.Movement.Systems; +using Content.Shared.Damage.Systems; +using Content.Shared.Mind; +using Content.Shared.Damage.Components; +using Content.Server.Objectives.Components; +using Content.Server.Light.Components; +using Content.Server.Light.EntitySystems; +using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.Eye.Blinding.Components; +using Content.Shared.StatusEffect; +using Content.Server.Flash.Components; +using Content.Shared.Movement.Pulling.Systems; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Cuffs; +using Content.Shared.Fluids; +using Content.Shared.Stealth.Components; + +namespace Content.Server.Changeling; + +public sealed partial class ChangelingSystem : EntitySystem +{ + // this is one hell of a star wars intro text + [Dependency] private readonly SharedMindSystem _mind = default!; + [Dependency] private readonly IRobustRandom _rand = default!; + [Dependency] private readonly ActionsSystem _actions = default!; + [Dependency] private readonly StoreSystem _store = default!; + [Dependency] private readonly AudioSystem _audio = default!; + [Dependency] private readonly PolymorphSystem _polymorph = default!; + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; + [Dependency] private readonly MobStateSystem _mobState = default!; + [Dependency] private readonly IPrototypeManager _proto = default!; + [Dependency] private readonly DamageableSystem _damage = default!; + [Dependency] private readonly BloodstreamSystem _blood = default!; + [Dependency] private readonly ISerializationManager _serialization = default!; + [Dependency] private readonly MetaDataSystem _metaData = default!; + [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; + [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; + + [Dependency] private readonly TransformSystem _transform = default!; + [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; + [Dependency] private readonly IPlayerManager _playerMan = default!; + [Dependency] private readonly FlashSystem _flash = default!; + [Dependency] private readonly EmpSystem _emp = default!; + [Dependency] private readonly EntityLookupSystem _lookup = default!; + [Dependency] private readonly PoweredLightSystem _light = default!; + + [Dependency] private readonly SharedHandsSystem _hands = default!; + [Dependency] private readonly InventorySystem _inventory = default!; + + [Dependency] private readonly MovementSpeedModifierSystem _speed = default!; + [Dependency] private readonly StaminaSystem _stamina = default!; + + [Dependency] private readonly BlindableSystem _blindable = default!; + [Dependency] private readonly StatusEffectsSystem _statusEffect = default!; + + [Dependency] private readonly PullingSystem _pull = default!; + [Dependency] private readonly SharedCuffableSystem _cuffs = default!; + [Dependency] private readonly SharedPuddleSystem _puddle = default!; + + public ProtoId ArmbladePrototype = "ArmBladeChangeling"; + public ProtoId FakeArmbladePrototype = "FakeArmBladeChangeling"; + + public ProtoId ShieldPrototype = "ChangelingShield"; + public ProtoId BoneShardPrototype = "ThrowingStarChangeling"; + + public ProtoId ArmorPrototype = "ChangelingClothingOuterArmor"; + public ProtoId ArmorHelmetPrototype = "ChangelingClothingHeadHelmet"; + + public ProtoId SpacesuitPrototype = "ChangelingClothingOuterHardsuit"; + public ProtoId SpacesuitHelmetPrototype = "ChangelingClothingHeadHelmetHardsuit"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnStartup); + + SubscribeLocalEvent(OnMobStateChange); + + SubscribeLocalEvent(OnOpenEvolutionMenu); + SubscribeLocalEvent(OnAbsorb); + SubscribeLocalEvent(OnAbsorbDoAfter); + SubscribeLocalEvent(OnStingExtractDNA); + SubscribeLocalEvent(OnTransformCycle); + SubscribeLocalEvent(OnTransform); + SubscribeLocalEvent(OnEnterStasis); + SubscribeLocalEvent(OnExitStasis); + + SubscribeLocalEvent(OnToggleArmblade); + SubscribeLocalEvent(OnCreateBoneShard); + SubscribeLocalEvent(OnToggleArmor); + SubscribeLocalEvent(OnToggleShield); + SubscribeLocalEvent(OnShriekDissonant); + SubscribeLocalEvent(OnShriekResonant); + SubscribeLocalEvent(OnToggleStrainedMuscles); + + SubscribeLocalEvent(OnStingBlind); + SubscribeLocalEvent(OnStingCryo); + SubscribeLocalEvent(OnStingLethargic); + SubscribeLocalEvent(OnStingMute); + SubscribeLocalEvent(OnStingTransform); + SubscribeLocalEvent(OnStingFakeArmblade); + + SubscribeLocalEvent(OnAnatomicPanacea); + SubscribeLocalEvent(OnAugmentedEyesight); + SubscribeLocalEvent(OnBiodegrade); + SubscribeLocalEvent(OnChameleonSkin); + SubscribeLocalEvent(OnEphedrineOverdose); + SubscribeLocalEvent(OnHealUltraSwag); + SubscribeLocalEvent(OnLastResort); + SubscribeLocalEvent(OnLesserForm); + SubscribeLocalEvent(OnSpacesuit); + SubscribeLocalEvent(OnHivemindAccess); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + foreach (var comp in EntityManager.EntityQuery()) + { + var uid = comp.Owner; + + comp.UpdateAccumulator += frameTime; + + if (comp.UpdateAccumulator < comp.UpdateTimer) + return; + + comp.UpdateAccumulator -= comp.UpdateTimer; + + Cycle(uid, comp); + } + } + public void Cycle(EntityUid uid, ChangelingComponent comp) + { + UpdateChemicals(uid, comp); + UpdateModifier(comp); + + if (comp.StrainedMusclesActivated) + { + var stamina = EnsureComp(uid); + _stamina.TakeStaminaDamage(uid, 7.5f, visual: false); + if (_stamina.GetStaminaDamage(uid) >= stamina.CritThreshold) + ToggleStrainedMuscles(uid, comp); + } + } + + #region Helper Methods + + public void PlayMeatySound(EntityUid uid, ChangelingComponent comp) + { + var rand = _rand.Next(0, comp.SoundPool.Count - 1); + var sound = comp.SoundPool.ToArray()[rand]; + _audio.PlayPvs(sound, uid, AudioParams.Default.WithVolume(-3f)); + } + public void PlayShriekSound(EntityUid uid, ChangelingComponent comp) + { + // todo: add camera shake + _audio.PlayPvs(comp.ShriekSound, uid); + } + + /// + /// Check if a target is crit/dead or cuffed. For absorbing. + /// + public bool IsIncapacitated(EntityUid uid) + { + if (_mobState.IsIncapacitated(uid) + || (TryComp(uid, out var cuffs) && cuffs.CuffedHandCount > 0)) + return true; + + return false; + } + + private void UpdateChemicals(EntityUid uid, ChangelingComponent comp, float? amount = null) + { + var regen = (float) Math.Abs(1 * (1 + Math.Clamp(comp.ChemicalRegenerationModifier, -.75f, float.PositiveInfinity))); + var chemicals = comp.Chemicals; + + chemicals += amount ?? regen; + + comp.Chemicals = Math.Clamp(chemicals, 0, comp.MaxChemicals); + + Dirty(uid, comp); + + _alerts.ShowAlert(uid, "Chemicals"); + } + public void UpdateModifier(ChangelingComponent comp) + { + var modifier = comp.ChemicalRegenerationMobStateModifier + comp.ChemicalRegenerationAbilityModifier; + comp.ChemicalRegenerationModifier = modifier; + } + + public bool TryUseAbility(EntityUid uid, ChangelingComponent comp, BaseActionEvent action) + { + if (action.Handled) + return false; + + if (!TryComp(action.Action, out var lingAction)) + return false; + + if (!lingAction.UseWhileLesserForm && comp.IsInLesserForm) + { + _popup.PopupEntity(Loc.GetString("changeling-action-fail-lesserform"), uid, uid); + return false; + } + + var price = lingAction.ChemicalCost; + if (comp.Chemicals < price) + { + _popup.PopupEntity(Loc.GetString("changeling-chemicals-deficit"), uid, uid); + return false; + } + + if (lingAction.RequireAbsorbed > comp.TotalAbsorbedEntities) + { + var delta = lingAction.RequireAbsorbed - comp.TotalAbsorbedEntities; + _popup.PopupEntity(Loc.GetString("changeling-action-fail-absorbed", ("number", delta)), uid, uid); + return false; + } + + UpdateChemicals(uid, comp, -price); + + action.Handled = true; + + return true; + } + public bool TrySting(EntityUid uid, ChangelingComponent comp, EntityTargetActionEvent action, bool overrideMessage = false) + { + if (!TryUseAbility(uid, comp, action)) + return false; + + var target = action.Target; + if (HasComp(target)) + { + var selfMessage = Loc.GetString("changeling-sting-fail-self", ("target", Identity.Entity(target, EntityManager))); + var targetMessage = Loc.GetString("changeling-sting-fail-ling"); + + _popup.PopupEntity(selfMessage, uid, uid); + _popup.PopupEntity(targetMessage, target, target); + return false; + } + if (!overrideMessage) + _popup.PopupEntity(Loc.GetString("changeling-sting", ("target", Identity.Entity(target, EntityManager))), uid, uid); + return true; + } + public bool TryInjectReagents(EntityUid uid, List<(string, FixedPoint2)> reagents) + { + var solution = new Solution(); + foreach (var reagent in reagents) + solution.AddReagent(reagent.Item1, reagent.Item2); + + if (!_solution.TryGetInjectableSolution(uid, out var targetSolution, out var _)) + return false; + + if (!_solution.TryAddSolution(targetSolution.Value, solution)) + return false; + + return true; + } + public bool TryReagentSting(EntityUid uid, ChangelingComponent comp, EntityTargetActionEvent action, List<(string, FixedPoint2)> reagents) + { + var target = action.Target; + if (!TrySting(uid, comp, action)) + return false; + + if (!TryInjectReagents(target, reagents)) + return false; + + return true; + } + public bool TryToggleItem(EntityUid uid, ProtoId proto, ref EntityUid? outItem, string? clothingSlot = null) + { + if (outItem == null) + { + var item = EntityManager.SpawnEntity(proto, Transform(uid).Coordinates); + if (clothingSlot != null && !_inventory.TryEquip(uid, item, clothingSlot, force: true)) + { + EntityManager.DeleteEntity(item); + return false; + } + else if (!_hands.TryForcePickupAnyHand(uid, item)) + { + _popup.PopupEntity(Loc.GetString("changeling-fail-hands"), uid, uid); + EntityManager.DeleteEntity(item); + return false; + } + outItem = item; + return true; + } + + EntityManager.DeleteEntity(outItem); + outItem = null; + + return true; + } + + public void AddDNA(EntityUid uid, ChangelingComponent comp, TransformData data, bool countObjective = false) + { + if (comp.AbsorbedDNA.Count >= comp.MaxAbsorbedDNA) + { + comp.AbsorbedDNA.RemoveAt(0); + _popup.PopupEntity(Loc.GetString("changeling-sting-extract-max"), uid, uid); + } + comp.AbsorbedDNA.Add(data); + + if (countObjective) + { + if (_mind.TryGetMind(uid, out var mindId, out var mind)) + if (_mind.TryGetObjectiveComp(mindId, out var objective, mind)) + objective.DNAStolen += 1; + } + } + public bool TryStealDNA(EntityUid uid, EntityUid target, ChangelingComponent comp, bool countObjective = false) + { + if (!TryComp(target, out var appearance) + || !TryComp(target, out var metadata) + || !TryComp(target, out var dna) + || !TryComp(target, out var fingerprint)) + return false; + + foreach (var storedDNA in comp.AbsorbedDNA) + { + if (storedDNA.DNA != null && storedDNA.DNA == dna.DNA) + return false; + } + + var data = new TransformData + { + Name = metadata.EntityName, + DNA = dna.DNA, + Appearance = appearance + }; + + if (fingerprint.Fingerprint != null) + data.Fingerprint = fingerprint.Fingerprint; + + AddDNA(uid, comp, data, countObjective); + + return true; + } + + private EntityUid? Transform(EntityUid uid, TransformData data, ChangelingComponent? comp = null, bool persistentDna = false) + { + if (!_proto.TryIndex(data.Appearance.Species, out var species)) + return null; + + var config = new PolymorphConfiguration() + { + Entity = species.Prototype, + TransferDamage = true, + Forced = true, + Inventory = PolymorphInventoryChange.Transfer, + RevertOnCrit = false, + RevertOnDeath = false + }; + var newUid = _polymorph.PolymorphEntity(uid, config); + + if (newUid == null) + return null; + + Comp(newUid.Value).Fingerprint = data.Fingerprint; + Comp(newUid.Value).DNA = data.DNA; + _humanoid.CloneAppearance(data.Appearance.Owner, newUid.Value); + _metaData.SetEntityName(newUid.Value, data.Name); + RemCompDeferred(newUid.Value); + + if (comp != null) + { + // copy our stuff + var lingCompCopy = _serialization.CreateCopy(comp, notNullableOverride: true); + AddComp(newUid.Value, lingCompCopy, true); + var newLingComp = Comp(newUid.Value); + + newLingComp.AbsorbedDNA = comp.AbsorbedDNA; + newLingComp.CurrentForm = data; + newLingComp.IsInLesserForm = false; + if (!persistentDna) + newLingComp.AbsorbedDNA.Remove(data); // a one timer opportunity. + + if (TryComp(uid, out var storeComp)) + { + var storeCompCopy = _serialization.CreateCopy(storeComp, notNullableOverride: true); + RemComp(newUid.Value); + EntityManager.AddComponent(newUid.Value, storeCompCopy); + } + RemCompDeferred(uid); + } + + var message = Loc.GetString("changeling-transform-finish", ("target", data.Name)); + _popup.PopupEntity(message, newUid.Value, newUid.Value); + + QueueDel(uid); + + return newUid; + } + public bool TryTransform(EntityUid target, ChangelingComponent comp, bool sting = false, bool persistentDna = false) + { + var data = comp.SelectedForm; + + if (data == null) + { + _popup.PopupEntity(Loc.GetString("changeling-transform-fail-self"), target, target); + return false; + } + if (data == comp.CurrentForm) + { + _popup.PopupEntity(Loc.GetString("changeling-transform-fail-choose"), target, target); + return false; + } + + var locName = Identity.Entity(target, EntityManager); + EntityUid? newUid = null; + if (sting) + newUid = Transform(target, (TransformData) data, persistentDna: persistentDna); + else newUid = Transform(target, (TransformData) data, comp, persistentDna); + + if (newUid != null) + { + PlayMeatySound((EntityUid) newUid, comp); + var loc = Loc.GetString("changeling-transform-others", ("user", locName)); + _popup.PopupEntity(loc, (EntityUid) newUid, PopupType.LargeCaution); + } + + return true; + } + + #endregion + + #region Event Handlers + + private void OnStartup(EntityUid uid, ChangelingComponent comp, ref ComponentStartup args) + { + RemComp(uid); + RemComp(uid); + EnsureComp(uid); + + // add actions + foreach (var actionId in comp.BaseChangelingActions) + _actions.AddAction(uid, actionId); + } + + private void OnMobStateChange(EntityUid uid, ChangelingComponent comp, ref MobStateChangedEvent args) + { + var modifier = 0f; + switch (args.NewMobState) + { + case MobState.Alive: default: modifier = 0; break; + case MobState.Critical: modifier = -.25f; break; + case MobState.Dead: modifier = -.5f; break; + } + comp.ChemicalRegenerationMobStateModifier = modifier; + } + + #endregion + + #region Basic Abilities + + private void OnOpenEvolutionMenu(EntityUid uid, ChangelingComponent comp, ref OpenEvolutionMenuEvent args) + { + if (!TryComp(uid, out var store)) + return; + + _store.ToggleUi(uid, uid, store); + } + + private void OnAbsorb(EntityUid uid, ChangelingComponent comp, ref AbsorbDNAEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var target = args.Target; + + if (!IsIncapacitated(target)) + { + _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-incapacitated"), uid, uid); + return; + } + if (HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-absorbed"), uid, uid); + return; + } + if (!HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-unabsorbable"), uid, uid); + return; + } + + var popupOthers = Loc.GetString("changeling-absorb-start", ("user", Identity.Entity(uid, EntityManager)), ("target", Identity.Entity(target, EntityManager))); + _popup.PopupEntity(popupOthers, uid, PopupType.LargeCaution); + PlayMeatySound(uid, comp); + var dargs = new DoAfterArgs(EntityManager, uid, TimeSpan.FromSeconds(15), new AbsorbDNADoAfterEvent(), uid, target) + { + DistanceThreshold = 1.5f, + BreakOnDamage = true, + BreakOnHandChange = false, + BreakOnMove = true, + BreakOnWeightlessMove = true, + AttemptFrequency = AttemptFrequency.StartAndEnd + }; + _doAfter.TryStartDoAfter(dargs); + } + public ProtoId AbsorbedDamageGroup = "Genetic"; + private void OnAbsorbDoAfter(EntityUid uid, ChangelingComponent comp, ref AbsorbDNADoAfterEvent args) + { + if (args.Args.Target == null) + return; + + var target = args.Args.Target.Value; + + PlayMeatySound(args.User, comp); + + if (args.Cancelled || !IsIncapacitated(target) || HasComp(target)) + return; + + var dmg = new DamageSpecifier(_proto.Index(AbsorbedDamageGroup), 200); + _damage.TryChangeDamage(target, dmg, false, false); + _blood.ChangeBloodReagent(target, "FerrochromicAcid"); + _blood.SpillAllSolutions(target); + + EnsureComp(target); + + var popup = Loc.GetString("changeling-absorb-end-self-ling"); + var bonusChemicals = 10; + var bonusEvolutionPoints = 0; + if (HasComp(target)) + { + bonusChemicals += 30; + bonusEvolutionPoints += 8; + } + else + { + popup = Loc.GetString("changeling-absorb-end-self", ("target", Identity.Entity(target, EntityManager))); + bonusChemicals += 10; + bonusEvolutionPoints += 2; + } + TryStealDNA(uid, target, comp, true); + comp.TotalAbsorbedEntities++; + comp.TotalStolenDNA++; + + _popup.PopupEntity(popup, args.User, args.User); + comp.MaxChemicals += bonusChemicals; + + if (TryComp(args.User, out var store)) + { + _store.TryAddCurrency(new Dictionary { { "EvolutionPoint", bonusEvolutionPoints } }, args.User, store); + _store.UpdateUserInterface(args.User, args.User, store); + } + + if (_mind.TryGetMind(uid, out var mindId, out var mind)) + if (_mind.TryGetObjectiveComp(mindId, out var objective, mind)) + objective.Absorbed += 1; + } + + private void OnStingExtractDNA(EntityUid uid, ChangelingComponent comp, ref StingExtractDNAEvent args) + { + if (!TrySting(uid, comp, args, true)) + return; + + var target = args.Target; + if (!TryStealDNA(uid, target, comp, true)) + { + _popup.PopupEntity(Loc.GetString("changeling-sting-extract-fail"), uid, uid); + // royal cashback + comp.Chemicals += Comp(args.Action).ChemicalCost; + } + else _popup.PopupEntity(Loc.GetString("changeling-sting", ("target", Identity.Entity(target, EntityManager))), uid, uid); + } + + private void OnTransformCycle(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformCycleEvent args) + { + comp.AbsorbedDNAIndex += 1; + if (comp.AbsorbedDNAIndex >= comp.MaxAbsorbedDNA || comp.AbsorbedDNAIndex >= comp.AbsorbedDNA.Count - 1) + comp.AbsorbedDNAIndex = 0; + + if (comp.AbsorbedDNA.Count == 0) + { + _popup.PopupEntity(Loc.GetString("changeling-transform-cycle-empty"), uid, uid); + return; + } + + var selected = comp.AbsorbedDNA.ToArray()[comp.AbsorbedDNAIndex]; + comp.SelectedForm = selected; + _popup.PopupEntity(Loc.GetString("changeling-transform-cycle", ("target", selected.Name)), uid, uid); + } + private void OnTransform(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryTransform(uid, comp)) + comp.Chemicals += Comp(args.Action).ChemicalCost; + } + + private void OnEnterStasis(EntityUid uid, ChangelingComponent comp, ref EnterStasisEvent args) + { + if (comp.IsInStasis || HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("changeling-stasis-enter-fail"), uid, uid); + return; + } + + if (!TryUseAbility(uid, comp, args)) + return; + + if (_mobState.IsAlive(uid)) + { + // fake our death + var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", uid)); + _popup.PopupEntity(othersMessage, uid, Robust.Shared.Player.Filter.PvsExcept(uid), true); + + var selfMessage = Loc.GetString("changeling-stasis-enter"); + _popup.PopupEntity(selfMessage, uid, uid); + } + + if (!_mobState.IsDead(uid)) + _mobState.ChangeMobState(uid, MobState.Dead); + + // faster regen + comp.ChemicalRegenerationModifier += 2; + + comp.IsInStasis = true; + } + private void OnExitStasis(EntityUid uid, ChangelingComponent comp, ref ExitStasisEvent args) + { + if (!comp.IsInStasis) + { + _popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail"), uid, uid); + return; + } + if (HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail-dead"), uid, uid); + return; + } + + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryComp(uid, out var damageable)) + return; + + // heal of everything + _damage.SetAllDamage(uid, damageable, 0); + _mobState.ChangeMobState(uid, MobState.Alive); + _blood.TryModifyBloodLevel(uid, 1000); + _blood.TryModifyBleedAmount(uid, -1000); + + _popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), uid, uid); + + // slower regen + comp.ChemicalRegenerationModifier -= 2; + + comp.IsInStasis = false; + } + + #endregion + + #region Combat Abilities + + private void OnToggleArmblade(EntityUid uid, ChangelingComponent comp, ref ToggleArmbladeEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryToggleItem(uid, ArmbladePrototype, ref comp.ArmbladeEntity)) + return; + + if (comp.ArmbladeEntity != null) + { + comp.ChemicalRegenerationAbilityModifier -= .5f; + _popup.PopupEntity(Loc.GetString("changeling-armblade-start"), uid, uid); + } + else + { + comp.ChemicalRegenerationAbilityModifier += .5f; + _popup.PopupEntity(Loc.GetString("changeling-hand-transform-end"), uid, uid); + } + + PlayMeatySound(uid, comp); + } + private void OnCreateBoneShard(EntityUid uid, ChangelingComponent comp, ref CreateBoneShardEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var star = EntityManager.SpawnEntity(BoneShardPrototype, Transform(uid).Coordinates); + _hands.TryPickupAnyHand(uid, star); + + PlayMeatySound(uid, comp); + } + private void OnToggleArmor(EntityUid uid, ChangelingComponent comp, ref ToggleChitinousArmorEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryToggleItem(uid, ArmorPrototype, ref comp.ArmorEntity, "outerClothing") + || !TryToggleItem(uid, ArmorHelmetPrototype, ref comp.ArmorHelmetEntity, "head")) + { + _popup.PopupEntity(Loc.GetString("changeling-equip-armor-fail"), uid, uid); + return; + } + + if (comp.ArmorEntity != null) + { + comp.ChemicalRegenerationAbilityModifier -= .5f; + _popup.PopupEntity(Loc.GetString("changeling-equip-armor-start"), uid, uid); + } + else + { + comp.ChemicalRegenerationAbilityModifier += .5f; + _popup.PopupEntity(Loc.GetString("changeling-equip-end"), uid, uid); + } + + PlayMeatySound(uid, comp); + } + private void OnToggleShield(EntityUid uid, ChangelingComponent comp, ref ToggleOrganicShieldEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryToggleItem(uid, ShieldPrototype, ref comp.ShieldEntity)) + return; + + if (comp.ShieldEntity != null) + _popup.PopupEntity(Loc.GetString("changeling-shield-start"), uid, uid); + else + _popup.PopupEntity(Loc.GetString("changeling-hand-transform-end"), uid, uid); + + PlayMeatySound(uid, comp); + } + private void OnShriekDissonant(EntityUid uid, ChangelingComponent comp, ref ShriekDissonantEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + PlayShriekSound(uid, comp); + + var pos = _transform.GetMapCoordinates(uid); + var power = comp.ShriekPower; + _emp.EmpPulse(pos, power, 5000f, power * 2); + } + private void OnShriekResonant(EntityUid uid, ChangelingComponent comp, ref ShriekResonantEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + PlayShriekSound(uid, comp); + + var power = comp.ShriekPower; + _flash.FlashArea(uid, uid, power, power * 2f * 1000f); + + var lookup = _lookup.GetEntitiesInRange(uid, power); + var lights = GetEntityQuery(); + + foreach (var ent in lookup) + if (lights.HasComponent(ent)) + _light.TryDestroyBulb(ent); + } + private void OnToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp, ref ToggleStrainedMusclesEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + ToggleStrainedMuscles(uid, comp); + } + private void ToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp) + { + if (!comp.StrainedMusclesActivated) + { + _speed.ChangeBaseSpeed(uid, 125f, 150f, 1f); + _popup.PopupEntity(Loc.GetString("changeling-muscles-start"), uid, uid); + comp.StrainedMusclesActivated = true; + comp.ChemicalRegenerationAbilityModifier -= 1; + } + else + { + _speed.ChangeBaseSpeed(uid, 100f, 100f, 1f); + _popup.PopupEntity(Loc.GetString("changeling-muscles-end"), uid, uid); + comp.StrainedMusclesActivated = false; + comp.ChemicalRegenerationAbilityModifier += 1; + } + + PlayMeatySound(uid, comp); + } + + #endregion + + #region Stings + + private void OnStingBlind(EntityUid uid, ChangelingComponent comp, ref StingBlindEvent args) + { + if (!TrySting(uid, comp, args)) + return; + + var target = args.Target; + if (!TryComp(target, out var blindable) || blindable.IsBlind) + return; + + _blindable.AdjustEyeDamage((target, blindable), 5); + var timeSpan = TimeSpan.FromSeconds(5f); + _statusEffect.TryAddStatusEffect(target, TemporaryBlindnessSystem.BlindingStatusEffect, timeSpan, false, TemporaryBlindnessSystem.BlindingStatusEffect); + } + private void OnStingCryo(EntityUid uid, ChangelingComponent comp, ref StingCryoEvent args) + { + var reagents = new List<(string, FixedPoint2)>() + { + ("Fresium", 20f), + ("ChloralHydrate", 10f) + }; + + if (!TryReagentSting(uid, comp, args, reagents)) + return; + } + private void OnStingLethargic(EntityUid uid, ChangelingComponent comp, ref StingLethargicEvent args) + { + var reagents = new List<(string, FixedPoint2)>() + { + ("Impedrezene", 10f), + ("Happiness", 5f), + ("MuteToxin", 5f) + }; + + if (!TryReagentSting(uid, comp, args, reagents)) + return; + } + private void OnStingMute(EntityUid uid, ChangelingComponent comp, ref StingMuteEvent args) + { + var reagents = new List<(string, FixedPoint2)>() + { + ("MuteToxin", 15f) + }; + + if (!TryReagentSting(uid, comp, args, reagents)) + return; + } + private void OnStingTransform(EntityUid uid, ChangelingComponent comp, ref StingTransformEvent args) + { + if (!TrySting(uid, comp, args, true)) + return; + + var target = args.Target; + if (!TryTransform(target, comp, true, true)) + comp.Chemicals += Comp(args.Action).ChemicalCost; + } + private void OnStingFakeArmblade(EntityUid uid, ChangelingComponent comp, ref StingFakeArmbladeEvent args) + { + if (!TrySting(uid, comp, args)) + return; + + var target = args.Target; + var fakeArmblade = EntityManager.SpawnEntity(FakeArmbladePrototype, Transform(target).Coordinates); + _hands.TryPickupAnyHand(target, fakeArmblade); + + PlayMeatySound(target, comp); + } + + #endregion + + #region Utilities + + public void OnAnatomicPanacea(EntityUid uid, ChangelingComponent comp, ref ActionAnatomicPanaceaEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var reagents = new List<(string, FixedPoint2)>() + { + ("Diphenhydramine", 5f), + ("Arithrazine", 10f), + ("Ethylredoxrazine", 5f) + }; + if (TryInjectReagents(uid, reagents)) + _popup.PopupEntity(Loc.GetString("changeling-panacea"), uid, uid); + else return; + PlayMeatySound(uid, comp); + } + public void OnAugmentedEyesight(EntityUid uid, ChangelingComponent comp, ref ActionAugmentedEyesightEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("changeling-passive-active"), uid, uid); + return; + } + + PlayMeatySound(uid, comp); + EnsureComp(uid); + _popup.PopupEntity(Loc.GetString("changeling-passive-activate"), uid, uid); + PlayMeatySound(uid, comp); + } + public void OnBiodegrade(EntityUid uid, ChangelingComponent comp, ref ActionBiodegradeEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (TryComp(uid, out var cuffs) && cuffs.Container.ContainedEntities.Count > 0) + { + var cuff = cuffs.LastAddedCuffs; + + _cuffs.Uncuff(uid, cuffs.LastAddedCuffs, cuff); + QueueDel(cuff); + } + + var soln = new Solution(); + soln.AddReagent("PolytrinicAcid", 10f); + + if (_pull.IsPulled(uid)) + { + var puller = Comp(uid).Puller; + if (puller != null) + { + _puddle.TrySplashSpillAt((EntityUid) puller, Transform((EntityUid) puller).Coordinates, soln, out _); + return; + } + } + _puddle.TrySplashSpillAt(uid, Transform(uid).Coordinates, soln, out _); + } + public void OnChameleonSkin(EntityUid uid, ChangelingComponent comp, ref ActionChameleonSkinEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (HasComp(uid) && HasComp(uid)) + { + comp.ChemicalRegenerationAbilityModifier += 1f; + RemComp(uid); + _popup.PopupEntity(Loc.GetString("changeling-chameleon-end"), uid, uid); + return; + } + + comp.ChemicalRegenerationAbilityModifier -= 1f; + EnsureComp(uid); + EnsureComp(uid); + _popup.PopupEntity(Loc.GetString("changeling-chameleon-start"), uid, uid); + } + public void OnEphedrineOverdose(EntityUid uid, ChangelingComponent comp, ref ActionEphedrineOverdoseEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var reagents = new List<(string, FixedPoint2)>() + { + ("Desoxyephedrine", 10f), + ("Stimulants", 10f) + }; + if (TryInjectReagents(uid, reagents)) + _popup.PopupEntity(Loc.GetString("changeling-inject"), uid, uid); + else + { + _popup.PopupEntity(Loc.GetString("changeling-inject-fail"), uid, uid); + return; + } + PlayMeatySound(uid, comp); + } + // john space made me do this + public void OnHealUltraSwag(EntityUid uid, ChangelingComponent comp, ref ActionFleshmendEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var reagents = new List<(string, FixedPoint2)>() + { + ("Ichor", 10f), + ("TranexamicAcid", 5f) + }; + if (TryInjectReagents(uid, reagents)) + _popup.PopupEntity(Loc.GetString("changeling-fleshmend"), uid, uid); + else return; + PlayMeatySound(uid, comp); + } + public void OnLastResort(EntityUid uid, ChangelingComponent comp, ref ActionLastResortEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + // todo: implement + } + public void OnLesserForm(EntityUid uid, ChangelingComponent comp, ref ActionLesserFormEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var poly = new PolymorphConfiguration() + { + Entity = "MobMonkey", + Forced = true, + TransferDamage = true, + Inventory = PolymorphInventoryChange.Drop, + RevertOnCrit = false, + RevertOnDeath = false + }; + var newUid = _polymorph.PolymorphEntity(uid, poly); + + if (newUid == null) + return; + + RemCompDeferred(newUid.Value); + + // copy our stuff + var lingCompCopy = _serialization.CreateCopy(comp, notNullableOverride: true); + AddComp(newUid.Value, lingCompCopy, true); + + if (TryComp(uid, out var storeComp)) + { + var storeCompCopy = _serialization.CreateCopy(storeComp, notNullableOverride: true); + RemComp(newUid.Value); + EntityManager.AddComponent(newUid.Value, storeCompCopy); + } + RemCompDeferred(uid); + + PlayMeatySound((EntityUid) newUid, comp); + var loc = Loc.GetString("changeling-transform-others", ("user", Identity.Entity((EntityUid) newUid, EntityManager))); + _popup.PopupEntity(loc, (EntityUid) newUid, PopupType.LargeCaution); + + comp.IsInLesserForm = true; + } + public void OnSpacesuit(EntityUid uid, ChangelingComponent comp, ref ActionSpacesuitEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryToggleItem(uid, SpacesuitPrototype, ref comp.SpacesuitEntity, "outerClothing") + || !TryToggleItem(uid, SpacesuitHelmetPrototype, ref comp.SpacesuitHelmetEntity, "head")) + { + _popup.PopupEntity(Loc.GetString("changeling-equip-armor-fail"), uid, uid); + return; + } + + if (comp.SpacesuitEntity != null) + { + comp.ChemicalRegenerationAbilityModifier -= .5f; + _popup.PopupEntity(Loc.GetString("changeling-equip-spacesuit-start"), uid, uid); + } + else + { + comp.ChemicalRegenerationAbilityModifier += .5f; + _popup.PopupEntity(Loc.GetString("changeling-equip-end"), uid, uid); + } + + PlayMeatySound(uid, comp); + } + public void OnHivemindAccess(EntityUid uid, ChangelingComponent comp, ref ActionHivemindAccessEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("changeling-passive-active"), uid, uid); + return; + } + + EnsureComp(uid); + _popup.PopupEntity(Loc.GetString("changeling-hivemind-start"), uid, uid); + } + + #endregion +} diff --git a/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs b/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs new file mode 100644 index 0000000000..d2a1d29053 --- /dev/null +++ b/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs @@ -0,0 +1,116 @@ +using Content.Server.Antag; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.Mind; +using Content.Server.Objectives; +using Content.Server.Roles; +using Content.Shared.Changeling; +using Content.Shared.IdentityManagement; +using Content.Shared.NPC.Prototypes; +using Content.Shared.NPC.Systems; +using Content.Shared.Roles; +using Content.Shared.Store; +using Content.Shared.Store.Components; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; +using System.Text; + +namespace Content.Server.GameTicking.Rules; + +public sealed partial class ChangelingRuleSystem : GameRuleSystem +{ + [Dependency] private readonly MindSystem _mind = default!; + [Dependency] private readonly AntagSelectionSystem _antag = default!; + [Dependency] private readonly SharedRoleSystem _role = default!; + [Dependency] private readonly NpcFactionSystem _npcFaction = default!; + [Dependency] private readonly ObjectivesSystem _objective = default!; + + public readonly SoundSpecifier BriefingSound = new SoundPathSpecifier("/Audio/Goobstation/Ambience/Antag/changeling_start.ogg"); + + public readonly ProtoId ChangelingPrototypeId = "Changeling"; + + public readonly ProtoId ChangelingFactionId = "Changeling"; + + public readonly ProtoId NanotrasenFactionId = "NanoTrasen"; + + public readonly ProtoId Currency = "EvolutionPoint"; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnSelectAntag); + SubscribeLocalEvent(OnTextPrepend); + } + + private void OnSelectAntag(EntityUid uid, ChangelingRuleComponent comp, ref AfterAntagEntitySelectedEvent args) + { + MakeChangeling(args.EntityUid, comp); + } + public bool MakeChangeling(EntityUid target, ChangelingRuleComponent rule) + { + if (!_mind.TryGetMind(target, out var mindId, out var mind)) + return false; + + // briefing + TryComp(target, out var metaData); + + var briefing = Loc.GetString("changeling-role-greeting", ("name", metaData?.EntityName ?? "Unknown")); + var briefingShort = Loc.GetString("changeling-role-greeting-short", ("name", metaData?.EntityName ?? "Unknown")); + + _antag.SendBriefing(target, briefing, Color.Yellow, BriefingSound); + + _role.MindAddRole(mindId, new RoleBriefingComponent { Briefing = briefingShort }, mind, true); + + // hivemind stuff + _npcFaction.RemoveFaction(target, NanotrasenFactionId, false); + _npcFaction.AddFaction(target, ChangelingFactionId); + + // make sure it's initial chems are set to max + var lingComp = EnsureComp(target); + lingComp.Chemicals = lingComp.MaxChemicals; + + // add store + var store = EnsureComp(target); + foreach (var category in rule.StoreCategories) + store.Categories.Add(category); + store.CurrencyWhitelist.Add(Currency); + store.Balance.Add(Currency, 16); + + rule.ChangelingMinds.Add(mindId); + + foreach (var objective in rule.Objectives) + _mind.TryAddObjective(mindId, mind, objective); + + return true; + } + + private void OnTextPrepend(EntityUid uid, ChangelingRuleComponent comp, ref ObjectivesTextPrependEvent args) + { + EntityUid? mostAbsorbedUid = null; + EntityUid? mostStolenUid = null; + var mostAbsorbed = 0f; + var mostStolen = 0f; + + foreach (var ling in EntityQuery()) + { + if (ling.TotalAbsorbedEntities > mostAbsorbed) + { + mostAbsorbed = ling.TotalAbsorbedEntities; + mostAbsorbedUid = ling.Owner; + } + if (ling.TotalStolenDNA > mostStolen) + { + mostStolen = ling.TotalStolenDNA; + mostStolenUid = ling.Owner; + } + } + + var sb = new StringBuilder(); + if (mostAbsorbedUid != null) + sb.AppendLine(Loc.GetString("roundend-prepend-changeling-absorbed", ("name", Identity.Entity((EntityUid) mostAbsorbedUid, EntityManager)), ("number", mostStolen))); + if (mostStolenUid != null) + sb.AppendLine(Loc.GetString("roundend-prepend-changeling-stolen", ("name", Identity.Entity((EntityUid) mostStolenUid, EntityManager)), ("number", mostStolen))); + + args.Text = sb.ToString(); + } +} diff --git a/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs b/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs new file mode 100644 index 0000000000..e53bcaaa77 --- /dev/null +++ b/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs @@ -0,0 +1,27 @@ +using Content.Shared.NPC.Prototypes; +using Content.Shared.Roles; +using Content.Shared.Store; +using Robust.Shared.Audio; +using Robust.Shared.Prototypes; + +namespace Content.Server.GameTicking.Rules.Components; + +[RegisterComponent, Access(typeof(ChangelingRuleSystem))] +public sealed partial class ChangelingRuleComponent : Component +{ + public readonly List ChangelingMinds = new(); + + public readonly List> StoreCategories = new() + { + "ChangelingAbilityCombat", + "ChangelingAbilitySting", + "ChangelingAbilityUtility" + }; + + public readonly List> Objectives = new() + { + "ChangelingAbsorbObjective", + "ChangelingStealDNAObjective", + "EscapeIdentityObjective" + }; +} diff --git a/Content.Server/Goobstation/Objectives/Components/AbsorbConditionComponent.cs b/Content.Server/Goobstation/Objectives/Components/AbsorbConditionComponent.cs new file mode 100644 index 0000000000..764e90d763 --- /dev/null +++ b/Content.Server/Goobstation/Objectives/Components/AbsorbConditionComponent.cs @@ -0,0 +1,11 @@ +using Content.Server.Changeling; +using Content.Server.Objectives.Systems; + +namespace Content.Server.Objectives.Components; + +[RegisterComponent, Access(typeof(ChangelingObjectiveSystem), typeof(ChangelingSystem))] +public sealed partial class AbsorbConditionComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float Absorbed = 0f; +} diff --git a/Content.Server/Goobstation/Objectives/Components/ImpersonateConditionComponent.cs b/Content.Server/Goobstation/Objectives/Components/ImpersonateConditionComponent.cs new file mode 100644 index 0000000000..f94d5db6f9 --- /dev/null +++ b/Content.Server/Goobstation/Objectives/Components/ImpersonateConditionComponent.cs @@ -0,0 +1,29 @@ +using Content.Server.Objectives.Systems; + +namespace Content.Server.Objectives.Components; + +/// +/// Requires that you have the same identity a target for a certain length of time before the round ends. +/// Obviously the agent id will work for this, but it's assumed that you will kill the target to prevent suspicion. +/// Depends on to function. +/// +[RegisterComponent, Access(typeof(ImpersonateConditionSystem))] +public sealed partial class ImpersonateConditionComponent : Component +{ + /// + /// Name that must match your identity for greentext. + /// This is stored once after the objective is assigned: + /// 1. to be a tiny bit more efficient + /// 2. to prevent the name possibly changing when borging or anything else and messing you up + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public string? Name; + + /// + /// Mind this objective got assigned to, used to continiously checkd impersonation. + /// + [DataField, ViewVariables(VVAccess.ReadWrite)] + public EntityUid? MindId; + + public bool Completed = false; +} diff --git a/Content.Server/Goobstation/Objectives/Components/StealDNAConditionComponent.cs b/Content.Server/Goobstation/Objectives/Components/StealDNAConditionComponent.cs new file mode 100644 index 0000000000..409ff8a30a --- /dev/null +++ b/Content.Server/Goobstation/Objectives/Components/StealDNAConditionComponent.cs @@ -0,0 +1,11 @@ +using Content.Server.Changeling; +using Content.Server.Objectives.Systems; + +namespace Content.Server.Objectives.Components; + +[RegisterComponent, Access(typeof(ChangelingObjectiveSystem), typeof(ChangelingSystem))] +public sealed partial class StealDNAConditionComponent : Component +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float DNAStolen = 0f; +} diff --git a/Content.Server/Goobstation/Objectives/Systems/ChangelingObjectiveSystem.cs b/Content.Server/Goobstation/Objectives/Systems/ChangelingObjectiveSystem.cs new file mode 100644 index 0000000000..6633bb07de --- /dev/null +++ b/Content.Server/Goobstation/Objectives/Systems/ChangelingObjectiveSystem.cs @@ -0,0 +1,32 @@ +using Content.Server.Objectives.Components; +using Content.Shared.Objectives.Components; + +namespace Content.Server.Objectives.Systems; + +public sealed partial class ChangelingObjectiveSystem : EntitySystem +{ + [Dependency] private readonly NumberObjectiveSystem _number = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAbsorbGetProgress); + SubscribeLocalEvent(OnStealDNAGetProgress); + } + + private void OnAbsorbGetProgress(EntityUid uid, AbsorbConditionComponent comp, ref ObjectiveGetProgressEvent args) + { + var target = _number.GetTarget(uid); + if (target != 0) + args.Progress = MathF.Min(comp.Absorbed / target, 1f); + else args.Progress = 1f; + } + private void OnStealDNAGetProgress(EntityUid uid, StealDNAConditionComponent comp, ref ObjectiveGetProgressEvent args) + { + var target = _number.GetTarget(uid); + if (target != 0) + args.Progress = MathF.Min(comp.DNAStolen / target, 1f); + else args.Progress = 1f; + } +} diff --git a/Content.Server/Goobstation/Objectives/Systems/ImpersonateConditionSystem.cs b/Content.Server/Goobstation/Objectives/Systems/ImpersonateConditionSystem.cs new file mode 100644 index 0000000000..c7cd0e3e29 --- /dev/null +++ b/Content.Server/Goobstation/Objectives/Systems/ImpersonateConditionSystem.cs @@ -0,0 +1,60 @@ +using Content.Server.Objectives.Components; +using Content.Shared.IdentityManagement; +using Content.Shared.Mind; +using Content.Shared.Objectives.Components; + +namespace Content.Server.Objectives.Systems; + +/// +/// Handles kill person condition logic and picking random kill targets. +/// +public sealed class ImpersonateConditionSystem : EntitySystem +{ + [Dependency] private readonly TargetObjectiveSystem _target = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnAfterAssign); + SubscribeLocalEvent(OnGetProgress); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var comp)) + { + if (comp.Name == null || comp.MindId == null) + continue; + + if (!TryComp(comp.MindId, out var mind) || mind.OwnedEntity == null) + continue; + if (!TryComp(mind.CurrentEntity, out var metaData)) + continue; + + if (metaData.EntityName == comp.Name) + comp.Completed = true; + else comp.Completed = false; + } + } + + private void OnAfterAssign(EntityUid uid, ImpersonateConditionComponent comp, ref ObjectiveAfterAssignEvent args) + { + if (!_target.GetTarget(uid, out var target)) + return; + + if (!TryComp(target, out var targetMind) || targetMind.CharacterName == null) + return; + + comp.Name = targetMind.CharacterName; + comp.MindId = args.MindId; + } + + private void OnGetProgress(EntityUid uid, ImpersonateConditionComponent comp, ref ObjectiveGetProgressEvent args) + { + args.Progress = comp.Completed ? 1f : 0f; + } +} diff --git a/Content.Server/Goobstation/Roles/ChangelingRoleComponent.cs b/Content.Server/Goobstation/Roles/ChangelingRoleComponent.cs new file mode 100644 index 0000000000..a1184c6795 --- /dev/null +++ b/Content.Server/Goobstation/Roles/ChangelingRoleComponent.cs @@ -0,0 +1,8 @@ +using Content.Shared.Roles; + +namespace Content.Server.Roles; + +[RegisterComponent, ExclusiveAntagonist] +public sealed partial class ChangelingRoleComponent : AntagonistRoleComponent +{ +} diff --git a/Content.Shared/Actions/ActionContainerSystem.cs b/Content.Shared/Actions/ActionContainerSystem.cs index 1c5a3ba0d9..dec57bdcc7 100644 --- a/Content.Shared/Actions/ActionContainerSystem.cs +++ b/Content.Shared/Actions/ActionContainerSystem.cs @@ -237,6 +237,19 @@ public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? ac DebugTools.AssertOwner(uid, comp); comp ??= EnsureComp(uid); + + // goobstation - changelings + if (!TryComp(actionId, out var actionData)) + return false; + if (!TryPrototype(actionId, out var actionProto, actionData)) + return false; + + if (HasAction(uid, actionProto.ID)) + { + Log.Debug($"Tried to insert action {ToPrettyString(actionId)} into {ToPrettyString(uid)}. Failed due to duplicate actions."); + return false; + } + if (!_container.Insert(actionId, comp.Container)) { Log.Error($"Failed to insert action {ToPrettyString(actionId)} into {ToPrettyString(uid)}"); @@ -250,6 +263,21 @@ public bool AddAction(EntityUid uid, EntityUid actionId, BaseActionComponent? ac return true; } + // goobstation - changelings + public bool HasAction(EntityUid uid, string pId, ActionsContainerComponent? actions = null) + { + if (!Resolve(uid, ref actions, false)) + return false; + + foreach (var act in actions.Container.ContainedEntities) + if (TryComp(act, out var metaData)) + if (TryPrototype(act, out var actProto, metaData)) + if (pId == actProto.ID) + return true; + + return false; + } + /// /// Removes an action from its container and any action-performer and moves the action to null-space /// diff --git a/Content.Shared/Content.Shared.csproj b/Content.Shared/Content.Shared.csproj index 9752bfcfe2..bd82b9fcd4 100644 --- a/Content.Shared/Content.Shared.csproj +++ b/Content.Shared/Content.Shared.csproj @@ -26,6 +26,9 @@ + + + diff --git a/Content.Shared/Goobstation/Changeling/AbsorbableComponent.cs b/Content.Shared/Goobstation/Changeling/AbsorbableComponent.cs new file mode 100644 index 0000000000..57fa59d1b1 --- /dev/null +++ b/Content.Shared/Goobstation/Changeling/AbsorbableComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling; + +/// +/// Component that indicates that a person can be absorbed by a changeling. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class AbsorbableComponent : Component +{ + +} diff --git a/Content.Shared/Goobstation/Changeling/AbsorbedComponent.cs b/Content.Shared/Goobstation/Changeling/AbsorbedComponent.cs new file mode 100644 index 0000000000..36bed5222a --- /dev/null +++ b/Content.Shared/Goobstation/Changeling/AbsorbedComponent.cs @@ -0,0 +1,13 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling; + + +/// +/// Component that indicates that a person's DNA has been absorbed by a changeling. +/// +[RegisterComponent, NetworkedComponent, Access(typeof(AbsorbedSystem))] +public sealed partial class AbsorbedComponent : Component +{ + +} diff --git a/Content.Shared/Goobstation/Changeling/AbsorbedSystem.cs b/Content.Shared/Goobstation/Changeling/AbsorbedSystem.cs new file mode 100644 index 0000000000..f5d94cf7f8 --- /dev/null +++ b/Content.Shared/Goobstation/Changeling/AbsorbedSystem.cs @@ -0,0 +1,18 @@ +using Content.Shared.Examine; + +namespace Content.Shared.Changeling; + +public sealed partial class AbsorbedSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnExamine); + } + + private void OnExamine(EntityUid uid, AbsorbedComponent comp, ref ExaminedEvent args) + { + args.PushMarkup(Loc.GetString("changeling-absorb-onexamine")); + } +} diff --git a/Content.Shared/Goobstation/Changeling/Changeling.Actions.cs b/Content.Shared/Goobstation/Changeling/Changeling.Actions.cs new file mode 100644 index 0000000000..2c7bb2b86b --- /dev/null +++ b/Content.Shared/Goobstation/Changeling/Changeling.Actions.cs @@ -0,0 +1,68 @@ +using Content.Shared.Actions; +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling; + +[RegisterComponent, NetworkedComponent] +public sealed partial class ChangelingActionComponent : Component +{ + [DataField("chemicalCost")] + public float ChemicalCost = 0; + + [DataField("useInLesserForm")] + public bool UseWhileLesserForm = false; + + [DataField("requireAbsorbed")] + public float RequireAbsorbed = 0; +} + +#region Events - Basic + +public sealed partial class OpenEvolutionMenuEvent : InstantActionEvent { } +public sealed partial class AbsorbDNAEvent : EntityTargetActionEvent { } +public sealed partial class StingExtractDNAEvent : EntityTargetActionEvent { } +public sealed partial class ChangelingTransformCycleEvent : InstantActionEvent { } +public sealed partial class ChangelingTransformEvent : InstantActionEvent { } +public sealed partial class EnterStasisEvent : InstantActionEvent { } +public sealed partial class ExitStasisEvent : InstantActionEvent { } + +#endregion + +#region Events - Combat + +public sealed partial class ToggleArmbladeEvent : InstantActionEvent { } +public sealed partial class CreateBoneShardEvent : InstantActionEvent { } +public sealed partial class ToggleChitinousArmorEvent : InstantActionEvent { } +public sealed partial class ToggleOrganicShieldEvent : InstantActionEvent { } +public sealed partial class ShriekDissonantEvent : InstantActionEvent { } +public sealed partial class ShriekResonantEvent : InstantActionEvent { } +public sealed partial class ToggleStrainedMusclesEvent : InstantActionEvent { } + +#endregion + +#region Events - Sting + +public sealed partial class StingBlindEvent : EntityTargetActionEvent { } +public sealed partial class StingCryoEvent : EntityTargetActionEvent { } +public sealed partial class StingLethargicEvent : EntityTargetActionEvent { } +public sealed partial class StingMuteEvent : EntityTargetActionEvent { } +public sealed partial class StingFakeArmbladeEvent : EntityTargetActionEvent { } +public sealed partial class StingTransformEvent : EntityTargetActionEvent { } + +#endregion + +#region Events - Utility + +public sealed partial class ActionAnatomicPanaceaEvent : InstantActionEvent { } +public sealed partial class ActionAugmentedEyesightEvent : InstantActionEvent { } +public sealed partial class ActionBiodegradeEvent : InstantActionEvent { } +public sealed partial class ActionChameleonSkinEvent : InstantActionEvent { } +public sealed partial class ActionEphedrineOverdoseEvent : InstantActionEvent { } +public sealed partial class ActionFleshmendEvent : InstantActionEvent { } +public sealed partial class ActionLastResortEvent : InstantActionEvent { } +public sealed partial class ActionLesserFormEvent : InstantActionEvent { } +public sealed partial class ActionSpacesuitEvent : InstantActionEvent { } +public sealed partial class ActionHivemindAccessEvent : InstantActionEvent { } +public sealed partial class ActionContortBodyEvent : InstantActionEvent { } + +#endregion diff --git a/Content.Shared/Goobstation/Changeling/Changeling.DoAfter.cs b/Content.Shared/Goobstation/Changeling/Changeling.DoAfter.cs new file mode 100644 index 0000000000..4138052954 --- /dev/null +++ b/Content.Shared/Goobstation/Changeling/Changeling.DoAfter.cs @@ -0,0 +1,7 @@ +using Content.Shared.DoAfter; +using Robust.Shared.Serialization; + +namespace Content.Shared.Changeling; + +[Serializable, NetSerializable] +public sealed partial class AbsorbDNADoAfterEvent : SimpleDoAfterEvent { } diff --git a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs new file mode 100644 index 0000000000..f4d2a2dfad --- /dev/null +++ b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs @@ -0,0 +1,161 @@ +using Content.Shared.Humanoid; +using Content.Shared.StatusIcon; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + + + +namespace Content.Shared.Changeling; + +[RegisterComponent, NetworkedComponent] +[AutoGenerateComponentState] +public sealed partial class ChangelingComponent : Component +{ + [DataField("soundMeatPool")] + public List SoundPool = new() + { + new SoundPathSpecifier("/Audio/Effects/gib1.ogg"), + new SoundPathSpecifier("/Audio/Effects/gib2.ogg"), + new SoundPathSpecifier("/Audio/Effects/gib3.ogg"), + }; + + #region Abilities + + [DataField("soundShriek")] + public SoundSpecifier ShriekSound = new SoundPathSpecifier("/Audio/Goobstation/Changeling/Effects/changeling_shriek.ogg"); + + [DataField("shriekPower")] + public float ShriekPower = 2.5f; + + public readonly List> BaseChangelingActions = new() + { + "ActionEvolutionMenu", + "ActionAbsorbDNA", + "ActionStingExtractDNA", + "ActionChangelingTransformCycle", + "ActionChangelingTransform", + "ActionEnterStasis", + "ActionExitStasis" + }; + + public bool IsInStasis = false; + + public EntityUid? ArmbladeEntity; + public EntityUid? ShieldEntity; + public EntityUid? ArmorEntity, ArmorHelmetEntity; + public EntityUid? SpacesuitEntity, SpacesuitHelmetEntity; + + public bool StrainedMusclesActivated = false; + + public bool IsInLesserForm = false; + + #endregion + + #region Base + + /// + /// Current amount of chemicals changeling currently has. + /// + [DataField("chemicals"), AutoNetworkedField] + public float Chemicals = 100f; + + /// + /// Maximum amount of chemicals changeling can have. + /// + [DataField("maxChemicals"), AutoNetworkedField] + public float MaxChemicals = 100f; + + public float ChemicalDrain = 0f; + + public float UpdateAccumulator = 0f; + /// + /// Time in seconds to take before the update cycle. + /// + public readonly float UpdateTimer = 1f; + + public float ChemicalRegenerationMobStateModifier = 0f; + public float ChemicalRegenerationAbilityModifier = 0f; + /// + /// Modifier for chemical regeneration. Positive = faster, negative = slower. + /// + [ViewVariables(VVAccess.ReadOnly)] + public float ChemicalRegenerationModifier = 0f; + + + [ViewVariables(VVAccess.ReadOnly)] + public List AbsorbedDNA = new(); + /// + /// Index of . Used for switching forms. + /// + public int AbsorbedDNAIndex = 0; + + /// + /// Maximum amount of DNA a changeling can absorb. + /// + [DataField("maxDna")] + public int MaxAbsorbedDNA = 5; + + /// + /// Total absorbed DNA. Counts towards objectives. + /// + [ViewVariables(VVAccess.ReadOnly)] + public int TotalAbsorbedEntities = 0; + + /// + /// Total stolen DNA. Counts towards objectives. + /// + [ViewVariables(VVAccess.ReadOnly)] + public int TotalStolenDNA = 0; + + [ViewVariables(VVAccess.ReadOnly)] + public TransformData? CurrentForm; + + [ViewVariables(VVAccess.ReadOnly)] + public TransformData? SelectedForm; + + #endregion + /// + /// The status icon corresponding to the Changlings. + /// + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public ProtoId StatusIcon { get; set; } = "HivemindFaction"; +} + +[DataDefinition] +public partial struct TransformData +{ + /// + /// Entity's name. + /// + [DataField] + public string Name; + + /// + /// Entity's fingerprint, if it exists. + /// + [DataField] + public string? Fingerprint; + + /// + /// Entity's DNA. + /// + [DataField("dna")] + public string DNA; + + /// + /// Entity's humanoid appearance component. + /// + [ViewVariables(VVAccess.ReadOnly), NonSerialized] + public HumanoidAppearanceComponent Appearance; + + public static bool operator ==(TransformData one, TransformData two) + => one.Name == two.Name && one.Fingerprint == two.Fingerprint && one.DNA == two.DNA; + public static bool operator !=(TransformData one, TransformData two) + => !(one.Name == two.Name && one.Fingerprint == two.Fingerprint && one.DNA == two.DNA); + + + +} diff --git a/Content.Shared/Goobstation/Changeling/HivemindComponent.cs b/Content.Shared/Goobstation/Changeling/HivemindComponent.cs new file mode 100644 index 0000000000..bc4e821726 --- /dev/null +++ b/Content.Shared/Goobstation/Changeling/HivemindComponent.cs @@ -0,0 +1,12 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Changeling; + +/// +/// Used for identifying other changelings. +/// Indicates that a changeling has bought the hivemind access ability. +/// +[RegisterComponent, NetworkedComponent] +public sealed partial class HivemindComponent : Component +{ +} diff --git a/Resources/Audio/Goobstation/Ambience/Antag/changeling_start.ogg b/Resources/Audio/Goobstation/Ambience/Antag/changeling_start.ogg new file mode 100644 index 0000000000000000000000000000000000000000..1132ccca29c4be205bbc8c09113f9012b5fc10fa GIT binary patch literal 120933 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 z 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 byu~4phd6S`;~|b*b!Xmnac5Q~)=Vt`EPz-u literal 0 HcmV?d00001 diff --git a/Resources/Locale/en-US/Goobstation/Changeling/alerts/alerts.ftl b/Resources/Locale/en-US/Goobstation/Changeling/alerts/alerts.ftl new file mode 100644 index 0000000000..0bcf510741 --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/Changeling/alerts/alerts.ftl @@ -0,0 +1,2 @@ +alerts-changeling-chemicals-name = Chemicals +alerts-changeling-chemicals-desc = Spend chemicals to use your abilities. Slowly regenerates. diff --git a/Resources/Locale/en-US/Goobstation/Changeling/objectives/changeling.ftl b/Resources/Locale/en-US/Goobstation/Changeling/objectives/changeling.ftl new file mode 100644 index 0000000000..9245653028 --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/Changeling/objectives/changeling.ftl @@ -0,0 +1,7 @@ +objective-condition-absorb-title = Absorb {$count} humanoids. +objective-condition-absorb-description = I must absorb {$count} humanoids. It is necessary for my survival. + +objective-condition-stealdna-title = Steal {$count} compatible genomes. +objective-condition-stealdna-description = I must steal {$count} unique genomes. The hivemind network is no good, it needs to be special. + +objective-condition-escape-identity-title = Escape on the evacuation shuttle while being {$targetName}, {CAPITALIZE($job)}. \ No newline at end of file diff --git a/Resources/Locale/en-US/Goobstation/Changeling/radio_channels.ftl b/Resources/Locale/en-US/Goobstation/Changeling/radio_channels.ftl new file mode 100644 index 0000000000..a2eeebd9cb --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/Changeling/radio_channels.ftl @@ -0,0 +1 @@ +chat-radio-hivemind = Hivemind \ No newline at end of file diff --git a/Resources/Locale/en-US/Goobstation/Changeling/store/categories.ftl b/Resources/Locale/en-US/Goobstation/Changeling/store/categories.ftl new file mode 100644 index 0000000000..c0b4796701 --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/Changeling/store/categories.ftl @@ -0,0 +1,4 @@ +# Changeling +store-ling-category-combat = Combat +store-ling-category-sting = Stings +store-ling-category-utility = Utility diff --git a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl new file mode 100644 index 0000000000..187467367a --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl @@ -0,0 +1,61 @@ +# Abilities +changeling-chemicals-deficit = Not enough chemicals! +changeling-action-fail-lesserform = Can't use it while in lesser form! +changeling-action-fail-absorbed = Need to absorb {$number} more organics to use it! + +changeling-absorb-start = {CAPITALIZE(THE($user))} starts absorbing {CAPITALIZE(THE($target))}'s! +changeling-absorb-fail-incapacitated = You can't absorb it until it's not incapacitated. +changeling-absorb-fail-absorbed = You've already absorbed it. +changeling-absorb-fail-unabsorbable = The target is not absorbable. +changeling-absorb-end-self = Another organic absorbed. You are evolving. +changeling-absorb-end-self-ling = Another changeling absorbed. You are evolving more rapidly. +changeling-absorb-onexamine = [color=red]It is nothing but a husk of a person, an empty shell.[/color] + +changeling-transform-cycle = Switched to {$target}'s DNA. +changeling-transform-cycle-empty = You don't have any DNA strains! +changeling-transform-others = {CAPITALIZE(THE($user))}'s body twists and takes shape of another being! +changeling-transform-fail-self = You can't transform into your current form! +changeling-transform-fail-choose = You did not choose a form to transform into! +changeling-transform-finish = You are now {$target}. + +changeling-sting-fail-self = You tried to sting {CAPITALIZE(THE($target))}, but failed! +changeling-sting-fail-ling = Someone just tried to silently sting you! + +changeling-sting = You silently sting {CAPITALIZE(THE($target))} +changeling-sting-extract-max = Disposed of the first stored DNA to free up space for new DNA. +changeling-sting-extract-fail = Can't extract DNA, need another target + +changeling-stasis-enter = You enter regenerative stasis +changeling-stasis-enter-fail = Can't enter stasis! +changeling-stasis-exit = You exit regenerative stasis +changeling-stasis-exit-fail = We're not in a stasis! +changeling-stasis-exit-fail-dead = Can't exit stasis! + +changeling-hand-transform-end = Your arm takes back it's initial form +changeling-fail-hands = Need to drop something beforehand + +changeling-armblade-start = Your arm reforms into a grotesque blade +changeling-shield-start = Your arm reforms into a meat shield + +changeling-muscles-start = Your body feels a lot lighter +changeling-muscles-end = Your legs feel heavier + +changeling-equip-armor-fail = Need to get rid of existing outer clothing beforehand +changeling-equip-armor-start = Your body gets wrapped in a sturdy chitinous shell +changeling-equip-spacesuit-start = Your body transforms into a spaceproof abomination +changeling-equip-end = Your body takes back it's original shape + +changeling-inject = You inject yourself +changeling-inject-fail = Failed to inject yourself! + +changeling-passive-activate = Activated ability +changeling-passive-activate-fail = Failed to activate the ability +changeling-passive-active = Already active! + +changeling-fleshmend = Your body twists, sealing wounds and regenerating dead cells +changeling-panacea = You mutate and alter your DNA for better cell regeneration + +changeling-chameleon-start = You adapt your skin to the environment +changeling-chameleon-end = Your skin is losing it's translucency + +changeling-hivemind-start = We tune our brainwaves to match the hivemind frequency diff --git a/Resources/Locale/en-US/Goobstation/changeling/administration/antag.ftl b/Resources/Locale/en-US/Goobstation/changeling/administration/antag.ftl new file mode 100644 index 0000000000..3ba13f47e5 --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/changeling/administration/antag.ftl @@ -0,0 +1,3 @@ +admin-verb-make-changeling = Make the target into a changeling. + +admin-verb-text-make-changeling = Make Changeling \ No newline at end of file diff --git a/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl b/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl new file mode 100644 index 0000000000..36b0609a13 --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl @@ -0,0 +1,19 @@ +changeling-roundend-name = changeling + +objective-issuer-hivemind = [color=orange]Hivemind[/color] + +roundend-prepend-changeling-absorbed = [color=white]{$name}[/color] has absorbed [color=red]{$number}[/color] organics. +roundend-prepend-changeling-stolen = [color=white]{$name}[/color] has stolen [color=orange]{$number}[/color] DNA samples. + +changeling-gamemode-title = Changelings +changeling-gamemode-description = + The changeling hive has boarded the station, ready to take anything it desires - be it your equipment, your faces, or your lives! + +changeling-role-greeting = + You are a changeling who has absorbed and taken the form of {$name}! + Your objectives are listed in the character menu. + Use your abilities to the max to achieve them. + +changeling-role-greeting-short = + You are a changeling. + Your starting form is {$name}. diff --git a/Resources/Locale/en-US/Goobstation/changeling/prototypes/roles/antags.ftl b/Resources/Locale/en-US/Goobstation/changeling/prototypes/roles/antags.ftl new file mode 100644 index 0000000000..5d5e578d60 --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/changeling/prototypes/roles/antags.ftl @@ -0,0 +1,2 @@ +roles-antag-changeling-name = Changeling +roles-antag-changeling-description = Use your shapeshifting abilities to complete your objectives. \ No newline at end of file diff --git a/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl b/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl new file mode 100644 index 0000000000..043981ddd9 --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl @@ -0,0 +1,151 @@ + +# combat + +evolutionmenu-combat-armblade-name = Arm Blade +evolutionmenu-combat-armblade-desc = + Reform one of your arms into a grotesque blade, composed of bone and flesh, able to pry open airlocks and cut through your foes like butter. + Costs 15 chemicals on activation, slows chemical regeneration. + +evolutionmenu-combat-boneshard-name = Bone Shard +evolutionmenu-combat-boneshard-desc = + Break off shards of your bone and shape them into a throwing star which embeds into your foes. But a one timer opportinuty. + Requires you to absorb at least 1 organic to use the ability. + Costs 15 chemicals on activation. + +evolutionmenu-combat-armor-name = Chitinous Armor +evolutionmenu-combat-armor-desc = + Inflate your body into an all-consuming chitinous mass of armor. + Provides extensive protection against physical damage, but less against other types. + It massively slows your movement, and maintaining its shape slows chemical generation. + Costs 25 chemicals on activation, significantly slows chemical regeneration. + +evolutionmenu-combat-shield-name = Organic Shield +evolutionmenu-combat-shield-desc = + Reforms one of your arms into a large, fleshy shield. + Blocks attacks automatically, but very brittle. + Costs 20 chemicals on activation. + +evolutionmenu-combat-shriek-dissonant-name = Dissonant Shriek +evolutionmenu-combat-shriek-dissonant-desc = + You emit an EMP blast, which disables technology in the surrounding area, including radio headsets. + Good for escaping cyborgs and security. + Costs 30 chemicals on activation. + +evolutionmenu-combat-shriek-resonant-name = Resonant Shriek +evolutionmenu-combat-shriek-resonant-desc = + You emit a tone beyond the range of human hearing, + bursting lights and causing disorientation in an area around yourself. + Good for escaping groups, or hindering people from fleeing. + Costs 30 chemicals on activation. + +evolutionmenu-combat-strainedmuscles-name = Strained Muscles +evolutionmenu-combat-strainedmuscles-desc = + You reduce lactic acid buildup in your leg muscles, allowing you to move at extremely fast speeds. + While active, you will take steadily increments of stamina damage and eventually pass out. + Cost-free on activation, halts chemical regeneration. + +evolutionmenu-combat-spiders-name = Spider Infestation +evolutionmenu-combat-spiders-desc = + One full grown hunter spider spawns from your mouth, being loyal to only you, and friendly with other spiders. + You can spawn up to three spiders total, and change their orders via other actions. + Costs 50 chemicals. + IMPORTANT: You must absorb at least 5 organics to activate the ability. + +# sting + +evolutionmenu-sting-blind-name = Blind Sting +evolutionmenu-sting-blind-desc = + Silently sting an organic target, completely blinding them for a short time, and rendering them near-sighted until oculine is applied. + May be used while under the effects of Lesser Form. + Costs 30 chemicals. + +evolutionmenu-sting-cryo-name = Cryogenic Sting +evolutionmenu-sting-cryo-desc = + Inject an organic target with a cocktail of chemicals that chills the blood. + May be used while under the effects of Lesser Form. + Costs 30 chemicals. + +evolutionmenu-sting-lethargic-name = Lethargic Sting +evolutionmenu-sting-lethargic-desc = + Inject an organic target with a cocktail of anesthetics, slowing the victim down for a decent amount of time. + May be used while under the effects of Lesser Form. + Costs 50 chemicals. + +evolutionmenu-sting-mute-name = Mute Sting +evolutionmenu-sting-mute-desc = + Inject mute toxin into an organic target, completely silencing them for a while. + May be used while under the effects of Lesser Form. + Costs 30 chemicals. + +evolutionmenu-sting-transform-name = Transformation Sting +evolutionmenu-sting-transform-desc = + Inject some of your genome into an organic target, forcing their body to shapeshift into whoever you've chosen using the Cycle DNA ability. + May be used while under the effects of Lesser Form. + Requires you to absorb at least 2 organics to use the ability. + Costs 75 chemicals. + +evolutionmenu-sting-armblade-name = Fake Arm Blade Sting +evolutionmenu-sting-armblade-desc = + Inject some of your genome into an organic target, forcing their arm to shapeshift into a dull armblade. + May be used while under the effects of Lesser Form. + Requires you to absorb at least 1 organic to use the ability. + Costs 50 chemicals. + +# utility +evolutionmenu-utility-panacea-name = Anatomic Panacea +evolutionmenu-utility-panacea-desc = + Cure yourself of diseases, disabilities, radiation, toxins, drunkenness, and brain damage. Generally covers the things that fleshmend doesn't. + Costs 30 chemicals. + +evolutionmenu-utility-eyesight-name = Augmented Eyesight +evolutionmenu-utility-eyesight-desc = + Evolve additional features in your eyes, such as flash protection. + Cost-free on activation. + +evolutionmenu-utility-biodegrade-name = Biodegrade +evolutionmenu-utility-biodegrade-desc = + Vomit a caustic substance onto any restraints you may be wearing, allowing yourself to break free. + Using this ability while being grabbed will spit acid in your attackers face. + Costs 30 chemicals. + +evolutionmenu-utility-chameleon-name = Chameleon Skin +evolutionmenu-utility-chameleon-desc = + Alter the pigment in your skin to match your surroundings, rendering you invisible. + WARNING: Halts chemical regeneration! + Costs 20 chemicals. + +evolutionmenu-utility-stims-name = Ephedrine Overdose +evolutionmenu-utility-stims-desc = + Inject a cocktail of stimulants into yourself, quickly removing any stuns and giving yourself a speed boost. + Continuous injection is poisonous. + Costs 30 chemicals. + +evolutionmenu-utility-fleshmend-name = Fleshmend +evolutionmenu-utility-fleshmend-desc = + Rapidly heal yourself of all bruises and burns. Significantly weaker when below 20 normal body temperature. + Costs 35 chemicals. + +evolutionmenu-utility-lastresort-name = Last Resort +evolutionmenu-utility-lastresort-desc = + Abandon your current body and becomes a headslug in a last ditch effort to evade discovery or capture. + As a headslug, you can lay eggs inside anybody, and, after some time, a monkey with your original conciousness will burst out of the body. + Costs 20 chemicals. + +evolutionmenu-utility-lesserform-name = Lesser Form +evolutionmenu-utility-lesserform-desc = + Abandon your current form and turn into a sentient monkey. + Costs 20 chemicals. + +evolutionmenu-utility-spacesuit-name = Space Adaptation +evolutionmenu-utility-spacesuit-desc = + Get rid of useless tissue in order to facilitate space travel. A source of oxygen is still required for space walking. Slows chemical regeneration while active. + Costs 20 chemicals. + +evolutionmenu-utility-hivemindaccess-name = Hivemind Access +evolutionmenu-utility-hivemindaccess-desc = + Tunes our chemical receptors for hivemind communication, giving us access to the hivemind network, allowing us to recognize other changelings who have also bought this ability. + +evolutionmenu-utility-contort-name = Contort Body +evolutionmenu-utility-contort-desc = + Allows you to contort your body, making you capable of fitting yourself in and under things where you normally don't fit, such as tables and closed airlocks. + Costs 25 chemicals. diff --git a/Resources/Locale/en-US/Goobstation/changeling/store/currency.ftl b/Resources/Locale/en-US/Goobstation/changeling/store/currency.ftl new file mode 100644 index 0000000000..0398a8cc4e --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/changeling/store/currency.ftl @@ -0,0 +1 @@ +store-currency-display-evolutionpoints = Evolution Points \ No newline at end of file diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml index 0bcd71fbad..474dd8200a 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Species/vulpkanin.yml @@ -5,6 +5,7 @@ id: BaseMobVulpkanin abstract: true components: + - type: Absorbable # Goobstation - changelings - type: HumanoidAppearance species: Vulpkanin - type: Hunger diff --git a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml index c135ac2b82..38da683652 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/arachnid.yml @@ -5,6 +5,7 @@ id: BaseMobArachnid abstract: true components: + - type: Absorbable # Goobstation - changelings - type: Body prototype: Arachnid requiredLegs: 2 # It would be funny if arachnids could use their little back limbs to move around once they lose their legs, but just something to consider post-woundmed diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index c4906f6f97..99258e3bf0 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -200,6 +200,9 @@ type: HumanoidMarkingModifierBoundUserInterface - key: enum.StrippingUiKey.Key type: StrippableBoundUserInterface + # Goobstation - changelings + enum.StoreUiKey.Key: + type: StoreBoundUserInterface - type: Puller - type: Speech speechSounds: Alto @@ -379,6 +382,7 @@ - type: Body prototype: Human requiredLegs: 2 + - type: Absorbable # Goobstation - changelings - type: UserInterface interfaces: - key: enum.HumanoidMarkingModifierKey.Key # sure, this can go here too diff --git a/Resources/Prototypes/Entities/Mobs/Species/diona.yml b/Resources/Prototypes/Entities/Mobs/Species/diona.yml index e1628c620a..060189c170 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/diona.yml @@ -5,6 +5,7 @@ id: BaseMobDiona abstract: true components: + - type: Absorbable # Goobstation - changelings - type: HumanoidAppearance species: Diona - type: Hunger diff --git a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml index b38ea2634f..67fd75665e 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/dwarf.yml @@ -5,6 +5,7 @@ id: BaseMobDwarf abstract: true components: + - type: Absorbable # Goobstation - changelings - type: Hunger - type: Thirst - type: Carriable # Carrying system from nyanotrasen. diff --git a/Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml b/Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml index c514a6f1a0..0660ea53d0 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/gingerbread.yml @@ -5,6 +5,7 @@ id: BaseMobGingerbread abstract: true components: + - type: Absorbable # Goobstation - changelings - type: HumanoidAppearance species: Gingerbread - type: Icon diff --git a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml index 60ccc31d79..5295e8c4fe 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/harpy.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/harpy.yml @@ -31,6 +31,7 @@ type: HumanoidMarkingModifierBoundUserInterface - key: enum.StrippingUiKey.Key type: StrippableBoundUserInterface + - type: Absorbable - type: Sprite scale: 0.9, 0.9 layers: diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index ac373725ce..6b187c52d3 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -5,6 +5,7 @@ name: Urist McHands abstract: true components: + - type: Absorbable # Goobstation - changelings - type: Hunger - type: Icon # It will not have an icon in the adminspawn menu without this. Body parts seem fine for whatever reason. sprite: Mobs/Species/Human/parts.rsi diff --git a/Resources/Prototypes/Entities/Mobs/Species/moth.yml b/Resources/Prototypes/Entities/Mobs/Species/moth.yml index 1c55dcf0df..c99b4b3a1a 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/moth.yml @@ -5,6 +5,7 @@ id: BaseMobMoth abstract: true components: + - type: Absorbable # Goobstation - changelings - type: HumanoidAppearance species: Moth - type: Hunger diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index 35f9e9fa39..ff91d4c788 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -5,6 +5,7 @@ id: BaseMobReptilian abstract: true components: + - type: Absorbable # Goobstation - changelings - type: HumanoidAppearance species: Reptilian - type: Hunger diff --git a/Resources/Prototypes/Entities/Mobs/Species/slime.yml b/Resources/Prototypes/Entities/Mobs/Species/slime.yml index 0e05b0c827..af575159cd 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/slime.yml @@ -4,6 +4,7 @@ id: BaseMobSlimePerson abstract: true components: + - type: Absorbable # Goobstation - changelings - type: Hunger - type: Thirst - type: Carriable # Carrying system from nyanotrasen. diff --git a/Resources/Prototypes/Entities/Mobs/Species/vox.yml b/Resources/Prototypes/Entities/Mobs/Species/vox.yml index a271e9d084..71f484c132 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/vox.yml @@ -3,6 +3,7 @@ id: BaseMobVox abstract: true components: + - type: Absorbable # Goobstation - changelings - type: Hunger - type: Thirst - type: Icon diff --git a/Resources/Prototypes/Goobstation/Alerts/alerts.yml b/Resources/Prototypes/Goobstation/Alerts/alerts.yml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml new file mode 100644 index 0000000000..622dd38a4d --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml @@ -0,0 +1,552 @@ +# abilities + +# starting +- type: entity + id: ActionEvolutionMenu + name: Open evolution menu + description: Opens the evolution menu. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: evolution_menu + event: !type:OpenEvolutionMenuEvent {} + - type: ChangelingAction + chemicalCost: 0 + useInLesserForm: true + +- type: entity + id: ActionAbsorbDNA + name: Absorb DNA + description: Absorb the target's DNA, husking them in the process. Costs 25 chemicals. + noSpawn: true + components: + - type: EntityTargetAction + useDelay: 5 + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: absorb_dna + event: !type:AbsorbDNAEvent {} + - type: ChangelingAction + chemicalCost: 25 + +- type: entity + id: ActionStingExtractDNA + name: Extract DNA sting + description: Silently sting an organic target to steal their genetic information, allowing you to later transform into them. Costs 25 chemicals. + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: sting_extractdna + event: !type:StingExtractDNAEvent {} + - type: ChangelingAction + chemicalCost: 25 + +- type: entity + id: ActionChangelingTransformCycle + name: Cycle DNA + description: Cycle your available DNA. + noSpawn: true + components: + - type: InstantAction + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: transform_cycle + event: !type:ChangelingTransformCycleEvent {} + - type: ChangelingAction + chemicalCost: 0 + +- type: entity + id: ActionChangelingTransform + name: Transform + description: Use stolen genetic information to transform into another humanoid. Resets your abilities. Doesn't come with clothes. Costs 5 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 5 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: transform + event: !type:ChangelingTransformEvent {} + - type: ChangelingAction + chemicalCost: 5 + useInLesserForm: true + +- type: entity + id: ActionEnterStasis + name: Enter regenerative stasis + description: Fake your death and enter into a regenerative stasis. Costs 15 chemicals. + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + checkConsciousness: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: stasis_enter + event: !type:EnterStasisEvent {} + - type: ChangelingAction + chemicalCost: 15 + useInLesserForm: true + +- type: entity + id: ActionExitStasis + name: Exit stasis + description: Rise from the dead with full health. Costs 99 chemicals. + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + checkConsciousness: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: stasis_exit + event: !type:ExitStasisEvent {} + - type: ChangelingAction + chemicalCost: 99 + useInLesserForm: true + +# combat +- type: entity + id: ActionToggleArmblade + name: Toggle Arm Blade + description: Reform one of your arms into a strong blade, composed of bone and flesh. Retract on secondary use. Costs 15 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 2 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: armblade + event: !type:ToggleArmbladeEvent {} + - type: ChangelingAction + chemicalCost: 15 + +- type: entity + id: ActionCreateBoneShard + name: Form Bone Shard + description: Break off shards of your bone and shape them into a throwing star. Costs 15 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 1 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: bone_shard + event: !type:CreateBoneShardEvent {} + - type: ChangelingAction + chemicalCost: 15 + requireAbsorbed: 1 + +- type: entity + id: ActionToggleChitinousArmor + name: Toggle Armor + description: Inflate your body into an all-consuming chitinous mass of armor. Costs 25 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 2 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: chitinous_armor + event: !type:ToggleChitinousArmorEvent {} + - type: ChangelingAction + chemicalCost: 25 + +- type: entity + id: ActionToggleOrganicShield + name: Form Shield + description: Reform one of your arms into a large, fleshy shield. Costs 20 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 2 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: organic_shield + event: !type:ToggleOrganicShieldEvent {} + - type: ChangelingAction + chemicalCost: 20 + +- type: entity + id: ActionShriekDissonant + name: Dissonant Shriek + description: Emit an EMP blast, with just your voice. Costs 30 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 2 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: shriek_dissonant + event: !type:ShriekDissonantEvent {} + - type: ChangelingAction + chemicalCost: 30 + useInLesserForm: true + +- type: entity + id: ActionShriekResonant + name: Resonant Shriek + description: Disorient people and break lights, with just your voice. Costs 30 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 2 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: shriek_resonant + event: !type:ShriekResonantEvent {} + - type: ChangelingAction + chemicalCost: 30 + useInLesserForm: true + +- type: entity + id: ActionToggleStrainedMuscles + name: Strain Muscles + description: Move at extremely fast speeds. Deals stamina damage. + noSpawn: true + components: + - type: InstantAction + useDelay: 1 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: strained_muscles + event: !type:ToggleStrainedMusclesEvent {} + - type: ChangelingAction + chemicalCost: 0 + useInLesserForm: true + +# stings +- type: entity + id: ActionStingBlind + name: Blind Sting + description: Silently sting your target, blinding them for a short time and rendering them near sighted. Costs 30 chemicals. + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: sting_blind + event: !type:StingBlindEvent {} + - type: ChangelingAction + chemicalCost: 30 + useInLesserForm: true + +- type: entity + id: ActionStingCryo + name: Cryogenic Sting + description: Silently sting your target, constantly slowing and freezing them. Costs 30 chemicals. + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: sting_cryo + event: !type:StingCryoEvent {} + - type: ChangelingAction + chemicalCost: 30 + useInLesserForm: true + +- type: entity + id: ActionStingLethargic + name: Lethargic Sting + description: Silently inject a cocktail of anesthetics into the target. Costs 50 chemicals. + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: sting_lethargic + event: !type:StingLethargicEvent {} + - type: ChangelingAction + chemicalCost: 50 + useInLesserForm: true + +- type: entity + id: ActionStingMute + name: Mute Sting + description: Silently sting your target, completely silencing them for a short time. Costs 30 chemicals. + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: sting_mute + event: !type:StingMuteEvent {} + - type: ChangelingAction + chemicalCost: 30 + useInLesserForm: true + +- type: entity + id: ActionStingFakeArmblade + name: Fake Armblade Sting + description: Silently sting your target, making them grow a dull armblade for a short time. Costs 50 chemicals. + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: sting_armblade + event: !type:StingFakeArmbladeEvent {} + - type: ChangelingAction + chemicalCost: 50 + useInLesserForm: true + requireAbsorbed: 1 + +- type: entity + id: ActionStingTransform + name: Transformation Sting + description: Silently sting your target, transforming them into a person of your choosing. Costs 75 chemicals. + noSpawn: true + components: + - type: EntityTargetAction + whitelist: + components: + - Body + canTargetSelf: false + interactOnMiss: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: sting_transform + event: !type:StingTransformEvent {} + - type: ChangelingAction + chemicalCost: 75 + useInLesserForm: true + requireAbsorbed: 2 + +# utility + +- type: entity + id: ActionAnatomicPanacea + name: Anatomic Panacea + description: Cure yourself of diseases, disabilities, radiation, toxins, drunkedness and brain damage. Costs 30 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 5 + checkCanInteract: false + checkConsciousness: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: anatomic_panacea + event: !type:ActionAnatomicPanaceaEvent {} + - type: ChangelingAction + chemicalCost: 30 + useInLesserForm: true + +- type: entity + id: ActionAugmentedEyesight + name: Augmented Eyesight + description: Toggle flash protection. + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: augmented_eyesight + event: !type:ActionAugmentedEyesightEvent {} + - type: ChangelingAction + chemicalCost: 0 + +- type: entity + id: ActionBiodegrade + name: Biodegrade + description: Vomit a caustic substance onto any restraints, or someone's face. Costs 30 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 5 + checkCanInteract: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: biodegrade + event: !type:ActionBiodegradeEvent {} + - type: ChangelingAction + chemicalCost: 30 + +- type: entity + id: ActionChameleonSkin + name: Chameleon Skin + description: Slowly blend in with the environment. Costs 25 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 1 + checkCanInteract: false + checkConsciousness: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: chameleon_skin + event: !type:ActionChameleonSkinEvent {} + - type: ChangelingAction + chemicalCost: 20 + +- type: entity + id: ActionEphedrineOverdose + name: Ephedrine Overdose + description: Inject some stimulants into yourself. Costs 30 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 5 + checkCanInteract: false + checkConsciousness: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: epinephrine_overdose + event: !type:ActionEphedrineOverdoseEvent {} + - type: ChangelingAction + chemicalCost: 30 + useInLesserForm: true + +- type: entity + id: ActionFleshmend + name: Fleshmend + description: Rapidly heal yourself. Costs 35 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 5 + checkCanInteract: false + checkConsciousness: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: fleshmend + event: !type:ActionFleshmendEvent {} + - type: ChangelingAction + chemicalCost: 35 + useInLesserForm: true + +- type: entity + id: ActionLastResort + name: Last Resort + description: Abandon your current body and become a headslug. Costs 20 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 5 + checkCanInteract: false + checkConsciousness: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: last_resort + event: !type:ActionLastResortEvent {} + - type: ChangelingAction + chemicalCost: 20 + +- type: entity + id: ActionToggleLesserForm + name: Lesser Form + description: Abandon your current form and transform into a monkey. Costs 20 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 5 + checkCanInteract: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: lesser_form + event: !type:ActionLesserFormEvent {} + - type: ChangelingAction + chemicalCost: 20 + +- type: entity + id: ActionToggleSpacesuit + name: Toggle Space Suit + description: Make your body space proof. Costs 20 chemicals. + noSpawn: true + components: + - type: InstantAction + useDelay: 2 + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: space_adaptation + event: !type:ActionSpacesuitEvent {} + - type: ChangelingAction + chemicalCost: 20 + +- type: entity + id: ActionHivemindAccess + name: Hivemind Access + description: Tune your chemical receptors for hivemind communication. Costs 0 chemicals. + noSpawn: true + components: + - type: InstantAction + checkCanInteract: false + checkConsciousness: false + itemIconStyle: NoItem + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: hivemind_access + event: !type:ActionHivemindAccessEvent {} + - type: ChangelingAction + chemicalCost: 0 + useInLesserForm: true diff --git a/Resources/Prototypes/Goobstation/Changeling/Alerts/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Alerts/changeling.yml new file mode 100644 index 0000000000..4207e5610d --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Alerts/changeling.yml @@ -0,0 +1,17 @@ +- type: alert + id: Chemicals + icons: + - sprite: /Textures/Goobstation/Changeling/changeling_chemicals.rsi + state: 0 + alertViewEntity: AlertChemicalsSpriteView + name: alerts-changeling-chemicals-name + description: alerts-changeling-chemicals-desc + +- type: entity + id: AlertChemicalsSpriteView + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: /Textures/Goobstation/Changeling/changeling_chemicals.rsi + layers: + - map: [ "enum.AlertVisualLayers.Base" ] diff --git a/Resources/Prototypes/Goobstation/Changeling/Catalog/changeling_catalog.yml b/Resources/Prototypes/Goobstation/Changeling/Catalog/changeling_catalog.yml new file mode 100644 index 0000000000..00eba5e962 --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Catalog/changeling_catalog.yml @@ -0,0 +1,341 @@ +# combat + +- type: listing + id: EvolutionMenuCombatArmblade + name: evolutionmenu-combat-armblade-name + description: evolutionmenu-combat-armblade-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: armblade } + productAction: ActionToggleArmblade + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilityCombat + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuCombatBoneShard + name: evolutionmenu-combat-boneshard-name + description: evolutionmenu-combat-boneshard-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: bone_shard } + productAction: ActionCreateBoneShard + cost: + EvolutionPoint: 3 + categories: + - ChangelingAbilityCombat + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuCombatChitinousArmor + name: evolutionmenu-combat-armor-name + description: evolutionmenu-combat-armor-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: chitinous_armor } + productAction: ActionToggleChitinousArmor + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilityCombat + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuCombatOrganicShield + name: evolutionmenu-combat-shield-name + description: evolutionmenu-combat-shield-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: organic_shield } + productAction: ActionToggleOrganicShield + cost: + EvolutionPoint: 2 + categories: + - ChangelingAbilityCombat + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuCombatShriekDissonant + name: evolutionmenu-combat-shriek-dissonant-name + description: evolutionmenu-combat-shriek-dissonant-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: shriek_dissonant } + productAction: ActionShriekDissonant + cost: + EvolutionPoint: 2 + categories: + - ChangelingAbilityCombat + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuCombatShriekResonant + name: evolutionmenu-combat-shriek-resonant-name + description: evolutionmenu-combat-shriek-resonant-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: shriek_resonant } + productAction: ActionShriekResonant + cost: + EvolutionPoint: 2 + categories: + - ChangelingAbilityCombat + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuCombatMuscles + name: evolutionmenu-combat-strainedmuscles-name + description: evolutionmenu-combat-strainedmuscles-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: strained_muscles } + productAction: ActionToggleStrainedMuscles + cost: + EvolutionPoint: 2 + categories: + - ChangelingAbilityCombat + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +#- type: listing +# id: EvolutionMenuCombatSpiders +# name: evolutionmenu-combat-spiders-name +# description: evolutionmenu-combat-spiders-desc +# icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: spiders_spawn } +# productAction: ActionSpawnChangelingSpider +# cost: +# EvolutionPoint: 6 +# categories: +# - ChangelingAbilityCombat +# consitions: +# - !type:ListingLimitedStockCondition +# stock: 1 + +# sting + +- type: listing + id: EvolutionMenuStingBlind + name: evolutionmenu-sting-blind-name + description: evolutionmenu-sting-blind-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: sting_blind } + productAction: ActionStingBlind + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilitySting + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuStingCryo + name: evolutionmenu-sting-cryo-name + description: evolutionmenu-sting-cryo-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: sting_cryo } + productAction: ActionStingCryo + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilitySting + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuStingLethargic + name: evolutionmenu-sting-lethargic-name + description: evolutionmenu-sting-lethargic-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: sting_lethargic } + productAction: ActionStingLethargic + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilitySting + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuStingMute + name: evolutionmenu-sting-mute-name + description: evolutionmenu-sting-mute-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: sting_mute } + productAction: ActionStingMute + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilitySting + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuStingFakeArmblade + name: evolutionmenu-sting-armblade-name + description: evolutionmenu-sting-armblade-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: sting_armblade } + productAction: ActionStingFakeArmblade + cost: + EvolutionPoint: 5 + categories: + - ChangelingAbilitySting + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuStingTransform + name: evolutionmenu-sting-transform-name + description: evolutionmenu-sting-transform-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: sting_transform } + productAction: ActionStingTransform + cost: + EvolutionPoint: 6 + categories: + - ChangelingAbilitySting + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +# utility + +- type: listing + id: EvolutionMenuUtilityPanacea + name: evolutionmenu-utility-panacea-name + description: evolutionmenu-utility-panacea-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: anatomic_panacea } + productAction: ActionAnatomicPanacea + cost: + EvolutionPoint: 2 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuUtilityEyesight + name: evolutionmenu-utility-eyesight-name + description: evolutionmenu-utility-eyesight-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: augmented_eyesight } + productAction: ActionAugmentedEyesight + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuUtilityBiodegrade + name: evolutionmenu-utility-biodegrade-name + description: evolutionmenu-utility-biodegrade-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: biodegrade } + productAction: ActionBiodegrade + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuUtilityChameleon + name: evolutionmenu-utility-chameleon-name + description: evolutionmenu-utility-chameleon-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: chameleon_skin } + productAction: ActionChameleonSkin + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuUtilityOverdose + name: evolutionmenu-utility-stims-name + description: evolutionmenu-utility-stims-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: epinephrine_overdose } + productAction: ActionEphedrineOverdose + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuUtilityFleshmend + name: evolutionmenu-utility-fleshmend-name + description: evolutionmenu-utility-fleshmend-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: fleshmend } + productAction: ActionFleshmend + cost: + EvolutionPoint: 5 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +#- type: listing +# id: EvolutionMenuUtilityLastResort +# name: evolutionmenu-utility-lastresort-name +# description: evolutionmenu-utility-lastresort-desc +# icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: last_resort } +# productAction: ActionLastResort +# cost: +# EvolutionPoint: 2 +# categories: +# - ChangelingAbilityUtility +# conditions: +# - !type:ListingLimitedStockCondition +# stock: 1 + +- type: listing + id: EvolutionMenuUtilityLesserForm + name: evolutionmenu-utility-lesserform-name + description: evolutionmenu-utility-lesserform-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: lesser_form } + productAction: ActionToggleLesserForm + cost: + EvolutionPoint: 2 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuUtilitySpacesuit + name: evolutionmenu-utility-spacesuit-name + description: evolutionmenu-utility-spacesuit-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: space_adaptation } + productAction: ActionToggleSpacesuit + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 + +- type: listing + id: EvolutionMenuUtilityHivemindAccess + name: evolutionmenu-utility-hivemindaccess-name + description: evolutionmenu-utility-hivemindaccess-desc + icon: { sprite: Goobstation/Changeling/changeling_abilities.rsi, state: hivemind_access } + productAction: ActionHivemindAccess + cost: + EvolutionPoint: 4 + categories: + - ChangelingAbilityUtility + conditions: + - !type:ListingLimitedStockCondition + stock: 1 diff --git a/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/Head/hardsuit-helmets.yml b/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/Head/hardsuit-helmets.yml new file mode 100644 index 0000000000..de00f10e38 --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/Head/hardsuit-helmets.yml @@ -0,0 +1,16 @@ +- type: entity + parent: ClothingHeadHardsuitBase + id: ChangelingClothingHeadHelmetHardsuit + name: organic space helmet + description: A spaceworthy biomass of pressure and temperature resistant tissue. + suffix: Unremoveable + components: + - type: Item + - type: Sprite + sprite: Goobstation/Changeling/ling_spacesuit_helmet.rsi + - type: Clothing + sprite: Goobstation/Changeling/ling_spacesuit_helmet.rsi + - type: PressureProtection + highPressureMultiplier: 0.225 + lowPressureMultiplier: 1000 + - type: Unremoveable \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/Head/helmets.yml b/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/Head/helmets.yml new file mode 100644 index 0000000000..11d3e23b9f --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/Head/helmets.yml @@ -0,0 +1,19 @@ +- type: entity + parent: ClothingHeadBase + id: ChangelingClothingHeadHelmet + name: chitinous helmet + description: An all-consuming chitinous mass of armor. + suffix: Unremoveable + components: + - type: Sprite + sprite: Goobstation/Changeling/ling_armor_helmet.rsi + - type: Clothing + sprite: Goobstation/Changeling/ling_armor_helmet.rsi + - type: Armor + modifiers: + coefficients: + Blunt: 0.9 + Slash: 0.9 + Piercing: 0.9 + Heat: 0.9 + - type: Unremoveable \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/OuterClothing/armor.yml b/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/OuterClothing/armor.yml new file mode 100644 index 0000000000..9627c226e1 --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/OuterClothing/armor.yml @@ -0,0 +1,28 @@ +- type: entity + parent: ClothingOuterBaseLarge + id: ChangelingClothingOuterArmor + name: chitinous armor + description: An all-consuming chitinous mass of armor. + suffix: Unremoveable + components: + - type: Sprite + sprite: Goobstation/Changeling/ling_armor.rsi + - type: Clothing + sprite: Goobstation/Changeling/ling_armor.rsi + - type: Armor + modifiers: + coefficients: + Blunt: 0.5 + Slash: 0.5 + Piercing: 0.5 + Heat: 0.5 + Radiation: 0.5 + Caustic: 0.5 + - type: ClothingSpeedModifier + walkModifier: 0.7 + sprintModifier: 0.65 + - type: HeldSpeedModifier + - type: ExplosionResistance + damageCoefficient: 0.5 + - type: GroupExamine + - type: Unremoveable \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/OuterClothing/hardsuits.yml b/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/OuterClothing/hardsuits.yml new file mode 100644 index 0000000000..dda0aef11c --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Entities/Clothing/OuterClothing/hardsuits.yml @@ -0,0 +1,30 @@ +- type: entity + parent: ClothingOuterBaseLarge + id: ChangelingClothingOuterHardsuit + name: organic space suit + description: A spaceworthy biomass of pressure and temperature resistant tissue. + suffix: Unremoveable + components: + - type: Sprite + sprite: Goobstation/Changeling/ling_spacesuit.rsi + - type: Clothing + sprite: Goobstation/Changeling/ling_spacesuit.rsi + - type: PressureProtection + highPressureMultiplier: 0.225 + lowPressureMultiplier: 1000 + - type: TemperatureProtection + coefficient: 0.01 + - type: ExplosionResistance + damageCoefficient: 0.2 + - type: Armor + modifiers: + coefficients: + Blunt: 0.95 + Slash: 0.95 + Piercing: 1 + Heat: 0.5 + - type: ClothingSpeedModifier + walkModifier: 0.9 + sprintModifier: 0.9 + - type: HeldSpeedModifier + - type: Unremoveable \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Shields/shields.yml b/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Shields/shields.yml new file mode 100644 index 0000000000..b6f409f432 --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Shields/shields.yml @@ -0,0 +1,31 @@ +- type: entity + parent: BaseShield + id: ChangelingShield + name: oraganic shield + description: A large, fleshy shield. + suffix: Unremoveable + components: + - type: Unremoveable + - type: Sprite + sprite: Goobstation/Changeling/shields.rsi + state: ling-icon + - type: Item + sprite: Goobstation/Changeling/shields.rsi + heldPrefix: ling + - type: Destructible + thresholds: + - trigger: + !type:DamageTrigger + damage: 60 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - trigger: + !type:DamageTrigger + damage: 50 + behaviors: + - !type:DoActsBehavior + acts: [ "Destruction" ] + - !type:PlaySoundBehavior + sound: + collection: MetalBreak \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Weapons/Melee/changeling_armblade.yml b/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Weapons/Melee/changeling_armblade.yml new file mode 100644 index 0000000000..6caa150e3f --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Weapons/Melee/changeling_armblade.yml @@ -0,0 +1,48 @@ +- type: entity + parent: ArmBlade + id: ArmBladeChangeling + suffix: Unremoveable + components: + - type: Sharp + - type: Sprite + sprite: Goobstation/Changeling/arm_blade.rsi + state: icon + - type: MeleeWeapon + wideAnimationRotation: 90 + attackRate: 0.75 + damage: + types: + Blunt: 5 + Slash: 12.5 + Piercing: 10 + Structural: 10 + soundHit: + path: /Audio/Weapons/bladeslice.ogg + - type: Item + size: Ginormous + sprite: Goobstation/Changeling/arm_blade.rsi + - type: Prying + pryPowered: true + - type: Unremoveable + - type: Tool + qualities: + - Slicing + - Prying + - type: DisarmMalus + malus: 0 + +- type: entity + parent: ArmBladeChangeling + id: FakeArmBladeChangeling + components: + - type: MeleeWeapon + wideAnimationRotation: 90 + attackRate: 0.75 + damage: + types: + Blunt: 1 + Slash: 1 + Piercing: 1 + Structural: 1 + - type: TimedDespawn + lifetime: 60 \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Weapons/Throwable/throwing_stars.yml b/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Weapons/Throwable/throwing_stars.yml new file mode 100644 index 0000000000..20d076903d --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Entities/Objects/Weapons/Throwable/throwing_stars.yml @@ -0,0 +1,12 @@ +- type: entity + parent: ThrowingStar + id: ThrowingStarChangeling + name: bone shard + components: + - type: Sprite + sprite: Goobstation/Changeling/bone_shard.rsi + layers: + - state: icon + map: ["base"] + - type: TimedDespawn + lifetime: 30 \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml b/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml new file mode 100644 index 0000000000..b87dc611fc --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml @@ -0,0 +1,20 @@ +- type: entity + parent: BaseGameRule + id: Changeling + components: + - type: ChangelingRule + - type: GameRule + minPlayers: 10 + delay: + min: 10 + max: 20 + - type: AntagSelection + agentName: changeling-roundend-name + definitions: + - prefRoles: [ Changeling ] + max: 8 + playerRatio: 10 + lateJoinAdditional: true + mindComponents: + - type: ChangelingRole + prototype: Changeling \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml new file mode 100644 index 0000000000..1507faf14f --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml @@ -0,0 +1,57 @@ +- type: entity + abstract: true + parent: BaseObjective + id: BaseChangelingObjective + components: + - type: Objective + difficulty: 1.5 # unused but necessary i guess + issuer: hivemind + - type: RoleRequirement + roles: + components: + - ChangelingRole + +- type: entity + parent: BaseChangelingObjective + id: ChangelingAbsorbObjective + components: + - type: Objective + icon: + sprite: Mobs/Demons/abomination.rsi + state: dead + - type: NumberObjective + min: 2 + max: 4 + title: objective-condition-absorb-title + description: objective-condition-absorb-description + - type: AbsorbCondition + +- type: entity + parent: BaseChangelingObjective + id: ChangelingStealDNAObjective + components: + - type: Objective + icon: + sprite: Mobs/Species/Human/organs.rsi + state: brain + - type: NumberObjective + min: 6 + max: 9 + title: objective-condition-stealdna-title + description: objective-condition-stealdna-description + - type: StealDNACondition + +- type: entity + parent: BaseChangelingObjective + id: EscapeIdentityObjective + description: I need to escape on the evacuation shuttle. Undercover. + components: + - type: Objective + icon: + sprite: Objects/Magic/magicactions.rsi + state: blink + - type: ImpersonateCondition + - type: TargetObjective + title: objective-condition-escape-identity-title + - type: PickRandomPerson + - type: EscapeShuttleCondition \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Objectives/objectiveGroups.yml b/Resources/Prototypes/Goobstation/Changeling/Objectives/objectiveGroups.yml new file mode 100644 index 0000000000..e233d8524e --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Objectives/objectiveGroups.yml @@ -0,0 +1,16 @@ +# changeling + +- type: weightedRandom + id: ChangelingAbsorbObjectiveGroup + weights: + ChangelingAbsorbObjective: 1 + +- type: weightedRandom + id: ChangelingStealDNAObjectiveGroup + weights: + ChangelingStealDNAObjective: 1 + +- type: weightedRandom + id: EscapeIdentityObjectiveGroup + weights: + EscapeIdentityObjective: 1 \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml new file mode 100644 index 0000000000..dc88cecb5d --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/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-description \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/StatusIcon/antag.yml b/Resources/Prototypes/Goobstation/Changeling/StatusIcon/antag.yml new file mode 100644 index 0000000000..3fd6952603 --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/StatusIcon/antag.yml @@ -0,0 +1,10 @@ +- type: statusIcon + id: HivemindFaction + priority: 11 + showTo: + components: + - ShowAntagIcons + - Hivemind + icon: + sprite: /Textures/Interface/Misc/job_icons.rsi + state: Changeling diff --git a/Resources/Prototypes/Goobstation/Changeling/Store/categories.yml b/Resources/Prototypes/Goobstation/Changeling/Store/categories.yml new file mode 100644 index 0000000000..8444dea1bf --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Store/categories.yml @@ -0,0 +1,16 @@ +# changeling + +- type: storeCategory + id: ChangelingAbilityCombat + name: store-ling-category-combat + priority: 0 + +- type: storeCategory + id: ChangelingAbilitySting + name: store-ling-category-sting + priority: 1 + +- type: storeCategory + id: ChangelingAbilityUtility + name: store-ling-category-utility + priority: 2 diff --git a/Resources/Prototypes/Goobstation/Changeling/Store/currency.yml b/Resources/Prototypes/Goobstation/Changeling/Store/currency.yml new file mode 100644 index 0000000000..3ad315d05d --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Store/currency.yml @@ -0,0 +1,4 @@ +- type: currency + id: EvolutionPoint + displayName: store-currency-display-evolutionpoints + canWithdraw: false diff --git a/Resources/Prototypes/Goobstation/Changeling/ai_factions.yml b/Resources/Prototypes/Goobstation/Changeling/ai_factions.yml new file mode 100644 index 0000000000..9a6e1745af --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/ai_factions.yml @@ -0,0 +1,7 @@ +- type: npcFaction + id: Changeling + hostile: + - NanoTrasen + - Syndicate + - Zombie + - Revolutionary \ No newline at end of file diff --git a/Resources/Textures/Goobstation/Changeling/arm_blade.rsi/icon.png b/Resources/Textures/Goobstation/Changeling/arm_blade.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..94bfbcc36539a639b57bd9bcc6a81a7bc4596b27 GIT binary patch literal 876 zcmV-y1C#uTP)Px&CP_p=R9HvtmtANSR~Uw$nVmDUyWQPx*6xonf`ZUUgcQ7pBB?E6Xwg!or8ZD+ zA_#2>lw4b|1bWen-Ux+?Qjm%*rBE+~5-3P+EP>D_s9>Q~)6KFNt<7X-f6na8DKj-o zQ=`Ps#s)b^JbE+%XqoWcu;0SaAJ0rUxtVI>H75$ z{j-}kh0(Vf(WiU&s_$>ySoLzWnz4UI>{%Ay-MYome4ZuCLeq81i3De_UghKde(JW( zJG*vKx_OiCEnBdJ;H`7#TEVs+J{TNSc05jfYKp25C{5!TDY;`=552WDOp{OM=J+O) zL5oC~jm1bb8q~8{lx5MJpXczwgV@)u@q8x3d^*kh=g+So{Q&lnkr5@Oq%tvq4&n3- zgJ`pfGz?tB2ur6S1ct7Ys@L&!ou==jc^(VNB+*(8U&@wvs;`fP<8buCg$MLj&HLEc zn5tANlrCPx^nJW-+wdF*-}4Ys5_KFrrOZM+gp!)Z zyJyZk^n5+?;C+7Vn3@xU$h~{~I5`QLhA>TBDM6{f=f6gPJhik$Jsw9!B99bb)7S8e z~oWx=J;UHnbD`991m_)lwzsrA8cS zb;`yxLQ$P@)qS*0x~NF1?u`wR&HlGDyMKJ%n>TOX=Y9VA-0}2qR*=<{1pp|xx;T1^ z87Ce{T2hQ1nZ#Hz!IHh59YFJt)-nLdbFPkf-_+0*%F8%aHr0;_$wOXz=OR~v!!nSif2(hShtb|#n1o04(vJhE zh+ze)%@~ZGy^2N*n~Ft2QwFCpMM0C9v6O@fv=qc&COms)4~^Do!)~2uNmM-`N0=LL&V%oa`fon{p;7 zfLFx$jAj|YZV(@Cdl*ei&--5P#ymsNf)s^x)@mi0Z&q^DfHmMgYfy$=SiWHE5223=ib4~$I-BKd#m z)Q=Fch%CqcreSJ=Wc^-?9JxlAZ+T{2x)F$gT^M*Z zFb+G9j^R#uaQ#y7MqsFCbW}M$KHl1H)tf`E#w)+xqUh&n8}fH463?Em$}{yZh1|4& zalGG{g}b9hYG;gWyhBH z{fhdZST6<&!8}H}18qWDBAE8dMfjS%T0C`}bpI<*EstI=*vxK5JZU3d_wT-J@)tKB z4hc!CEX7gRsNG$$0%>0+l)~-t2h+SjOxQ*I6*~=KGm>GK@SlEvU+TBWQwY&yh>IS6T>ziUBcHEF9nye8$^sc!6wg^?Y4@3qu-M zu@?e#ufyEZYql$^%*Hf9%?2d1o(3;-qBRWjWp+6EfPLqwZbVv@1)(7;U*!>}O_JGp zV?BDmi7)wjf~Wv`i?H6%b}`aT+MiD@aX2Wv0O;3Bx&xfrR6$L?{4we;MS71}nh?)P zG7!viEhU*tJGbdB_;Z~~;1ipvp^#XZ`z&1-Xp&IoQSbwI$%ga~u$K`>eYNaiL&kZ{ U238VE;ui~Coje?y9m2A}-*9%A?EnA( literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/arm_blade.rsi/inhand-right.png b/Resources/Textures/Goobstation/Changeling/arm_blade.rsi/inhand-right.png new file mode 100644 index 0000000000000000000000000000000000000000..10e69a7ef1b4e8863b14252a91d5a6bad23d9b5d GIT binary patch literal 1443 zcmV;U1zh@xP)Px)TuDShRCr$PoUv;hM-;~Ansf$Z@Kr`|hzmwCq>%%*Fe0t4P+@~P<0@_Z2N)GD zQi!Ct+f>18gHxzDwLt{Au+_K`!iP8zsaB1_otrV=!ZVAIcIKVld$Vhe8VTL*d~bg5 zo7t7-&Gg(Q|LR@x{<{nyJK&NN$V1?g8z4`Dc?jeo5J}*EzwiGk%Vdtx(r0yOG8%dC z8cimw`yv?!FVoNub_C>qIXQ7pyboR*?7Urn zaQ`dnH#%TpVZjXs12=p9x_TT+O>m)h$N?7mjSlGd`)+M*t%`SRYs(A(Ilw}{u~TG2 zxWF1feHdu!2LT`puwqdZrVgmPKvO^1sqftdK>(DArKKgeyu6%jC}`3t(mckD?7l7bX#?@1KOGytGKcQtVp+2 zCp)06nX!s1J3yMW);fM{oLqe|2hhs#)~8QyZ*Nbm-Pn%Lt1lX8eJP!riP(-dT~}X9 zkJT3eK=cvZ`eaho5=e9?wvD>}jme~{C5XK@`od6yw0c~3!FX}efBF2`WJRg2PX!e* z`eFbG5C9C;*B$19&8t_<>xsDf;sEMJ4SydVIx7cI0=W9(01yaOl;GzSwFMk~aR8Wo z-<0LIx`H2vLpPmH#dXe`lc4{sEWgziJP7EE2ZjKCUR-pyu3dAeD=O~Ztzxx$H{j~4 z0QmLx?doy(XgqfN`}^h&fIc3Fe?;hu13sRd zySwYkvNUx7bUFHJ0FZra`_se-0I~Wi0LJ67hxJorzZwDzeQ^LB`k~kBc|Pariv!r$ z*zgAj2j=6RU~%=u0bm{9^2O)0OSt;t0Oql6%6l?^l=R!6ogL7IEToJfJ0K^*AW5^CjNxu!+*#T|HLdqDj132`tnaS$k+h@h4q0e>z_wQ{E;FxQr_1OThlRVW{ zdH}*sa>=dq(Evam0pPzq)*GpztEC?e071~wGnTxt1a7^hUPGS+fFS&P{ko&W6=KG% zq2ZD$8`AxoMuW(R1}?Lynx0bR(FW{lYZnsmF+c6LA)vZNVfc7P_`F0`E; z(1k2%#>jF2RcL4i)YP_yJ_~?kY05khHA-_>08-Qg)In+JvjAXI^6INsNzMfU2o3#c z0JJF?yTVh{3v1~|0iY8hQXPn(sUHP^yCr>|39jiLP5mH%y7f{Vh@h?C2mq4~WhTXO x7#8|L2T0qwZS7?MZOe#tJlO%(#M`F#6@wLet~F3r#Q*>R07*qoM6N<$f&ic5sTKeL literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/arm_blade.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/arm_blade.rsi/meta.json new file mode 100644 index 0000000000..9ef5a5f7d0 --- /dev/null +++ b/Resources/Textures/Goobstation/Changeling/arm_blade.rsi/meta.json @@ -0,0 +1,34 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "taken from tg at https://github.com/tgstation/tgstation/blob/master/icons/obj/changeling_items.dmi and edited by https://github.com/RealFakeSoof", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "inhand-left", + "directions" : 4, + "delays": [ + [ 0.5, 0.5 ], + [ 0.5, 0.5 ], + [ 0.5, 0.5 ], + [ 0.5, 0.5 ] + ] + }, + { + "name": "inhand-right", + "directions" : 4, + "delays": [ + [ 0.5, 0.5 ], + [ 0.5, 0.5 ], + [ 0.5, 0.5 ], + [ 0.5, 0.5 ] + ] + } + ] +} diff --git a/Resources/Textures/Goobstation/Changeling/bone_shard.rsi/icon.png b/Resources/Textures/Goobstation/Changeling/bone_shard.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7661444b00f3d3096f6b3f8a2f0f453e7db67b1f GIT binary patch literal 4334 zcmeHLeNYo;8s8k{ID>F%MLFSQP3;vao82TElI#!xlK>eBA0cDG&KzuZ--NA6HtsGY zaHsaLHzz|Y)H3~`_Byq-qE5k4#+Eux@enQ3`Z0bD)=Jf})y^S$Ugu;^hTgjgpL3nL zVdnZDnc3{V&-;6R@ALeg=Y1!erqbeq1my}Pf*=X@LaP(rQTciz27dp^+3zb zvt#$mx9|N;NNT%Y70BPZ_?4zpOAn^(Pbte<)sy$JxBtL}vn3Tr!+#!BZnxga?EA9& z`LhY1eZ1^zi-~VbZ)_TDsL4#cw77e~7`NeMP3#+7+py)Y<6OaL%pJNiz2}^*4M7wy zaTZId-C~){2NV<}jhjxKXz4$&z4=IIVrL^ds<<_LUy#4yw`ge_AX6#z`$Kq^oKDH^XM}2JURL}6CUR__lT33?#yJcUPGanHLlQTBI zY~0ha;@kN8H#)zja{w7%c@I{)H!OZDNRtAAg4f92hK_rB4+GrkpR`A|5#aiH%N*m-pv?63+) z5ykL+HO=yFpbqr*Vxa4Z#hWPqOV0HX%;=vg;LY6&Ao8{8T!i_jZr0}V76O-2K4(hw0UmZ9=_!B4|-a(>zi@L<3jnUDpi@=EPyl~x^v zMl7X1TJk^#xCgiZ%ZJ2iu#5A9a*3Ap)EKk`q0t-ldc8(R>WtH%4L}fKFUqVMLaies zGjd@lm=2VdmOB*^MC>pd$|3+-;sqDa`^+ji1XT8%ns&eq#nKXOr6mAK2`xzx28z(R z2ot3tD7_XV^b|1z&$FDT{(qt6?Sp1Vi(bfyaQ*s-DY~c1!RF{FI`nao&4i+nZ9&mY zGzF2a18gKt$Q7MpYUqF$!1H6GVJGd}Pc(ypb-Rs($AGzMpv6d7P0XYv^cbx(05_54 zF?c-DW|(3Zd5;vL1(4^3Izp{rgGRJM(441^tNqhT3Rl)x8LvYe|YQGvxn?Qp_f8(7MCNr-wncl0uY6JeSsC( z5&PRhI9L!YtP~L>eu;c3E?nO_290r&-C>Kn8yAzPGz_()YoV#qZq0M0pJS?%hi@RC z_Wqt+D?W;KDc;Xmsu;TFcq^qfHqe#1fX~16&*Aj=rDfXOEayNOc|7H+r`h?aGJTm? wa&fTvg);(hc^`}rx#IVJsq4YQf1i&-;!Y-|T`F6Y1Q$WPx%8A(JzR9HvNmp@AbK@i0!g%A`KEVU3rs$e0hf?61m6p~o_F~m}^7OX`47?v6= zf^ZfwqzV?2Dnuk`DX1udMdU3EO!oHA-6bKp=E7ZW-*0B#% zhiO9{I2M3O96kV0Qa^YBOeU0~nC}v-dYGm1WhZiFya0L|Vr3SU;(N6hdT)JEId@Nu zbcJq9tC1kQ0Gt6fAq5sCm3joB_^~k0KU;>74+a8q7e~r*pyt)~4lU*96+j$u?z${n!nm2L&VBD^z(C~Cg-d#&n^QLBryC>xi7_-x96 tb6d^}PV~dXH%3Q&e>%SnM;)bWy?@yX!Z7qC^Nauh002ovPDHLkV1ic(21Eb= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/anatomic_panacea.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/anatomic_panacea.png new file mode 100644 index 0000000000000000000000000000000000000000..348e6bc318d3befa08a723c7211bfbcc8c221f3b GIT binary patch literal 768 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 Z`~mmYb3YLXD*FHc002ovPDHLkV1foIC941c literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/evolution_menu.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/evolution_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..a68fa0544d05aa6abe6d4d99daf1f021de4877b4 GIT binary patch literal 673 zcmV;S0$%-zP)Px%TS-JgR9HvNSFuV1K@{98Y!pTO0WqWsR;fg6L`Wg|5({g=S`h3~*!Tz5!9tKU zB35Y>D~b367K%0&kvW-j?%m$*Dwt%OOODOXy!YnKzGQC7E1NRDn@s|(`ML3bWai=H zvl4LS1;Iz#P1hJa7Ci7>*V^Wi%Z`R__wu>){-U$$#)s|v-P6r${=_*v&ypw+u;Ba= zo&dywcw!O!W%yPiu+m<1N&@@z9FS2G6#^Qmf`-6v57rA38^`xfN#J*yNojxMk)AA{l?+OS%o0Wk(ChKELZh$idnJIbphK#D zR71&D%v4(vKJ|x6V7n-4L{zkzhl1MHBRI1YGP0-$?G?;&Xua9@b+bTdPKdT4%|!2g zs2vNyjGncwvumFbv82fwV6PJ#T>vsvdd#fsJdt!8;!w3s>vr}khDEDs* z6On?m#`FN6$Os-AMi5w126P_ayl3x#6t|;cDKPaCH6lB3`ewY8nSr9S6Lu{DFvMhR zry`wlu}9}Zd*kT@t>bvoSkA+y`o}~gn#AYX{BJmk$cgaPx%T}ebiR9HvNm%lCqQ543{MkrQRrO*<^Mj;WEL?aR!4MMUM>j}Jo+6yT33M2%L zhDIY%i9{h=2(3b8V+$hWyUE9yGjs3WiQVkpW@hd^bH3j>bN`$vNQYaY!}`*l0At18 z?{;M7q4AjtIMqS%>R^xTy?zO@7qBz$e~6J(2);zkYJh zbPiG6Z|u$n?Eq^bQUHjU4UQo`vwE3ue04M@gy29kyM>)cmrX$EEMx*OjdZ#!Qwe|* zxe*N!V}}eyW#D{s)QM@QOXYzCz-jGG2!R1)2#_H=WQ-8O3;@82mnM>nqJCEpLI8l` zAv)Amt&+p~Z?=$yrT`$-DP>gJIUEzh z!5MLmyQ|6;eFrmO0Vu%u3E~4lKO4l+^qfI(xdAK$Bhxc8>-%%OB>;(Kkn7=`z%( zpM}6qrz2!GbRQtmU~9#r0bpp)1I`B>A+mx7QQQLv<390d03NSd5D@S+o&{NvvsQ@u z1X{oc0Wk~Xi=1@>{6-ze1PEn*03#M9`bFQIAgoQhF3&<`S!O}PI**gl z)_Qt%z$R8asv=@UwkY*Mz+P8JzR2kh_QAtQPe2W!=LPRstm-1wes6wGmSaHx4BCGJ zLg0+hRj^%(jNF#%TG)!?IrGn`qdz9zU8YR~-T80WreHJO{s3OrX+JT8lnVd=002ov JPDHLkV1nMCBp(0( literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/hivemind_access.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/hivemind_access.png new file mode 100644 index 0000000000000000000000000000000000000000..a0405d5f6cb288b9a26003bec99e890a0525a976 GIT binary patch literal 634 zcmV-=0)_pFP)Px%G)Y83R9HvNS1n6KQ53ujT5N*BAXo%JjRu27kPW)U;(xG+(PFX~#o~XkSXRR# zST-1pDhL+AU=VDU6_j}}%k0^6KUP=PTi(}w=ggTqXWo6aFyc`gasKc>0W3_6fBgkB z4n+M%%J;wxGWAI%ZIV^}C0Hw)G*ijI6zJllj zz-nWaBiPvbl#Ai>>aFK~3`7zDA`k>=SBjCmy*wSR&+1`qyHzFJXg!C^`fOOeeGLS` zcRUbT23S4T+)K_fo4a$>Y2)OnTX_kHff*p}sGMWv=q}vsFIAVy2Cgj#zC$q<15nkI zdIZzt46Yy)0J|dA2~iPh+iCI=05KMd7|`A)0~P>9r;>*rQd&??7Y)WJ zLy`e1o5G2?AuSk3ew1wc(FK)FbW9e2m)vSWMMq6o>a}u$$O3>9+76%wa6~(%oDd)| zbZ?-a=AtFApI|0tMWt4(Kp3-_0jqp9ArFAp8CDYmiO}Px%?@2^KR9HvNS3yWrQ51cH43Y~`Gg`<9MQCKJRzu_>5ZkyBYExh?1ZyX>3DbqB z&_;Br;3lCq25s6XLPR31g0Lc`5*l0tqT!+#B#d+G{O;U&|NSp9jBoS*y!YQd=bm@& z{eLp9$|F-{e0MwvT&T+)RzjH%KMu+gE}PKcjaPcz-s&@_uOMdU@SDT#!93@1Z%LF8 z05~2dh*lv)f*?NCG&!zo60rpMurFJZw1DkB<1)4Je6);zvPtlw^~k;fkG{0fo?64+>Kks#TY6LLNK;*$i^(lTp+ z6BO&+&0TJJXGN~JJ}yNh^l@4vmU#dK+Bdgl&*6hhZfk8{NKjfWHNXe%3`OG1&(5SG z5meyI)pG&}a1M~58YI>~_g28Oxs$T?dHSVXsQ~ZwabOODiOg?XXqz88w34y3<3IWAc< zV}N%~Ou5JBugUZEPbS>{&5;r>uoM%kfbuiz-M$XVp5xWXy-51};BaI_nvW!O_PQj4 z1OV2Us}=b%bO&VFkpRF^AuL5GgEQx_C7enD$ls8w6%sxL01l~XMiPxs|2880)=eyR zC*=9F^eDg16mu9n7QM&Uxa|8N(E|vGNBN1Rv&;Xl@-{f`e zTZ>$;TDLuh|03W6w@T0lBx6-b{3N6KY=#6!;JEB5J;ALRgKtA@;%B*T$qW{fo_#?3 z1>m|Qky!AvoIq9mV`AN0>EFlmzhR}b*71J>*A;$0%z%-E00000NkvXXu0mjfyA*l5 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/lesser_form.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/lesser_form.png new file mode 100644 index 0000000000000000000000000000000000000000..5b287d37b71d51b5276aefa496fe7fc8d139dfdc GIT binary patch literal 686 zcmV;f0#W^mP)Px%Xh}ptR9HvNmoZBNK@f#EN}32(mNtq-u+dhq34#_{DOzY@eu1F1zrn)7%ErP* z&`JwMNF&&4BPk@6rIlcsppm!mj+e=1?=BLOYc999cki2-w{x3<8}d;Yvc4Nlfca9f zJ&4S_HCtT)cMU*%%PqNU_w0BE%wlOQ$n?_u_478%lkTSe<>%OE^+5~K0ifcSbHj0QJ2+nDZ~$IDJn5IL2{6K$vG{fnKn_hyu`X6rWh0^}^pz4G#fv zj_Eiv5h?>piUa-tR9^rTQ*ow*a5e+DPe0M5C`~Q^idbQY;=s5Cpcu|-W_l+IP`g@n zht)^N60r7~dI~}S^A(_aw&KAmI}iSh{-G-g5V-&lVe_cw)<0SuhP<0Dy3$D{5u*pT zcV~Q7LI9aEfJl$e8=e{uSkom-L5!(O5dd5rq3Cfhmj^Pr!o9=6A)l_7Hm=m9H^`sopBwOO(dErIp{_ITD4;vv@) zDy*BQd31+lcSD#L+-nc@k4dcQ4C3Bs{x=*%q`ltx2c~*v UKl|sTlK=n!07*qoM6N<$f*f%p^8f$< literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/meta.json new file mode 100644 index 0000000000..850ba214e9 --- /dev/null +++ b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/meta.json @@ -0,0 +1,113 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken and edited from Paradise Station wiki https://paradisestation.org/wiki/index.php/Changeling , sting_transform and sting_armblade retextures made by whateverusername0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "absorb_dna" + }, + { + "name": "anatomic_panacea" + }, + { + "name": "apex_predator" + }, + { + "name": "armblade" + }, + { + "name": "augmented_eyesight" + }, + { + "name": "biodegrade" + }, + { + "name": "bone_shard" + }, + { + "name": "chameleon_skin" + }, + { + "name": "chitinous_armor" + }, + { + "name": "contort_body" + }, + { + "name": "epinephrine_overdose" + }, + { + "name": "evolution_menu" + }, + { + "name": "fleshmend" + }, + { + "name": "hivemind_access" + }, + { + "name": "last_resort" + }, + { + "name": "lesser_form" + }, + { + "name": "organic_shield" + }, + { + "name": "shriek_dissonant" + }, + { + "name": "shriek_resonant" + }, + { + "name": "space_adaptation" + }, + { + "name": "stasis_enter" + }, + { + "name": "stasis_exit" + }, + { + "name": "sting_armblade" + }, + { + "name": "sting_blind" + }, + { + "name": "sting_cryo" + }, + { + "name": "sting_extractdna" + }, + { + "name": "sting_lethargic" + }, + { + "name": "sting_mute" + }, + { + "name": "sting_transform" + }, + { + "name": "strained_muscles" + }, + { + "name": "swap_forms" + }, + { + "name": "tentacle" + }, + { + "name": "transform" + }, + { + "name": "transform_cycle" + } + ] +} diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/organic_shield.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/organic_shield.png new file mode 100644 index 0000000000000000000000000000000000000000..1fc5524e2d65bba2f5f8b95f387117018ded0ea8 GIT binary patch literal 594 zcmV-Y0Px%3`s;mR9HvNSD{V=K@{8*7(_pT1cGP?3P&9Z4>*1T4-z~-fdf1WR~(9v3IYkA zP=P^|%q7!I=XUq@XfExVz3y#y=Dm6E?Yk)_Z*ybThiBe@ej5fi78LyR zXgBOUe+0}J+oM4dudnfRzK{RMjNv2}2&BLXT;04D5tv;b)cZ&zMJepu`%zfVgV)Kq)wb4l@ETM`9@g9@#EK zDkIbz#ROPDH2)cJF)P&zN=&n*a}ZL3yWw$NR5azflIR?S8h|9=R|XF{=%v`D zau4uIZY2=w1>gY;zYF{lQZIP$E#(0ca1yB)h^3zHZIyucfyY723p_Yic+{)K*ReOF zY|FejxeVTbICyIe(4tgF+}44!L;wH) literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/shriek_dissonant.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/shriek_dissonant.png new file mode 100644 index 0000000000000000000000000000000000000000..88e691862460da4889d88cab23aadd6f4f6cdad8 GIT binary patch literal 626 zcmV-&0*(ENP)Px%EJ;K`R9HvNSD{V=K@i;vLI8!Uz(7#NB5N=hFeHTV31CPqUw|bT@EAf!FvVaH zRFI&lB48>IBvn921>`k(mv?i!-R(isj=AgJ?Y%eiW@n}qH)gAh8Q)DNf!gfU&nP0( z{_GVTE-VmyzP{zUt!sy?ns06I>LT~fQFYoi50(o)Y@R$jOCm>L5V!?a(ECm|18c2T zGw`|kQh~(U&TWoFkN|gvfEDzqv7Z6qeR%H!$4gNHHstC8ye6=MDp`ge1ZEa%+4is3 zcK<$!KuBkR`+zA2wI}%dc{3*g)}sWl!$yIgTW|t1D%2l9AFUUMq=VgEu4hD|1Y+Qf zj!zCe1DJvnnIw`3SP*(1S_cRSK>`2)1a`A9oXy_gvXC1&j0ddd5G&BL8{}ciW&N=`LQ283EPz+t5bq=IUW zV2_=Qv=ZV}ph`x2K}sR=5#$=Kb}qnL@<39y7uZ>W$;ZUxh!%~&U%H{~0nneu<2aB2 zUe?Lj*8Gq>8h#~lw!NE;_B^Ho$8v5S>mL&v#!>&C%>Ra?_FBLH3*=|sKWYNl+W-In M07*qoM6N<$f@sYKt^fc4 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/shriek_resonant.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/shriek_resonant.png new file mode 100644 index 0000000000000000000000000000000000000000..1f75903bb4057aa1ba7e8008d1c413d63b54c418 GIT binary patch literal 605 zcmV-j0;2tiP)Px%7fD1xR9HvNm#t3&K@i2Kq6R@iKw@bcBnlD?7z7wV;_&BE|XRj@3c(fYUm)QhZookQkaOU-Q7z7+l z#KaF}LoVe>@TrHLJLt~KQU6XBHur-Vz8|l}LR0{(xE8fS9*h-V>TCxP@4b@>L=?b_ z*b~(X8o(3B+9CjLGevXud(kg;w#Iy4gNOg`MF3bB%Q<&Ut`>btKH_GsnN|S=mQ=A1unWf0fZ;+Q9``v6j%TX za8nI2srbYWSaA!H>ig^ke1<*2-UmSn7@-$AE}0mFwjD>#c0ojskp;jaK$M7SSUiVk zc-|?9dAKnVfR4r%p684eXCJ^ku4P_KMKxe6I92&{ivT)3{LXg+x{ytQw%=AfHf1l+ zPGFrLc8zwSs1suIlvoTx&wCC`)d{_$=s81RyPidfQ-Lm-bpRW&8O8cQF84a`JdVXL zBq?!QAKp;~oY$au60!~eAa>M{<6KI3h1+#3#3TUdpV#(AvB-3E-rWFk4hEu&5#N)q ri&B+e;d{vwch^^v@rEBPx%P)S5VR9HvNm(44LQ543n#cb?+EG#G~l1r`?Rp}fr*y1oveRW0-Xukh`|6++| zQZ*HYl>kkM6*>VG46^{`L-5j$dXv3BT0 zdjU9za1}wX5Za7MARI!AZ|gZ904V5b2msU#k6=PT1?+QaRtf}wIQhK_s3R8wzd;o) zl~%z}HUJ<2tv%0qZvhOo6jTDu#FrA-5I{k>&+@7C1p(05b*>QXZUXWQZQSMy28haD zOM?Kqpi11Hmf+h>h+wY#aeJ-`?hFWO!X()l*i}IH*(a(>&>(^nycsKiI!TfmXWhVR wPk_YNa%NT3KPI80_NVOr=YPYW$}YsuCoo_7Fl?&RApigX07*qoM6N<$f;V_6vj6}9 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_enter.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_enter.png new file mode 100644 index 0000000000000000000000000000000000000000..a4161f355b593986115d8e2f6c7e92a2c56c2c4b GIT binary patch literal 747 zcmVPx%rAb6VR9HvNm%mF}Q546Ih>L=#WGe)eE{zBh2sjiWD1u{4waLAe2Ht914LTgz8c%2;G8;pi3#1@3r5@$;r*T&j+G+dCARt_xm~D@44^g zb;u)+jz`v)rwNej?z(*lXO3_Fv=rPj@lfI{$U-y$gv94Eoo=1A#$Ul_9<^KcH+;84 zL$n3Z0$ONzq+fp2FGL?HfcbVXdOir?6Ney1Zlq6+>z7T($lm&8q~n9}#n9qFNF0k~ z-p_b@w6VVD<^hPIonmzEch!tu0KK}doWf&;PhNoU)g?C%5DQVNG@`yq6af~}Iggj$ zNjqUn0}u<5$^if%bJ`Ao^6HAWdQO(+7lZ&d4nhPY&|Uxf?V$9$9FpRjuTK0~HX94@ z`|M0C#Lm`fH&KqI@&JUeGu${a`CRgu>DXyEVM4LS_okfjBo&p&mW^n;KP2Y+T?5!1fb+d?MxnpwX+UsIq8F32i>JNrX}%k# dttQQN{{Zn>azEX!ZWI6j002ovPDHLkV1o1SQ7ixe literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_exit.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_exit.png new file mode 100644 index 0000000000000000000000000000000000000000..1ad4b4fd98068b060302da08759a15e7336ed300 GIT binary patch literal 767 zcmVPx%xk*GpR9HvNmp^DzQ542+zzjN6kPd1!pjb6dAy5$M(143bP!I=kb8&757iSj* zJG;1vLn$bwbSZQZ5rojm`i~Z((M1G1=#WC?cgy!Ux$l2O(!1REUhcc+cfRwT_wp)k z#G^9e_-=m!TsTzS{U6G_``wQv9J`Pe{Nwq$=Q{g-!*M)$3D)o*sT$%GIC z05&)Q!p+#|z_GsD`Cj$Guirf9_kOYPAqHYW*(VJm5g;izff=VRjrp12FB{MO@wN3^ z5yaN(ZGR0&J1m4%AqG$;IDr{}q(MkADZ-o!V99Ofk~|D3H3URStP<>TdhN6K4N7DJ z5L{*xJ@f4RRHEd9O)5!P4wa#>0t60gt}Lzjq)c8pREAstxt|+w`^3e=?pu4!J$(Aa z-Me!)W;~m{)>u!_rc$$13DoBbz~&RRn!A1ZiZ`7d_~O~>itBZ|9#R05!FEJp0D?n4 z^XO=lJ;BG0jQhCl=F|YdQs=Ed-~Rby0AhZ2?>Z2LJz$@7N!cs1f?61>!tte+>%IBq zJEC+KNPg9yjgfx&#NGqu>es%r0QS1phGi(MfPLHwfM8@=1VC%80`ftw2R2)0e1^0i z!I3ajf!{G?z0ve*H`Y5*cPM)W*B&xsE`Z*0+%P?N(!F~8#m}@MnaN}r(hl4&lcE#= zx>r76JzmIt4(I_CD!>MU;22LZ$eKZi$dDEQ5EPXeGXW@p7HgIQq5F}Zp;Fjxuq6OM zWvx8uWGk?HzZtzq!V*XU0;vN8rE8%ANV(n&dBMX#S?8e&g%lVW0GaU57$b(QS0?MU xQiR#FF7eA~q#q{#lIbw_?$2+-VKN!^p1<4Ks6R!a8~6YK002ovPDHLkV1n2FVUqv= literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_armblade.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_armblade.png new file mode 100644 index 0000000000000000000000000000000000000000..5eca7950874b8e99fac77640059f2fc85be24019 GIT binary patch literal 672 zcmV;R0$=@!P)Px%T1iAfR9HvNSFtWcK@^>hC@2(IYKh3(t+X1gMnx1T{K5W#Li~Y0P#_{y8m&gF z+H6FkmZecph>$bM*_?NG-pr7d-D&o{c{6j*z4zR6XKQZAqc&vwZZrWjC&$14BQovx z&pwCyCSZJTcFY~Wd^!Ab;GJXBt@y`g{lm4p6YkR8x$D-VTR(bq{D$wC$H2FX(x!viG~k&)Rb*_^7`?cBrxPy<+c8UofkjABq5R5t(ugPLGV zHwnNRrkGek!MlRB&y0rUQZ@iqKuAc`ScxR&#gY@8NCL2>WOHxBPn%1t+y3$4_N)g( zdBJm&B&=dTHmatFJWqk(o@f=MBEa4d0&r5Sxr4dsQY6^Wl$=ojBq7<06wrGZ=YiTC z6%0ss-e~w#RCU!P03Jwfzv{gC)@qHzV+;^W_Yi;r)4fR{0@1XLz+Qr|-m2PBZWTc2 zPLb6Osc<$5mWZr#00S6sfR&&r;04ECN}~4xDjJN0^dP1I{Pw2^aI>bt0#GTgQblW= z5y?)0b~xJy2-4mi2~0_6l`~ldR>5+N$AH+9O-2A4sN>p%H0PmCsB;=2%auw1Sg1E+ zfWV;shr+Jxu426ch+fONaHxMwa!PBEYew_G;UGOso%I8X$!I^#3_tJy0000 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_blind.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_blind.png new file mode 100644 index 0000000000000000000000000000000000000000..4d3e48c7bca814c34f65e33619e1314ea7708396 GIT binary patch literal 606 zcmV-k0-^nhP)Px%7)eAyR9HvNmpw`ZK@f#|0uuv4@B)e&7%rHIiRK1g!Q2xVdjfN>U|_C^m5`#}I~92)nUajhV)OhW+r^zsrf0^lX8KHRDyPyl_VYvLjHx6`LQbu!t{ zSH1TD=zX&aN&vPq4h6vfIR+~LkG(gcQk<;<_N^)epk+n*v_qDR>;LqrIw%KtxHcU< zEK-I%!@gOC%22(t?*>?l0(i!oNoe1M^&W;XYzOq35$2$vjeivYi{3XuH1?&gSgHW* scfX7l`e9Pb?lIPu^V@I?PfKV10h$zPKZuyNAOHXW07*qoM6N<$g7=39IsgCw literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_cryo.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_cryo.png new file mode 100644 index 0000000000000000000000000000000000000000..b935074af093df7f0c17fe5076954f1184b175a2 GIT binary patch literal 654 zcmV;90&)F`P)Px%MoC0LR9HvNS1n6KK@^=uEmjw_3ThCe!C(=L)-4vnC}{R4m=%nI#j@2XSPTZE z7*wz-tkt5ToP{IjoxShPY*^M?-q*~%=iWK@%)C|_v$e(?KmAVt%hOXoqo~Zo$LB!8 z11}JKr#+F*d*2CH8lGv3yC?nInWc^N{&=09&i2#g!9vqu=TVA>~{o(Q5#e@00Kr$@Yc-&@W2!k z7Zf}zSifgAoJ++3*a4A{XfOa-zk5w^%&YRlE0G1@t>kO3<=24Gz4C&7vn0g8i2azT zn$9ded#{3%Gb=>73UF`mj0p%O)@9*_rsQlCB9f5o#S$=k1gArE06=J9f3DkAvjFIn z+WvOh?Qg9fT(hmK0N?A}tQ3Q2+D1?`f~ZJi&;bz#IET7Xv>Q_4yb4Ohk}lxEMz2`_ zK&+$zh)56WIzg)xovMle5Cn)90Z=KfQ$-I>Qf2`ld*uOI)As>gR}Dm9N;+50Y!|qK zl?3-gtOC&kZB~egmWK@8gmi7AolxI7^L1`*1u#fY#@zR*|54c^ma70YWeN!3D0000Px$mkFtbDtSbROk@xUUWyGH~{8zm;>hDm0^f7mt)`%6K3EP69QZE{na~$AFtjr zEIav#;d}lBBjAvtdp=8@#*#IRFGeagPo_(EtLlL`k#*2o*5M zq0n)C8rTw$Wyp>|=2vKDfz=T008&EWf3*?V@NK!x7!L57Tnmgu7Jfo zOpGRJ0A@HWEg(Ap7WYIu0Hl!AasX~2G%cXS12pbQasaX;h%E`h7C_SiiUXi=PqG7G zjzCXUq&7m(^%G13Fb%MR2Hk*ZK6}Y(h>#Qlgqmf9(g;RgL=qqv0;5f`0Sf_8G!1C8 t9F|T7v}1xV_0mFaaP|#p;Zs~@0swts0YBj+rqloc002ovPDHLkV1ls8+R^|3 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_lethargic.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/sting_lethargic.png new file mode 100644 index 0000000000000000000000000000000000000000..53283dcac913fda6ef17dec55fd06e02076e57fb GIT binary patch literal 647 zcmV;20(kw2P)Px%K}keGR9HvNSD{YCKor~yV9s(0SP~Rq`2YkCdkMiHFc=Vg2m0+}X5rgN{|MO^OIwB5El^WM&z*LKp3M`yS$RY9g@<7bi54CE%sxd++IIgb)|c=?kh0uA3EMllt+Z zdb$P+VBF`>YZqtMh;kWVZ%{BsjH8obLsN0KmLWw*^1k6{&aCECF2* z!g+ijR;@JxZ_|KolfIiZA_z^(NJ&Jh_q0dA>KjGfkPgQV$1J!IbKv~2(PNeX*s5p% z68Ql|ry}$WMPF4#1V{qHiwI~+n_SVsX<%6bh+cJo()2#SNd4Q9#8hZ7Yk82as{f#=I&S{2R7mWn)pne&H1P1*-N_(VoZO?7dW4-XtXr@0VkPx%8%ab#R9HvNSF270K@gl17z7Ck8V9)$et-lF2F=6a@cac9g~#D|Xbg&kA0Xrc z4oyOWz#t&CNnMKCo}0PAW^c^y&CX6$S6BDV4pPr!&@+CTO#*B4bKhMk^Yr=E3b-{v z@WsvDhQs^)O~O5|B(}7=-Q2#MACI~2r;W5XTp0hp9(|;%^pIAL&eGfSU9*Ri_(ed$ zu^59yR1ts^_+q%z5^MQjy_tKxd`wD$?#Yl zTtOkT4o}5Y1fqOnbAn@Y34#~3kX0bcIb=sP7zEXI$gvV|6VlWZJ>aHO*(oJ9 zf~#2x8a?je0j9{S0MDxlzIG{3)WUC;I*%^vno6VUjOO?HEWT8HX=ks?{A%))+ta!fc-$tsY-C(tK(l4aAlM@+qSML zdI0$^MXxEqed(;AL^>f5kzlFfs)rW*E~cPH0armgUG#s#=fy@;Q-a=<+y|Z#eO_$E zlL)Z>9WWQY64A3|RU?Yhpl1O6Z*UJduy)ozDG+tTocui0Mrvghz+#+?OQqPx%fk{L`R9HvNS0PUWF&OPaAYsYOLV_eqhFPXia|FZ`j)1`6C(wjnfJWdk2*?~k zARs8FPz#17Xc98BWCB7)UU#p2_g#BE#4>8`uGi~(ec$`O_DW{Xtu$wR^FIkR%8Qd( zDDyJ@$}Bk30r10>1#|K7ZE$77Guy*e+ulxA>!$QRFkAa~=H{qwcFqQ-b=r61ddE$B zk9XIXZ+1>iB1J%h?;X_aq-X#IV7a`M5@$UAw9ng{=Tq=?rQNGBAQB?sC4eaiPGIzD zaP*}Jbo-`SsW=A1(P(-vNMJ!y1uucWf;$Bi0rYDU=*uO*SQcG5J%EWB0;z(JL?HtA zj~%OCDCPi0%NQ{6K!`+dw^>|)T??Td@7AAV4 zH~|(EPa%8zV0rOsNLVD$L6Vf5j&X?Z?31E;s8G{5Gw_iD_Phwekr0YZA t==c5^&GpB`S7>J0_dov)XSrfM_YbKSaX)h{=WqZ3002ovPDHLkV1jhlHIM)R literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/strained_muscles.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/strained_muscles.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc4ca3177f618072ae2b3107ea5890b51b27d8f GIT binary patch literal 676 zcmV;V0$crwP)Px%UP(kjR9HvNmp?B=K@`RBMl^)V7a$7JDJ2?_(1?PNprBUxFbXvV5<(#wn}|lD z)QLj)0xCiyvT~lBm$P^0&CJHin&!Qoow?_C|ID9Pvo5zC-w=l#g+PBY0$vxT!F#lEJRS?k#cmVZOA*#f&Vo-!C?$Zzj zz%tUp^8hrAXfQHd{}hiwB#Tf1DDHC%2<{6MM;|w<(tWe0XrO<7=P|tsH20w*QvrR~ zU6TC(L}OaA0n~dK&_eS7v0V~dG#Vq-ae%6N@sM1i7@ypTSYXIK8zO)PL)v`0_#f+r zPo4W~fP=BQ%1wzi<0HTnqCXqJ8`CHN0*$+)-x)&yj6xOw2J1ojm>NxiKf?7tJqx@z#s$BOy82<#PL+3J?Em~W?1X2zHogH{91lN*xze5h0000< KMNUMnLSTZ~dL`Qc literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/swap_forms.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/swap_forms.png new file mode 100644 index 0000000000000000000000000000000000000000..7256f103ae454d0638257f75fc8a9024879f96df GIT binary patch literal 778 zcmV+l1NHogP)Px%#7RU!R9HvNm%mF}Q545-h74^XE`=7wLc~GDNf2zXLzZ??MBAV#(w{?}6mrxqao+&vck*`b2_c6i~(gyLH09sSH2@zrfR z3y}bzI9>z<0noEOdNJb1zt;B?8W7j-;-NMGV+)Z2z(*l*3O`v|_M)4MZ)L1OULO8T zKz$y1CUe5!x%NWP1J-hgQ+Q{0-V2P6t;!f6gs=d786Z&}(DBE|6sPd{rwv!D)@9$Z zk2BH(L_<(i!?XmzIBN0B+JXDGbu8i##{5ok*!Qsz(IIFi4lmq)HR;7U&w_v#q5$R> zOA>Rqoty8@eHY6yN4qyMknlhV0%8=!H!TBHzC0BX#=Tg|i!egLMo(Z3W9@~|jcCaO z8?OhwrL+*jh(8)ZdJ5AX2%h7CywM@#82|*@yfE>m--~l@A);;pLSRoY4<*F8d1c{DEXN$J zSSlnu5Q4}AXygF`T)i6=5yn0Lpf17)1sgqqHH@_vS^^*pdIB7m2fd}V5WduglB=@V>F+#2!Km zfw|5NOiCI=TaNdmt|7~@W|fW24ZNc0ZCYlf#M&^kCbSh%D4&NaWkTs}xw=$M6thKXt3bEC2ui07*qo IM6N<$f~%ZdI{*Lx literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/tentacle.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/tentacle.png new file mode 100644 index 0000000000000000000000000000000000000000..f09b036552c5d38feecb0844692810256363f7b3 GIT binary patch literal 691 zcmV;k0!;mhP)Px%ZAnByR9HvNm$6C%K@f&Fg^fiXK~OA$SlJn{u(DS>UqY}nWh@0>!cOh2EQHuu ziKGw(AHl-LBJv-6@y}#)n+PG<=8n6)oo{CTo!M)o1&_vp?Wg4g*jil~%p)?d-#=px zk3GQn`~GIycz8>L^PP&Naq(PaV(v%pYdBYHu$CT1h!KFCaSR`&-Ql8#t!BFG{~6)M zZF;J11tN&`cC)$)LIV(o7z5BG4Sg)*7?x4S+VOV!ym?6{{rlmw>(;>#N+3YQ2*fC4 z0o=F@M?+df5PRLru@nJP2T>aVk>feW3EgAotnygiYX}TaW7Gi9^O%mQ0OW}bxbdrQ zZ$t+~fxIJDkAlBL548aLKzTpyw5t$n#08n72nj-ix&BV~603_-(ft29T)$@J^m-F&CBR8;=3wJL!bo!aJfdl?L#@)h@5S*l4$qcZ|5_?3O6pnE-st z5s4@)K!Azim!5!7DvO0E)&YfXQa)q(jo%_fAe7RX0JH$5SBp>sqOf9WqDXyyQH0w2 zc;X^+fe{s7I0q58WibW%007@|Ikm^soPMG=5z#8#t>=G0Dlyg9{A=vx*M7d1c! z%R$7bs7kU^RH|dWV7{lc2%wJ~f_wUVPU*4Oc#~p18r@VPtb_1w=_S~ANNmSwOU@Jl zn2c9`tvf5IT?>MeJLynT1fT``HHwL|0$UG}3L>WkKN%GPJX{We_nZ|SMy7NitPx%NJ&INR9HvNSFuV%F%(S)2Ni{iKhPow9XpA1DXv{y+#KCSa1tF9(M@r7>o0V2 zaS`3RCOLEUWIrk(js0p{igyXB}1W=iq8Tu3kWW=w=2OZLI#&*NAW!z@9G7c z)kn3w-O%qZ&6d#``dk2VZvjC_>JfakaiW8v{66n``hC7uHUQA)0+DMALgIUEf0$sG z8`t_B8MDonDyC2R!k;i;*ED|HW$4oZD$g2rN*HA^MXvAh)4d+tj6K zA4mv}5^P*`&56i)Qh-qa>=V&Gke*wILwaEv7YTY%bOZL|`E>)SYe oQGZOv36L^(I{zD{WRg1P7ylMEKYErIlmGw#07*qoM6N<$g8o4kX8-^I literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/transform_cycle.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/transform_cycle.png new file mode 100644 index 0000000000000000000000000000000000000000..fab4a3dcb09817e32896f05fbfedb214fc59b723 GIT binary patch literal 768 zcmV+b1ONPqP)Px%xk*GpR9HvNmp^C}K^Vp73JaA4BdKg8M?eUIaFvD>20@!Ew6PFOrG;G(1Vyya zh=QPEW06z|SXf!4QIJ?jVigc2AcPC{LByaI7Lnh;8@`*J-JJ`O%QQPPJ2UTn@B1^m z4cF(^=rg|Cn*_!V?A!hy%B=nVTQImVA@I)RsB12*JDwxsgPzaE@=xDyJXlTg+rHfj zYqVZx@TA2gx(MWepBk926lY~?swWZ!0@iXNVRapF%t}P!d6cj!Bo$y2L})t!7pNs+ zU6D33HOYhS)OALdeVP#FVt;GAp;F#fZ#=5BSKbP<6> zwgQs=(z(}_mZ!kAAw}Zj&Ew&`N*AOEuo*Rwe&yb@KWL%#*$rQA`~K#_VmO>R2uxml z+vPr~5T|0&1!d9$eyn}pgFS3scfUS#eE;A?GZ0{I=15UJEx?#s1+4tmdJg%%x)s2l zotg7}ELPh;GUmIffRr0*vp@=vMpQk?UkHjs5SGP3b_{R;fJz^g-k1bRC>?M7xawrN z6d?vE%@i=5m7RvNV(Haccly(&AEuOm1hG&CuDgThw`)xwm{~!{BEZ!kks=V+fY^G* z0;iu%z=Df}O)I3A^)~nvjFR7iXA?+)TM|PP7knzp|55>PQn3Ae-TOWU&nCb^iV;4u zF0gU%b@5JDJ>VVS)B{=1-1`&5;S9;YqZnY5jRVGzpivQNDn=q;!Fhl5MaSOsc@GJsy12rJFgm;e*%?4@JAr?~LC`UZddQ!lC`&9*Ifr*)r z-xZW1QF|BI>c=Kzr`q+>C{C%gto|hROCZ-d zGxyA#nR{K&&M!xv+`epk^2hOVdX*7GXOjjg9CBFkh!4Je3&l%tCi>5BzqQDUf9xNOxDS z>J+;&{hzefBw&kxv`u*T&=6EsW)f?*1h4~x#Q|^B=dHQYB*4;n)1tjI@SXuET2x@? zb~SD+FYC3<<;KwWXAL0ujjq;B5{Dxqjpb85@1sv0yQPz+^z#jYP zZAMH1%w`c!ZU(FDzQ;F#pSQnzT=XZ58jQU=r}hB7-(LTq{NWMIRc94|E};PB0G_9R zdt9F15SA07D6XY|+vx zqRg7m^K!D9i{zhecr>)Jtd_$zWEm1MeYKOSh?16B6A}=#wg`%2VuitVks2G;e`Mib zp=`T%^uXu`XQA@r*WkUB)0v;B3F&{ssK9uOcQ%&aX#1RCXyWs2mA;}NjUdB@j|}n5 zw=Agu26h;_!P4ySd?@RMML&Hzs{vU6c;J5$^k16C0a*Yr`2QF<)t&?1MZH_V^?+Ic O0000Y`D}50*-s4V1!d+6v8flOULa5O#Abfn4XC zxo7T~yZ5Fd+Al0KbLY&Q?|gIS-jg;`c&WE5%&q6>)N5zxa^-W6o+8@h;Kv(R>7(+; zA^J~?h(jQs@6aRwCbIVDA2fM=;=d%L_f@LX%7eNW6Yydml1P#aBS|XFM%c1LJsuc3 zP4)Rj-PZ)b1bN_Xx`Bd7@OZ-^!_SirlQt3qL#HV6qucgoJlB@Cz#~B<45>X|ppBqm z+cZ*8uzhC-Z9HyLerG!cL6ho_YBV|hJ=K;T((IiZI`ZoiQ`FVdO?U4I1+pNCbanQa zPN6f?{!wF16Ltd7*rK-%AE$|x$(A+S64(uAk;7|?GtOLboM_=f+yZYLdOIelS}3sR z1v9QKE!nm8rP}rc*zsB;PAE8PjP?sFpI+YIFIv*}h0pwiok34uA4L~HgY&^5(0F#8 zx(j_aS{WUwcDU-(rIHyA7uB3{X6F5by>It?{aA^9y;UX3d9*VBn|{XUZwcTApzYq# zr=vhbgI9c-JKL}Nl42Y!`iPY0VXuRT=A{xK5&6-H2qhu~LaP9r2cOQ+y(r>)B{yP3 zXZpUKFOK_GAv0F(a-;^uea07;$EjX>NYj3NBicB6&0aCarp$cqtRboK%9Z#C5WPM31oLEM5`)5axaul|9J#a{=LTJf*B$8Xv3+|4%(gK~ zjtW`NI7dGGy-d|tE7aUqR(n+R9L}HfhjRganUIj*As{1^?5(^B8;*W()mJUqme*61 zlu{Mo{JC@X3cmaPX+z+}U3;`l#Ug3uSO7`+>@@*#zVe13PoTn_QUPP0il2M-@g z{~R-}-Ja2<7l;5LbL;9nJqahCD_eWuK0SzLS}P(hQXK+exFt%4mMnL_? z2>vp8)3v*shEATRiJ!h9@0py<{Fa)K|2GWtB5w80M)?gKNH_!3=i4cLRY4k31_oX^ zE;HXjs{(MGh%o6a4NtP+tXBws{%*<=atKJr|HSFPgeM6(1mOICbexL60SzOxZF+LU Q+5i9m07*qoM6N<$f|(LnFaQ7m literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/10.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/10.png new file mode 100644 index 0000000000000000000000000000000000000000..17316fd6c35af63488e70436c865ca6b7a79a9bd GIT binary patch literal 1389 zcmV-z1(N!SP)svGVl~?DhRn>JGw1uxxidQlFF20YhIG%i2j|-^;nu)646hJgb@0nrKR&t)J~JjFzuP;AP)#)+&L|nk1yx9>tj6dB%W~Rp z#x{gtM*sq8ynpf(dRO`~)*L9Hl*mlN(b-|gTuV1FdBbiAXBs}R1@e|MP+nq;BTGxx z+UQc`U;&bNBW71d9>_rgfyqXCTVe*KLNSQD*pj#Ora_!4u%$X6-wK^lF=!|~Y zns4b2TNyIOynSS519o5H+gsg;MwT%YwoimtMlaNJ#pF~fzjN6TF*ylGv=S{XVJqUy zv6?MIjNFhP^1q&y+x91*=&kSWQtpmS#aYF+Yr>f!+9Nj~*?R*w>~&SOHCEsH1jjpS z(e~LpXuH;o>(`8TMTN!os_V##_g-Z06PmWRMLYKW6m~iuI5s4u-ywkDFYNa{&E(>JkJxPlGs ziSR3N^~x3N2!8a*1w+6aua{#T%XnGL>wj=benj*I{8KRH(o5^E3}`!h(9>yXh>J)i zWb_0XZ56A-f<9x7-FCE|{0M*Cuf(y|dhth}U}#{_5KzW5FuP+I#GmcLTj0fWwuK-& z%-!b+cPbHqQm+b9}fxw&Ix3FY7B}g$(`6eTj@Qtd@6aGv-#%gpGi}#|!pBq_^p_PUH0W@D~ z6pIO~{HY98S_@)%+c?Zhc-%=bew>aVma<8Rm>|ig`okjqgq+7vW zsKloJ1c|&DQhP?y_Ph!Bk^!u(WfBvJ-1z;6ASDrWCzxQ4QxM!Q7L^W_=TvG<^^c49 zBj4Q@S()O$a#B$7ks#9i9K|-`R+=TLgxq-Uim3i%L-y-fVsv^~lwKr+po%#N9Yq%{ zdm#Xe=1gWqB#Tss09{WjC4()5YQc^sr}Kk^%i58Uts=@<6EbETFkL)O(5I1(MYXKO zkqZ=v-8VcL6;UQ~)`U_BAkkXl)k(m)sX}3Z=X_kz5|&VAYxa9ZnA9nI~%#{IrQIt v9JB;EFC&HcWk~-em@3F2fXM$x;;i@!8tJ{joTrXZ00000NkvXXu0mjfX^)s| literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/11.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/11.png new file mode 100644 index 0000000000000000000000000000000000000000..d5060df98bbac9f9fa1b14a12cd8617565f5aa0a GIT binary patch literal 1387 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 DIi8Q& literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/13.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/13.png new file mode 100644 index 0000000000000000000000000000000000000000..2df6df47dd92c6e461dbc2efa66c53048b423dde GIT binary patch literal 1401 zcmV-<1%~>GP)LP;95G_|C`m)O>% z5@VBw_|=&9lmEbv{sZHOn);a#KQsi1;tN$m+7i$Z;zET4mJOu}n_^*KSXg$PGk53i z+`GG2jrJj%nYnXjp68r9XYRp-zzgjmZfqO4c(xxmM?YnFiZJ!Tw^Jkd@bX8T`cF%U zLxA63B}EXFkjwZI!Z#)yD5hl_1sP9w$af$a1nVP*#?q@IkVIP22BX(2>63Yr4x2rN)$yf z;HyGvHwnKlfOI;ESUig1@vjkGS;dd@Q_}N0Lt_ZFwc`G~7J+img@o$ctWL1LtbIJO zDFr(mkjUZnlV>orHe9skPyuyBmM5HCxb2ubeFOOUdZ8tp?|h>mP_z_*hFWVJU0Jc$ z##W+-3sA(HiNb**qQoa%npFfRn!B_X(mwcc#ARopEgVKZ0VLFUa0p0LY@;<8w!PK8 zgJ`eKMDTf!HN19O%n34UKH{>E*n6Jq?!n#J8Ngszn_iO7)cdyuPzF%j>J3XT$fMV8 zMex&mU81i`EO<+O@>I=}y>uc!uSbGJdp zgENdxq3h^fDvb61dTia=$6nKqWnT>28#~}5X)I1G%h8n5IQW&3;ffe_q;$)S6;tvZlzir?VcE!j79 z@6W#f3SR1K$1YEy$qb>MHNwXNf+QzMjr;X+O>uCL!CywmVteiV8gy;v7!e1o7U+o1m z4ERyyAT?1TLcec&*}+a>ng51%b^`bLoEDu5>>*=|p?wlYLhh?@^~x1H1>bt-k|p4o zV-48ADxMUJ{8!+0W(lYl5m?x<43dBD!t|MV%(f6@N5u9Xxt;qQQ0R3*ij=Z~ zUc+O)h4ev6?nj_QNFS!6G^ELI7KZV5=ew8~9ECSfDbw)LK^&FLD{s%k(vZd~Gf2#8 zJ`HI-WzfvT9Hs_;kBA*?J*mpqL zG*nU0MW|@d&8X1W*n~L$11V1e2N^4988ZcMHh_l@i-`$DZr%Toq$GmA33)I_76kWe zWTivpIh9(oBh&Kv#25EuRA$8oNC~QWHi(?aqr_&?4znUvPzTRl5p|sGEd4oFn4G&U zOD_^a(8U~vzUs@iy%Yd-{T5e5sz`MR;5-NY%cO)*E!cO;x%pwjRqaS9RS}h~2^q5z zjLSbK=+&r=Wwo46q8!MPLf_iSs)&kFwkA|U2uVRoW91c*+eK;~RR1ZYGilj|>RK^y z;VOo{`x4A!*eAcJCRG0$a=+%cxU;GJCbYQ72%>Crl)j=M z5|f%rd@?bnKKUQ`=zm~*sHx9{_+SVUMGZBCv?ZV+#DxkGmX%V4O|h^)SXgGf=gyv) zJF~l3jrJpO=gypazwey6=gh$mfoD1*roOG?;<Iuc%TsiY*h$dGEj_B&HYS zwG==~=p1PKZbwN-b~ob^!}q;}UKETjzBR|1RCqCd3VQOsj!p*v_y$AG7y7f4T>^XJ-Ii!`k$cyvOg~7C;$5V_P7KjfPdc z{a!z68*9*UK7zLv9f8e}X7uhl^WNvgpLeb6^~Aay*?#$U0^1uqm>r2p5d|K35GfLc z2$*&RnEZ5qw@prjPx+s=30w52Q0yxTZypEut>agF{(JtOErRwVEKV%r(;L^3h_7Ni z=46DQy=M^lg@G64HIujN$6aK)a>@2! z{keBuz_WcF*fld~_D9g5HK9ri2@*q)>V*aF25w`kw2Q3&yEWr>Vc&27YPW~1fc0U zf~a>6E|p3dTprECCH70t-8qLHf^K_^bSQsBIyv9TD66lmj{9Tw6O?9Z@mL_Jk~ikQSsgXRnCdZc+=N{*RH(W#t&DZ^OWAS26V67tnmV zee#QXLjK>-3@Uz$noaFDq18i15N(^Q^%V`Ni8APY{;YcPT_}zK5qmuJl9j^W+3=|s zFu(t5%ogPS7%9Y$Li#VEse)Vri2Q#fE{VSYo`0yNqb+b800000NkvXXu0mjf6;qad literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/15.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/15.png new file mode 100644 index 0000000000000000000000000000000000000000..36ca36b9fa49dc99137d2b7692c8425529bfe26a GIT binary patch literal 1396 zcmV-)1&jKLP)I{b!NRiRoHKi8 z?#%9DHQJAC?wy%)&-dLk_nbLsF!6juH`E;!S1t_V*7&Cio*^{(;@g=qynpROgXYgl zh)X~qP|G3+Lde$kCPwd!{Fh?buNif$uO<{nfZ}k-LcC%GyrRn4NVbBIult9tA~Czf za~42K=o}~q?v0X=>|VwtM%?!jdTnI%4_!f#hsR*rd8U!T%Wjug;HWJSX1rQ5D&uiONF`oI|pmwGNc0?m;k(A;2M$73;j zZ6X#wT7V$lN;wCLh*F>QN>&Rv-QFvWVEfR=3m!WQ;b;`a1Yn5s;1ZCk+CgV1ip{1q zzV3Uk7xm3`#iuVK@`El}4+58+arJJ(w%hq=7jt6^=n6-0vF{x2&fN!81?#gbY$4vi zEr2qB#!kOQ-Zy9wM=n}~t$Hhux)#MD@;vu>g4e45#^Xj}gGXAwv$@N25>e#ggGiAm zM!>MWlp`nNQ(Bu%a#C$GX?-Q(&6~3JwLPAninQVP{6n5oRLCD;d1@7(-MWcHd<~Ne zPDW^A_X0$IY3NnUS&=-fcDcw@>691D-H<3@6DurR>vSt}(LFXM%Q2=vPIm2Tl-KY3a@fcx*k3NQ&k+#l zh=lEVNfy)|uR*{U#4+Urs^RC|sxF$!u;U`#@6oO9TY!{7_UC z0o1xkHB_0QKd^V%t(?WG@hc+AX*@9IrRY>)A30kL`AMlM_A7AX`gJ=6-+23~CE&Re z&Dg{mo)*i-FXkQth#M>Tr%s~VF_q5Bd}zpEYg4OL95a5MA=42IcB=|u{-ZMpwP>L1Sw?& zy@n^oHnN8q_D7&YNFSz>G^B^$EREu=o_8=cJPv=bn$z&{VG`AfpR4ClX-L;1Gf2#u znT7;fPZ@N7Y92GgKl8n^X()OD;gG5qopkf+|D99p2 zG@OqS(9+V1r11wbz7!5~R?u=<2L5~yj~Q9DHF!Lz2mA1nyNCm{f^L9ktXL`zijs>RYZ*%KKLI|>$ zqcBi+&9-L&5aqYDBN9!jOMqb%Ab**J5b6a7&bpl+C0z85gmN8G#hy@6tOPUo&kcGt z!ZCNt*)%GFHsZ`JPgX}%ii$m<5JE_sq;xHIMdWsqS_Jif0@+-aN4>EVLvP-|$ah~r z@u~L7FX;)z|At0D+P7J=sr@E&c*qE5lj>@H&4$!K1@ymkUOxFQltzGvJv;Q0mBZiJ zh*NLE`2MR2Taf!>BoRLi>A!>`3vvk{^8b;zEdBz+D4?^#&>w#Q00005Z`_EuQ5KD)Mr9`Fa(LBh8jZJ5*kB@uoWV^tdv^VKnweWg=NP%XZFtCncW37 z+K+7Rotb;j_nmX^x%Xhe!t3FXsqJdGe&rVKO?)o!3Sp=ZKg^Ef)0>}})PGe%Tmt<5 zdKN*DLbi4`G4^2ezZAoEt*B#tH6cm@M2SNc;uRy|6;)}BGFB4u^Wg9eB<7;rX91*y z_JKHbJ4!;b`x%!Q`QA(D9iw7!_&SO_+y>I6Z$_mF0*VmRM8F?JCYy5FN}?!&fx3ER z_S5j!1(40Akx0fdHuW9iu{Hd>G|Qep7@b6@w+By_ln7LVDkRj}Yj=XJRmUe&TP)b+ z0Hc6+&tJyq`dHbVV+FJjS&?wFbqyz>4c#SyR!b3RYqp=`v6wS15sM!$KoYB@1Gg@4 z+t}7nHhy<=kK6h2MfZnjsoNWgpqKy*xgT5tQZ>8i2}T@ZA|=A`)et{7cQtVv(CeWI z-JX-*<#zjz7Z|vG0jm!aEQrLM8w%s%iFO{@HLR{>(q+K1D+=-SYG*zPR@#67e-mEtWFEklw4xuMNLr8!M9cYUf;JnzG9a zmNz6yj)@hP?Rg5x=s9fU6KpS+Ip7FLbccJLepwdOH`T$f2XIQ9MlF2ot>&Vs z4trdI=h6HfJ&Amiy=S}|lEPmmz~A75&@}i_?;Oiu)| zdpVee0Gqxocq?d|29_g$Cz_(w}HH8E)mXtg2`wNn=9 z7yB^k47?OO2#Qmdd7aI9_W^}o6(mV1D`**BnmfoIW!N5pHX&V1WobwszmJaLqyF2N z9+`kIP|In!eUwD4@bT$+TpH4|%nTBI8sL?5+Ww3cY-_vs-~fgf+|8rL%sdl+dGgnpChBEaFnxymNPQ&3)kkt|YO0!*_2^=6Vns1_W$=r(?w za9KMNDpf>PYeFIH1T*>14O$v$n5*S%8r49Kl=`+MyCNz_)tXQWA*3x*dX`=hxl^PT zLG_wnKg z6%4+0S>5?Alt+MwJsx_=D&eFta@SihfBJUP5#+v%6ym=_`Y$0=K`sGA{y!2|#9tJ| Vnnf6pQCa{1002ovPDHLkV1g)BgOvaP literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/2.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/2.png new file mode 100644 index 0000000000000000000000000000000000000000..1c538e539bd0d6a8602af1ee7a35e661d313d541 GIT binary patch literal 1276 zcmVo=K8dcLZrr`Y6i5Om($&$U zj7&%3{KvIbN!STMViWHiIE?XXv1!eg1a^|M(BZYkX=f}q2F#zcE#d9`?^=SQB?UTP zRQ>YuvRPYME^kdhJ6^5Xgd#_^^I0~_$7_4Ctc9GP`NEIPQ|RgILwErsbUru)YR|8s zJJV;P<yyRo%R94uWqK%epid2u&d>KSI&Xph%p&yxuq(rDdU=$$djZdd>FO2wJ-i?@| zQ?_r$b7Q_yNcBbgFjNE9*7)Y)7%JrlnDXt7XwlJ2_JT2Os+rH6QY7Z*;Y6#E%lRha z<~~c63Uaaydxmw16blXDF32F-aq&ORY6LsUA|Ca1mEo% zb!;68k5Z2%*?AOUR1dhFiQ5E=^b5-aW zk1ENIz846I*HEiKBt)fytLr6P`3(5*t1Q|(x0?-m*HifN{^!6tR7(i$r5oaEU9RHn zw;y5c(YjdgGf&%Pr9oR)qtP47YPnHI5-1RBtDRIu z)QrTMP!l24g;VP5sK~h~QbSPvr_uPcA=|E<-55H02IIf{0Pne!$^538Q2%cjq*>hJ zolWI8VQ<74L43ZQ(pMCu0TR&v`eB~=4w@Ap$B_u5&f@TQb9B}V1i$_`X$Uz4xZ{7~ m^k2f`gd76o{C{*Di@yM1JHX(Yu?H3a0000 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/3.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/3.png new file mode 100644 index 0000000000000000000000000000000000000000..2cd4e38d3ab3bd24f4ddf3f8cded99b46aed8719 GIT binary patch literal 1301 zcmV+w1?u{VP)E*XulHfz3<+4&-Z=z+%tj3BA7HO5*FNaNmt zb5NdJqCOEoCYT4#Q(Gt!sc+XTGWbZE>3l11WJP(qI6x8Y! z)rOHm2a|hSptfCw#NH&->s2T}DZ%*EcTie+2siHCA?3HmCLx{agkSC%4n)Bul5We$ zPO2?({gcWD5w-*n8gS~!aTr@2Z+d4}0{bzTAMobVv^AF-1y~r;E#mE-)0&`Y(Si0C z<+!x6qTVg9ly)b;fj25Tp)gS8Qr1}I!fOY!Mhmn)^_d%3Cz0vy2LA#OI3FwmmFG60 zGu5rsn(9EbgYzz2&dcFoPRxm5a^4NBch#OJj^*L@;yggifz`PO^o-A65x^UOZRhjd zqyiQ7U3FpRd{*>Lidrr9Q7O;kdJ^H!n@%7JKMo}#I^jFuDF-lk{nKfBaw-uv0 z*>}}^Zq!u{$+2i3`EG#jGrqVo3gyy6m~!=vh_iZPFBs#d%zWm&B(bmnmRd$G=PJd` zW7923h7mizJQkU*%JZde9aqzd4ev?2p4e7bJ|%g0tq|D zN3Y=-4q!Hmc(fU;8TZ}43Fdj{4-AX`h<3wJADokWfZFe1{xJRF5lj^)C4n~K0OtUn zXa4T6I!``Z^Nd^8Myw1s=cH;qm%l6BL09p^)N~DT(f3Q^@ZQl6unLk^b-2QbVfdt_6b3O_k^hZdj!?#U_KDwUP2e~DiGY^KW7LmQ)uhwvX`F0upnC=2EaY8D_;PUq$C@7M^Z-NiU z`WmL5wo;{o^y;vQ8tsuU2xK^Yi4tRgymq$m7y z%((gUG?iXR1Ywfd4JY>%)OsQS%l=GrMZ`s_MZohKM#-RwU@ds^sJ;1a#JP4vgjGb5 zHKF6gsG9TWpA9@3-k7T8dKIF8LUdp5BrBq3MAn2%1W^@1am=bPxGGY8VExCa@6@SH z@9%_xGZ$g($8W&d6H}SrR1@<54ZVb+Tl{8Y`HeUf2!;@!Z>#hb1*r!S=zZh3nfVr) z9l*c=g~4E9_`5Zn^#b0tZzmNYi+~yUp9KAva7;oL0Sx{>1`fqvyfwlSLE$*e00000 LNkvXXu0mjfj$?4| literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/4.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/4.png new file mode 100644 index 0000000000000000000000000000000000000000..951a2417c8407459bb65a17fa8e23a4e4f7eee03 GIT binary patch literal 1323 zcmV+`1=RY9P) zhLAuI+HEnbLRZp7m!S)nEnOJWMWLlg7eTDFwneLiq=^brc(F-A%|s)OLP2L7CDGS= z&U@#+d*8gVrL_5ExcAOI@1E~`=iGV6awt6CobnQD37mT646Y1+>fi~Y++6%{a|rKs zz33(4PVmK0BZGAl?s76wt zZlAwin4j0rX6F}nCm@bja$!M{quix7!^*{r2iuGhY@htdjoA5UYH2}m0T@yb7J=MT zYiLZi=xEL8pvIml7nd{svL~(flrw+djo7>T%oE2lxG^&YBpfVG{?6Y?{xt!*0W>!5 zY2hd+qRy)>#?Q5>xk)jMmgW?x?2|nQ5$u~v5Q)GK7a}PUDBx)Y$a&#|G296vzLT*d zrs({+tM}9Wu2#rjtMPuI2Ew`Evn&0`F5JVY8{UX=iJsXj#&oDKA3x`ln4X3etstFt zb;QMMQ!Ss26yzJ~^Y!A@Fa;F7&U=J;G%^#NhHcN3tq|47<@;f6;9~gPq2?w%*C`OT zxl6EADD|Dx$h*s3n0k2_`2$_*jA7oR{7d~vEzsO7NRrK)JSB=o4 zJddC#O;rdN&Y#y;@XfbR`vjhSrjBJwr@_qW0g_a5&jgJ8nm2^%gj5(;D$rW?LS@4s zh_GY4^maT$0jg%@j}OC=ao>ZRVCFk>pjXZNLN$!&qkH}t(B~b@9Oe(DV03uICs3;t zkQ$(TW^VQBd`fQ399J#%Sru;X`B8^^nOnX(q$<6bimo9UeLp*hw~n5rF36Xv-c?HU z;;Z^TYc7$PQOaJac?Mltx!7wcy#Os<=xD~VGi|7;t48yB0w3S~5DlpYHaxlc36JfY zu-0+&2iXU}x@uCscn51=&hoi;C+4(G>@{Q+TF+u)XqZXN-N>T(M2Zg+5`<(~HJ*0% zVH=Nmo%7a^+g{iE>|BAKUwgQYnITs@FL@0`1(O7U4j?rI_4N%{_5Q#{Sq|G<{txI@ z`3CkpuEg?kv10-`_kaJvPKgA)69PEa+c5d4hC3Z<&#Bd#8JgtpCqDm$RhdzRB!p_7 zOJYaequffK9TLQ;fSkv5M2C;G#=njQ*M1%2){BS`OftKnqq@oCJ4s9{<^()X2fyEf?}A1qvjb`zQGwQ87yP zghGUnS59dyQIT`qqz0h=Pf*w@@R-`)i0+dYF!19yaLN+8@{4*x`QOm1FydCf+0=d$ z4n>?1)a%=7eN{v1p#<7rI&N0Jg<=KBaU{a1vpD>nja0pY_tUo{nvg}nbo@`8{!2I} hA&USx{~sO4;xGF6(ki5OLa+b;002ovPDHLkV1k|9f+qj~ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/5.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/5.png new file mode 100644 index 0000000000000000000000000000000000000000..e32b23487d23d76d5b96d837797cbe075b8aeb2a GIT binary patch literal 1332 zcmV-41j8 z=e&3B&zqSjqWy;9-uv#k_k8EO=gvD527&#ZF*mdkg44&(!qxPb7Cc9U^}&y~#^95I zPhIRkFCqqkNTh*CAehM7pO0bmdg{L|ns9^o>1}Hu)KxB74lu8B2t!H6$;(N$u9>OnoZc*gdQ{xbecfj2{ngX?;6N$CN zy-u{Hrv19JMud$32xWNd@G(d&k5;VNkw7y#^BvACP8xHG5rFv%szsdaep?X~Eh^C3 zKx8mp+?>dw#_Cl4ish zo!7VJd}73w3VCDEKJwK7)n|Nlbp&$RhcIEQ8xiK{iM?Qqn=cn5X=))VM?fgW1dsWsvwZC zrM>jZuBHHHv+zfoL0-G>{!P%&JK26w^e-zlwCIC#-X0+LJCHm?Up#_|^teZ$MJT{I zfajUKeNmn#pRInz4f)GPhU;@))M_qy+fxTs#TQe>HAJHy7DwT|BOk!@lQi^w{3e{- zyB|(HP-**7<_o_)E}spmpLS#Jx2?Rfb|!Tb+;|B05YX5N8sco{FAyd9D=g}TXtmIZ;=|+zB3!>^ZBo(@a3S((FdbWoaBUZ!* zi7=eH4S3ONf^FEMalV`dXInWz#|U=5FlNL3=_Ptr^BS@WItf;zv2~B!eBZt{Sa*Mi zVx0rqo1SQ!;UetXs)yCpO2-Iv?*0A)IYkoqPViwFZ^P)bMyhnMJjYUNVQiM3Pk()r zR9O&-#Dp4OjzU@7BWJB3WoD-gbY86@I&`?Z`s^ov^z7uh-9TC+kqM9|KWre7kbLnFOkA^pODCS)Rrf^dpsjtZU2BGm`he~i**iP~6m2MnD$52>HN1*KhixIce4E(sX~ qbjSb1>A!@f6EX;(^Z(ItRs01L4ax&c+Hc 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 KMNUMnLSTZyC#b&w literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/meta.json new file mode 100644 index 0000000000..8ed018ec41 --- /dev/null +++ b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/meta.json @@ -0,0 +1,62 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/09a5191fb11aab8ddffe3f9be94292b53e4d96f6/icons/mob/hud/alien_standard.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "16" + }, + { + "name": "15" + }, + { + "name": "14" + }, + { + "name": "13" + }, + { + "name": "12" + }, + { + "name": "11" + }, + { + "name": "10" + }, + { + "name": "9" + }, + { + "name": "8" + }, + { + "name": "7" + }, + { + "name": "6" + }, + { + "name": "5" + }, + { + "name": "4" + }, + { + "name": "3" + }, + { + "name": "2" + }, + { + "name": "1" + }, + { + "name": "0" + } + ] +} diff --git a/Resources/Textures/Goobstation/Changeling/ling_armor.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Goobstation/Changeling/ling_armor.rsi/equipped-OUTERCLOTHING.png new file mode 100644 index 0000000000000000000000000000000000000000..5510da2679e702a45002e31dd5c8db5ae96f02aa GIT binary patch literal 2340 zcmV+<3ETFGP)Px--AP12RCr$Pn@wz#MHt7QZ5!5>Y*P|x1tTI70g;abNvj8xC?`WrJQ3r;1dSIp zdh{b64C+yXUJROeFou(fHJ*q8IVepy@F4;QQ6Zu&CXjCEvf6gdZ{)ujUfSK6d3UP@ z-V57zXXpRSf1aOX_*1yS*Kaoo_&;mFd)?i&=PQ+5o0$CLk>TOm@W@E=@ADGh zD*RjvK!{)V?zOzTkV=L~sF~H?@9s{LBborf3iglhXw@#H5CHbJkr7K>xGE{5%(kssW&MSM?LK+Z5fZY%Mq_5fXFPMu>=?tl6#S+F!v2~&?yzIO{bt86 zUbKasopllk&b34fX2ajHdbQov-*0Fg4;%E+$&;3X-;e;9 zeQeb#yY1*v2lQ@Nm;3jHwQJoDqX{e?0d^ffZtrZ_;=cdz@L@;7=ci8P(gGjvsIc(I+Cn8-~4B#U?-U)s0z!Uc!r$!Wlk<#OTv z{{GtHTW+zjYSj*ojny@4XkfqwibcC{<%)efK3=%HRH`BL$A=Et>B-3?EfD^eix=yf z6pGJppQ%=D-=alL@tc|e6QNoV@ao`T?aLDqDp@-b2@L+16mP*&)XMdOE15iOZAiO?drn^|QO0{bE z1r|M1C|F0WW-kv78A9rN!W2;f;fN9``G68G7V6`p?(#J(On(!ev-oIXGe_9a2OPU} z$zB^AbOe$I{5&z?J_D2m_8lZ4kcTjphX6K!aOUHqk?i<*rBdHmg+4iG;qM$BwRL@c z4L6`60SEvXc8@cYQjA3RYm=OTa&c%Z-k8hjie3k6$`w33JTeze&h+oZ<-1-kq#I~ z%4L@XDduh{m5g_21kMlvc&CF?Bj*$IT^8TZKbQx3Qt^Wbz^gmk@9cExFRG3ZEZEQb z{H%?r5wplfV|{!wQ&gQ@=V9li`h2J85Q-lxq)ufzgjA-dU1&r&5i_%4L(5KqSwa*Q z@akaW^$CrHJPtv~00GUses zRyG)$c?J^z2={?FKu%jqj?e(ifYTb^wex0(OAzGH;Yb?DJB7m73))};{2d?+bA-!0*V1fHso|-b7nF-#7Mmd1n{R}=@$m2Ufv@Zp}DFLlYX8x=5+$7+? zx-;`7SF#4=DMz(>ad~7(Cct0Bd=*9;5tk}M5&^T-<5LAl(T2II!YMLD6X2_x-z&gE zQUr)4U@nZ*Ds?Ub5I);4z`NK3Nhk?A3~7EXngoodjTB!HO+a?h{*KI-1|Y+1tvT~X zfO2!?+w-yTtd{tgS$RS=igD2zK)L$CWy|cV^XJ_S?80Kv6-e5p_OmTUPLSfvCMlb=ol=N?4DUVS!p%Mf8w9&*05M!@w z0L9v$#>X8Nl_wQ$)F%+zm=R5YnkA(6*ddi+JzSc$>s}Ds_xlliStymIG-d=7zyekE zRjUf`X6qSfml>e`em??bdYU05M{G?XItoAlJFhYn-57chgsZZvlSUC6Y6fJUjAa9| z*uGqhX?bBp8i3=G?V)ylOUisj0hPgo&EoMzpPxPJimAxeHUhFeI9yMuIg08Gy?U*_ z6!kR(XG~?tzlh~3j4+IO(62z3N~l%^4`f2BKvMNxLqo0!!nZ%-ao5yTbTSeuEKy*p ztLwUsFd8Vhu2*QLViY=O@IWRM`|LQ43&ekE1Uu5s!iez6`sDolwmfcwX~|K&pe+Ot zh7@`L@XZ1C(SX?6T&SQv0d%f`MaZ2^o>2QwiUP7d7Ek=g@lZ4Hdjw+yh#`O|YZpxj#4tvBm&e3$&RB9DMiUV1o%zV@;&&4*xirz0TS9j zktqzOu%Cwn|NjiBK|3V{PtpKVQrctHl#Dady*NK_dx&v002ov KPDHLkV1fXTA8pA1 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/ling_armor.rsi/icon.png b/Resources/Textures/Goobstation/Changeling/ling_armor.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c0ab6299110eef8d99fe643c942834ee11694749 GIT binary patch literal 1020 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 oCIaI6^>}Fr0^$hs!TuMhwN8u$nFY510000t<8 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/ling_spacesuit.rsi/icon.png b/Resources/Textures/Goobstation/Changeling/ling_spacesuit.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..27bcc7fe797ad0a8e1836d25472817a9b48951db GIT binary patch literal 687 zcmV;g0#N;lP)Px%Xh}ptR9Hvtmd{HYK@`W|8V~~ki4x74qz8pu)QHf72hj#86xtq2MX(Cq^(c7g zAK*ps)?*Js(UW*kp-@VQUUDc+lUk5KduY^X1Cd1`R*YM|122qg+8uFSDP%7DV|Mp_ z-gn;j?Pzq*L%ZkrR0pUlP*>ppQeeKh+3bx(==sjhHSqPG9`kEHPp=K*MoB3@jNnmS zr(hr;de`9gcJ-bgOaRLDH zV>r45>}?rD>Gec9xP2c~5{Bc^hU0;JqTA*~(^>_5yfT2>_LDZ6LU28~$ys)_v=}KhIOB VFj;3>fB*mh07*qoM6N<$f&j(eEtLQO literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/ling_spacesuit.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/ling_spacesuit.rsi/meta.json new file mode 100644 index 0000000000..ffa6adf456 --- /dev/null +++ b/Resources/Textures/Goobstation/Changeling/ling_spacesuit.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "whateverusername0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-OUTERCLOTHING", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Goobstation/Changeling/ling_spacesuit_helmet.rsi/equipped-HELMET.png b/Resources/Textures/Goobstation/Changeling/ling_spacesuit_helmet.rsi/equipped-HELMET.png new file mode 100644 index 0000000000000000000000000000000000000000..27a959da55eeec133fec773894c0b0cbc3a909b5 GIT binary patch literal 864 zcmV-m1E2hfP)Px&7)eAyRCr$Pn!ipPK@i3V8Veiyl#4jWkt;yKh|UE`lz>E$Dx$(~Ku3`_5=t5I z0(pTvfSm@VN`VAKNk~I!6#hs-gybL>9p)~wkho}8+S4hOckNw`aQiZR;wjJ5Tx2&*R|rlY?y`6&+u4J3jiKO5zVP7 zYtE}CrPueh0rc8{`g;t3xDK;0qJ`Zxim2~)4n383zfX?>gk(}bzl z=LAr9$h1CAfN8?i>vICAJ7iiPC%`me>h(DR)EzRdj}xFvID0>jHEi4rXSEKhzg7a| zen3{xZ5UeH5g25DWdQPi9tXxp-W20^8xZ~KhDfiI1Q0P?8S zQm(1;GyJ@=BDO;wHk%wifE8ycJwfyaNm6s%x#p12-gGvQ9@_C5k7+wPi5a<9xu#o^cJPwWz z?kc<~mEh~af%1-kG5~BSA-ue~flj9b?`CG;MHB%pPMH30kX_Kg9fe}C2&_< zz5(>&8s&Ew0A+V0X8U8?!gJ5?2Zj> qoB$h?Dfi$6Pt=`Ln7SY zPBY|dau9H}FJlu@;x*j3InvoZeTwphO0Ir~HMZ^S7fwkxbn2>hN@?jBx-5v@ta9W1 z5f9_b=l_`N&%SGFP$0?tyqWO^gLDFO3bVUnN_}VUrXHL2D;1nfmYZ^COuK5hr<>`u zisIQzc|o)Nmj(Cue&)V##dO|xYYnw6j90#`wm4?WW6dDC&t`sMl+Hi)1z%QInewKs z>yDn4#IB$`hc%aZ-}}=ma(5qSeqO3@+vfPQj}9TBw?1!?4p|`JG%fXK?Md0=in;6A zCNP9NzPIgZp`CjEy~~ps>%LA*WH39x26kDq^bdwjO74oH3#KFkea_(N>gTe~DWQo0 E0HeTn7ytkO literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/ling_spacesuit_helmet.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/ling_spacesuit_helmet.rsi/meta.json new file mode 100644 index 0000000000..a40d761820 --- /dev/null +++ b/Resources/Textures/Goobstation/Changeling/ling_spacesuit_helmet.rsi/meta.json @@ -0,0 +1,18 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "whateverusername0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon" + }, + { + "name": "equipped-HELMET", + "directions": 4 + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-icon.png b/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..7c8bd9243376a01a126d0898f268d56b5b1724c3 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnF3?v&v(vJfvp#Yx{*8>L*7&92BF&NKeF#gYA ze1^ez&JoKCKv~9;AirP+hi5m^fSg`W7sn8diAT>K6m3=zUt>ep`t(lw;1>YPF8{dy;jp%vdVs zTu`1AR9nIk7d4Ml>_p<9cB$pg1-*T7ri?7RW(p*5zH0R4II`zYTBebdhrlnRX_2=q lO9PH7Hz)39<9RjzCsVnZs0+89NgvQ*44$rjF6*2UngHL{R0{wA literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-inhand-left.png b/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-inhand-left.png new file mode 100644 index 0000000000000000000000000000000000000000..5f9c0fb57667501428b218db9dfa7ef7f122eb2c GIT binary patch literal 1020 zcmVPx&v`IukRCr$PT0wH#Fbt$G?MHIV&HYP%ki04Rfj`nybL)T9S<^d0I3huS(vTxl zGnrU21rv(}K#6+^PkvmU-2X5Em<)Jw1ZE06IRW-5g4b|et|5fa@O5Vt&fgx$e%;SQ z!1-qg;r%g21HNRl)`s>Q_`W#>IML{MbP~r$2m#LXumGUh90q_i(gYzzo^1Ga2PLv3 zchuM%27r%nUEl~J&1fU??+-TMJ2($!!1*|~3_!`Ic5e;k(=kmcfXSxL!&x2x0)XJK z0I+5AQdv8!Ah-dLk)o5*aeih2gi`>d6vdp)qnyVa-2lWw7a0JW!?}1ZQl1Xu+Fx<5 z(KxlKjkiX8*A!U7b%{~^e!yYUo`BR8K-`ns92NjX3M~)OPTL>_=igWS9RUa8ZhT!_z4e}t6 z0kkOrc)K&M44|hTxB*~PSn!w%%zfDoacx};=`2`_=>87xS_H9bV`M>fNq;VV2?SZ7 zrGd1mjrBzQ5CG^TD9;=wBiJ6TF~$p9;2XB$5maJGnA{h17~B6ha%lL2RosMVjz04ri6 zjNej^c;6N^Ghh!nj0M!3dZY^ge2d#6hp{>@wMV#j-Gi)q-Io4d69c%PFO6E{Fc#G%)~s`Eb{203}6AmmPx&Z%IT!RCr$PnvHRzFc5|1nz&}JfuyFSrlf&uhMQ;jhP6NpzF^N!u$Li zPcYxYFqYW583Js6+W53djgJt5HqU4cfD&6*0K}dKgxI^V;m;kEi6x#x3@EX6W%$QD zkvxsJ<@xuS4O&E-7uwpqMhgG{{#O{rXKdXBAl|$-%h2QkM59FjWY;)5@h7%E0ARh( zChf-gOac^8TBIcgEcs~(2{AThYmx!FNt@FU7}_+G=)14Xq@KCJuC08wXX^?8(P#4N zzu8dNsxpGa>TwZ zhtuTbg?0dl^8pP~%GS+J5YLM0mj1N900FYCg+ZJHbe*y_2>_cE<;evwg7Qd-Y#9Jb zVanDA0PK(Dd6hYu2msN4+hI-N(M|~196(!ao4VN66$7}5Y!7{id6HwR-p7fAE8!pz zcB{bDHBH?Ur7rFWnGhg&25+tVvtG~Hy7?GupQxw^cpPvRAljinW$zhVHv!N?kq;Qy1&Tq2Cf6(?E&xgI|IBu;JtolfVT&{*Y6DQ_JH^LodMn+ z@Ls<&z}o}f>vsltd%%1BVg_8T82KB*_)j{ONVQUXGF1h@m5Py+YQ+rzj?$sPN`PhlA!I(*3Hnd=Y<^AA=e@Fj!>x;hXCCf3=jxsDMpaHaWHFDD{hUD zRbQR07_oZ4>A-VQt+<5%4a(Vykv-LlTN1P(Kn_T$FlzgNoNC1_1Z-SFOU~nKHyq!e zQ?0lGpd4W3bQoP5lPU00s+HRHimSlYijkCR#SMU?Y3?;&F$25|K6>2_z|n~Qw)y@8 Z%t4}1-KbR-00000NkvXXu0mjf003dyxyb+k literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/shields.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/shields.rsi/meta.json new file mode 100644 index 0000000000..a7ffc69df7 --- /dev/null +++ b/Resources/Textures/Goobstation/Changeling/shields.rsi/meta.json @@ -0,0 +1,22 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from https://github.com/ParadiseSS13/Paradise/blob/master/icons/obj/weapons/shield.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "ling-icon" + }, + { + "name": "ling-inhand-left", + "directions": 4 + }, + { + "name": "ling-inhand-right", + "directions": 4 + } + ] +} diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/Changeling.png b/Resources/Textures/Interface/Misc/job_icons.rsi/Changeling.png new file mode 100644 index 0000000000000000000000000000000000000000..95be43946010d365dcb8820ee628f21dde3327f7 GIT binary patch literal 177 zcmeAS@N?(olHy`uVBq!ia0vp^93afW1|*O0@9PFqjKx9jP7LeL$-D$|vOHZJLo|Yu zQxXyqCQO|8lYi00gOk<$B_tgu{rU5UXH(*83Ay!L%&RRgs2zC_@U`#Zjni(>)dj Z#&YN1*B(#N1KP^q>FVdQ&MBdZ0RT)%J!t>{ literal 0 HcmV?d00001 diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json index 745cc43b84..95b57d99c5 100644 --- a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json @@ -185,6 +185,12 @@ }, { "name": "InitialInfected" + }, + { + "name": "Admin" + }, + { + "name": "Changeling" } ] } From ec880fc0306b0e957db7a850e68195716d4b0f10 Mon Sep 17 00:00:00 2001 From: username <113782077+whateverusername0@users.noreply.github.com> Date: Sun, 14 Jul 2024 04:42:40 +1000 Subject: [PATCH 03/20] GO --- Content.Client/Content.Client.csproj | 8 - .../Changeling/UI/TransformMenu.xaml | 11 - .../Changeling/UI/TransformMenu.xaml.cs | 26 -- .../Changeling/ChangelingSystem.cs | 234 +++++++++--------- .../GameTicking/Rules/ChangelingRuleSystem.cs | 17 +- .../Systems/ImpersonateConditionSystem.cs | 22 +- .../Changeling/ChangelingComponent.cs | 21 +- .../Changeling/objectives/changeling.ftl | 8 +- .../changeling/abilities/changeling.ftl | 5 +- .../game-presets/preset-changeling.ftl | 11 +- .../changeling/store/changeling-catalog.ftl | 8 +- Resources/Prototypes/GameRules/roundstart.yml | 4 + .../Changeling/Actions/changeling.yml | 30 ++- .../Changeling/GameRules/roundstart.yml | 6 +- .../Changeling/Objectives/changeling.yml | 3 +- .../Goobstation/Changeling/game_presets.yml | 31 +++ Resources/Prototypes/game_presets.yml | 1 + Resources/Prototypes/secret_weights.yml | 1 + 18 files changed, 215 insertions(+), 232 deletions(-) delete mode 100644 Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml delete mode 100644 Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml.cs create mode 100644 Resources/Prototypes/Goobstation/Changeling/game_presets.yml diff --git a/Content.Client/Content.Client.csproj b/Content.Client/Content.Client.csproj index f15d8e262d..c1958acba7 100644 --- a/Content.Client/Content.Client.csproj +++ b/Content.Client/Content.Client.csproj @@ -33,12 +33,4 @@ - - - - - - MSBuild:Compile - - diff --git a/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml b/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml deleted file mode 100644 index 77b874af22..0000000000 --- a/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml.cs b/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml.cs deleted file mode 100644 index b626605046..0000000000 --- a/Content.Client/Goobstation/Changeling/UI/TransformMenu.xaml.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Content.Client.UserInterface.Controls; -using Robust.Client.AutoGenerated; -using Robust.Client.GameObjects; -using Robust.Client.UserInterface.XAML; - -namespace Content.Client.Changeling.UI; - -[GenerateTypedNameReferences] -public sealed partial class TransformMenu : RadialMenu -{ - [Dependency] private readonly EntityManager _entity = default!; - - private readonly SpriteSystem _sprite = default!; - - public TransformMenu() - { - IoCManager.InjectDependencies(this); - RobustXamlLoader.Load(this); - - _sprite = _entity.System(); - - var main = FindControl("Main"); - - // TODO: transform radial menu - } -} diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index daa1d9b2dc..7c31d50ca7 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -26,14 +26,11 @@ using Content.Server.Body.Systems; using Content.Shared.Actions; using Content.Shared.Polymorph; -using Content.Shared.Humanoid.Prototypes; using Robust.Shared.Serialization.Manager; using Content.Server.Actions; using Content.Server.Humanoid; using Content.Server.Polymorph.Components; using Content.Shared.Chemistry.EntitySystems; -using Content.Shared.Camera; -using Robust.Server.Player; using Content.Server.Flash; using Content.Server.Emp; using Robust.Server.GameObjects; @@ -55,6 +52,10 @@ using Content.Shared.Cuffs; using Content.Shared.Fluids; using Content.Shared.Stealth.Components; +using Content.Shared.Revolutionary.Components; +using Robust.Shared.Player; +using System.Numerics; +using Content.Shared.Camera; namespace Content.Server.Changeling; @@ -69,6 +70,7 @@ public sealed partial class ChangelingSystem : EntitySystem [Dependency] private readonly PolymorphSystem _polymorph = default!; [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly IPrototypeManager _proto = default!; @@ -80,12 +82,12 @@ public sealed partial class ChangelingSystem : EntitySystem [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; [Dependency] private readonly TransformSystem _transform = default!; - [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; - [Dependency] private readonly IPlayerManager _playerMan = default!; [Dependency] private readonly FlashSystem _flash = default!; [Dependency] private readonly EmpSystem _emp = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly PoweredLightSystem _light = default!; + [Dependency] private readonly ISharedPlayerManager _player = default!; + [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly InventorySystem _inventory = default!; @@ -100,17 +102,17 @@ public sealed partial class ChangelingSystem : EntitySystem [Dependency] private readonly SharedCuffableSystem _cuffs = default!; [Dependency] private readonly SharedPuddleSystem _puddle = default!; - public ProtoId ArmbladePrototype = "ArmBladeChangeling"; - public ProtoId FakeArmbladePrototype = "FakeArmBladeChangeling"; + public EntProtoId ArmbladePrototype = "ArmBladeChangeling"; + public EntProtoId FakeArmbladePrototype = "FakeArmBladeChangeling"; - public ProtoId ShieldPrototype = "ChangelingShield"; - public ProtoId BoneShardPrototype = "ThrowingStarChangeling"; + public EntProtoId ShieldPrototype = "ChangelingShield"; + public EntProtoId BoneShardPrototype = "ThrowingStarChangeling"; - public ProtoId ArmorPrototype = "ChangelingClothingOuterArmor"; - public ProtoId ArmorHelmetPrototype = "ChangelingClothingHeadHelmet"; + public EntProtoId ArmorPrototype = "ChangelingClothingOuterArmor"; + public EntProtoId ArmorHelmetPrototype = "ChangelingClothingHeadHelmet"; - public ProtoId SpacesuitPrototype = "ChangelingClothingOuterHardsuit"; - public ProtoId SpacesuitHelmetPrototype = "ChangelingClothingHeadHelmetHardsuit"; + public EntProtoId SpacesuitPrototype = "ChangelingClothingOuterHardsuit"; + public EntProtoId SpacesuitHelmetPrototype = "ChangelingClothingHeadHelmetHardsuit"; public override void Initialize() { @@ -118,8 +120,6 @@ public override void Initialize() SubscribeLocalEvent(OnStartup); - SubscribeLocalEvent(OnMobStateChange); - SubscribeLocalEvent(OnOpenEvolutionMenu); SubscribeLocalEvent(OnAbsorb); SubscribeLocalEvent(OnAbsorbDoAfter); @@ -177,7 +177,6 @@ public override void Update(float frameTime) public void Cycle(EntityUid uid, ChangelingComponent comp) { UpdateChemicals(uid, comp); - UpdateModifier(comp); if (comp.StrainedMusclesActivated) { @@ -196,10 +195,27 @@ public void PlayMeatySound(EntityUid uid, ChangelingComponent comp) var sound = comp.SoundPool.ToArray()[rand]; _audio.PlayPvs(sound, uid, AudioParams.Default.WithVolume(-3f)); } - public void PlayShriekSound(EntityUid uid, ChangelingComponent comp) + public void DoScreech(EntityUid uid, ChangelingComponent comp) { - // todo: add camera shake _audio.PlayPvs(comp.ShriekSound, uid); + + var center = Transform(uid).MapPosition; + var gamers = Filter.Empty(); + gamers.AddInRange(center, comp.ShriekPower, _player, EntityManager); + + foreach (var gamer in gamers.Recipients) + { + if (gamer.AttachedEntity == null) + continue; + + var pos = Transform(gamer.AttachedEntity!.Value).WorldPosition; + var delta = center.Position - pos; + + if (delta.EqualsApprox(Vector2.Zero)) + delta = new(.01f, 0); + + _recoil.KickCamera(uid, -delta.Normalized()); + } } ///

@@ -216,10 +232,10 @@ public bool IsIncapacitated(EntityUid uid) private void UpdateChemicals(EntityUid uid, ChangelingComponent comp, float? amount = null) { - var regen = (float) Math.Abs(1 * (1 + Math.Clamp(comp.ChemicalRegenerationModifier, -.75f, float.PositiveInfinity))); + //var regen = (float) Math.Abs(1 * (1 + Math.Clamp(comp.ChemicalRegenerationModifier, -.75f, float.PositiveInfinity))); var chemicals = comp.Chemicals; - chemicals += amount ?? regen; + chemicals += amount ?? 1 /*regen*/; comp.Chemicals = Math.Clamp(chemicals, 0, comp.MaxChemicals); @@ -227,11 +243,6 @@ private void UpdateChemicals(EntityUid uid, ChangelingComponent comp, float? amo _alerts.ShowAlert(uid, "Chemicals"); } - public void UpdateModifier(ChangelingComponent comp) - { - var modifier = comp.ChemicalRegenerationMobStateModifier + comp.ChemicalRegenerationAbilityModifier; - comp.ChemicalRegenerationModifier = modifier; - } public bool TryUseAbility(EntityUid uid, ChangelingComponent comp, BaseActionEvent action) { @@ -311,7 +322,7 @@ public bool TryReagentSting(EntityUid uid, ChangelingComponent comp, EntityTarge return true; } - public bool TryToggleItem(EntityUid uid, ProtoId proto, ref EntityUid? outItem, string? clothingSlot = null) + public bool TryToggleItem(EntityUid uid, EntProtoId proto, ref EntityUid? outItem, string? clothingSlot = null) { if (outItem == null) { @@ -382,14 +393,38 @@ public bool TryStealDNA(EntityUid uid, EntityUid target, ChangelingComponent com return true; } - private EntityUid? Transform(EntityUid uid, TransformData data, ChangelingComponent? comp = null, bool persistentDna = false) + private ChangelingComponent? CopyChangelingComponent(EntityUid target, ChangelingComponent comp) { - if (!_proto.TryIndex(data.Appearance.Species, out var species)) - return null; + var newComp = EnsureComp(target); + newComp.AbsorbedDNA = comp.AbsorbedDNA; + newComp.AbsorbedDNAIndex = comp.AbsorbedDNAIndex; + newComp.Chemicals = comp.Chemicals; + + newComp.IsInLesserForm = comp.IsInLesserForm; + newComp.CurrentForm = comp.CurrentForm; + + newComp.TotalAbsorbedEntities = comp.TotalAbsorbedEntities; + newComp.TotalStolenDNA = comp.TotalStolenDNA; + + return comp; + } + private EntityUid? TransformEntity(EntityUid uid, TransformData? data = null, EntProtoId? protoId = null, ChangelingComponent? comp = null, bool persistentDna = false) + { + EntProtoId? pid = null; + + if (data != null) + { + if (!_proto.TryIndex(data.Appearance.Species, out var species)) + return null; + pid = species.Prototype; + } + else if (protoId != null) + pid = protoId; + else return null; var config = new PolymorphConfiguration() { - Entity = species.Prototype, + Entity = (EntProtoId) pid, TransferDamage = true, Forced = true, Inventory = PolymorphInventoryChange.Transfer, @@ -401,24 +436,27 @@ public bool TryStealDNA(EntityUid uid, EntityUid target, ChangelingComponent com if (newUid == null) return null; - Comp(newUid.Value).Fingerprint = data.Fingerprint; - Comp(newUid.Value).DNA = data.DNA; - _humanoid.CloneAppearance(data.Appearance.Owner, newUid.Value); - _metaData.SetEntityName(newUid.Value, data.Name); - RemCompDeferred(newUid.Value); + var newEnt = newUid.Value; + + if (data != null) + { + Comp(newEnt).Fingerprint = data.Fingerprint; + Comp(newEnt).DNA = data.DNA; + _humanoid.CloneAppearance(data.Appearance.Owner, newEnt); + _metaData.SetEntityName(newEnt, data.Name); + var message = Loc.GetString("changeling-transform-finish", ("target", data.Name)); + _popup.PopupEntity(message, newEnt, newEnt); + } + + RemCompDeferred(newEnt); if (comp != null) { // copy our stuff - var lingCompCopy = _serialization.CreateCopy(comp, notNullableOverride: true); - AddComp(newUid.Value, lingCompCopy, true); - var newLingComp = Comp(newUid.Value); - - newLingComp.AbsorbedDNA = comp.AbsorbedDNA; - newLingComp.CurrentForm = data; - newLingComp.IsInLesserForm = false; - if (!persistentDna) - newLingComp.AbsorbedDNA.Remove(data); // a one timer opportunity. + var newLingComp = CopyChangelingComponent(newEnt, comp); + if (!persistentDna && data != null) + newLingComp?.AbsorbedDNA.Remove(data); + RemCompDeferred(uid); if (TryComp(uid, out var storeComp)) { @@ -426,11 +464,14 @@ public bool TryStealDNA(EntityUid uid, EntityUid target, ChangelingComponent com RemComp(newUid.Value); EntityManager.AddComponent(newUid.Value, storeCompCopy); } - RemCompDeferred(uid); } - var message = Loc.GetString("changeling-transform-finish", ("target", data.Name)); - _popup.PopupEntity(message, newUid.Value, newUid.Value); + // exceptional comps check + // there's no foreach for types i believe so i gotta thug it out yandev style. + if (HasComp(uid)) + EnsureComp(newEnt); + if (HasComp(uid)) + EnsureComp(newEnt); QueueDel(uid); @@ -454,8 +495,8 @@ public bool TryTransform(EntityUid target, ChangelingComponent comp, bool sting var locName = Identity.Entity(target, EntityManager); EntityUid? newUid = null; if (sting) - newUid = Transform(target, (TransformData) data, persistentDna: persistentDna); - else newUid = Transform(target, (TransformData) data, comp, persistentDna); + newUid = TransformEntity(target, data: data, persistentDna: persistentDna); + else newUid = TransformEntity(target, data: data, comp: comp, persistentDna: persistentDna); if (newUid != null) { @@ -482,18 +523,6 @@ private void OnStartup(EntityUid uid, ChangelingComponent comp, ref ComponentSta _actions.AddAction(uid, actionId); } - private void OnMobStateChange(EntityUid uid, ChangelingComponent comp, ref MobStateChangedEvent args) - { - var modifier = 0f; - switch (args.NewMobState) - { - case MobState.Alive: default: modifier = 0; break; - case MobState.Critical: modifier = -.25f; break; - case MobState.Dead: modifier = -.5f; break; - } - comp.ChemicalRegenerationMobStateModifier = modifier; - } - #endregion #region Basic Abilities @@ -508,9 +537,6 @@ private void OnOpenEvolutionMenu(EntityUid uid, ChangelingComponent comp, ref Op private void OnAbsorb(EntityUid uid, ChangelingComponent comp, ref AbsorbDNAEvent args) { - if (!TryUseAbility(uid, comp, args)) - return; - var target = args.Target; if (!IsIncapacitated(target)) @@ -529,6 +555,9 @@ private void OnAbsorb(EntityUid uid, ChangelingComponent comp, ref AbsorbDNAEven return; } + if (!TryUseAbility(uid, comp, args)) + return; + var popupOthers = Loc.GetString("changeling-absorb-start", ("user", Identity.Entity(uid, EntityManager)), ("target", Identity.Entity(target, EntityManager))); _popup.PopupEntity(popupOthers, uid, PopupType.LargeCaution); PlayMeatySound(uid, comp); @@ -613,7 +642,7 @@ private void OnStingExtractDNA(EntityUid uid, ChangelingComponent comp, ref Stin private void OnTransformCycle(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformCycleEvent args) { comp.AbsorbedDNAIndex += 1; - if (comp.AbsorbedDNAIndex >= comp.MaxAbsorbedDNA || comp.AbsorbedDNAIndex >= comp.AbsorbedDNA.Count - 1) + if (comp.AbsorbedDNAIndex >= comp.MaxAbsorbedDNA || comp.AbsorbedDNAIndex >= comp.AbsorbedDNA.Count) comp.AbsorbedDNAIndex = 0; if (comp.AbsorbedDNA.Count == 0) @@ -646,6 +675,8 @@ private void OnEnterStasis(EntityUid uid, ChangelingComponent comp, ref EnterSta if (!TryUseAbility(uid, comp, args)) return; + comp.Chemicals = 0f; + if (_mobState.IsAlive(uid)) { // fake our death @@ -659,9 +690,6 @@ private void OnEnterStasis(EntityUid uid, ChangelingComponent comp, ref EnterSta if (!_mobState.IsDead(uid)) _mobState.ChangeMobState(uid, MobState.Dead); - // faster regen - comp.ChemicalRegenerationModifier += 2; - comp.IsInStasis = true; } private void OnExitStasis(EntityUid uid, ChangelingComponent comp, ref ExitStasisEvent args) @@ -691,9 +719,6 @@ private void OnExitStasis(EntityUid uid, ChangelingComponent comp, ref ExitStasi _popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), uid, uid); - // slower regen - comp.ChemicalRegenerationModifier -= 2; - comp.IsInStasis = false; } @@ -710,15 +735,9 @@ private void OnToggleArmblade(EntityUid uid, ChangelingComponent comp, ref Toggl return; if (comp.ArmbladeEntity != null) - { - comp.ChemicalRegenerationAbilityModifier -= .5f; _popup.PopupEntity(Loc.GetString("changeling-armblade-start"), uid, uid); - } else - { - comp.ChemicalRegenerationAbilityModifier += .5f; _popup.PopupEntity(Loc.GetString("changeling-hand-transform-end"), uid, uid); - } PlayMeatySound(uid, comp); } @@ -745,15 +764,9 @@ private void OnToggleArmor(EntityUid uid, ChangelingComponent comp, ref ToggleCh } if (comp.ArmorEntity != null) - { - comp.ChemicalRegenerationAbilityModifier -= .5f; _popup.PopupEntity(Loc.GetString("changeling-equip-armor-start"), uid, uid); - } else - { - comp.ChemicalRegenerationAbilityModifier += .5f; _popup.PopupEntity(Loc.GetString("changeling-equip-end"), uid, uid); - } PlayMeatySound(uid, comp); } @@ -777,7 +790,7 @@ private void OnShriekDissonant(EntityUid uid, ChangelingComponent comp, ref Shri if (!TryUseAbility(uid, comp, args)) return; - PlayShriekSound(uid, comp); + DoScreech(uid, comp); var pos = _transform.GetMapCoordinates(uid); var power = comp.ShriekPower; @@ -788,7 +801,7 @@ private void OnShriekResonant(EntityUid uid, ChangelingComponent comp, ref Shrie if (!TryUseAbility(uid, comp, args)) return; - PlayShriekSound(uid, comp); + DoScreech(uid, comp); var power = comp.ShriekPower; _flash.FlashArea(uid, uid, power, power * 2f * 1000f); @@ -814,14 +827,12 @@ private void ToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp) _speed.ChangeBaseSpeed(uid, 125f, 150f, 1f); _popup.PopupEntity(Loc.GetString("changeling-muscles-start"), uid, uid); comp.StrainedMusclesActivated = true; - comp.ChemicalRegenerationAbilityModifier -= 1; } else { _speed.ChangeBaseSpeed(uid, 100f, 100f, 1f); _popup.PopupEntity(Loc.GetString("changeling-muscles-end"), uid, uid); comp.StrainedMusclesActivated = false; - comp.ChemicalRegenerationAbilityModifier += 1; } PlayMeatySound(uid, comp); @@ -893,7 +904,13 @@ private void OnStingFakeArmblade(EntityUid uid, ChangelingComponent comp, ref St var target = args.Target; var fakeArmblade = EntityManager.SpawnEntity(FakeArmbladePrototype, Transform(target).Coordinates); - _hands.TryPickupAnyHand(target, fakeArmblade); + if (!_hands.TryPickupAnyHand(target, fakeArmblade)) + { + EntityManager.DeleteEntity(fakeArmblade); + comp.Chemicals += Comp(args.Action).ChemicalCost; + _popup.PopupEntity(Loc.GetString("changeling-sting-fail-simplemob"), uid, uid); + return; + } PlayMeatySound(target, comp); } @@ -968,13 +985,11 @@ public void OnChameleonSkin(EntityUid uid, ChangelingComponent comp, ref ActionC if (HasComp(uid) && HasComp(uid)) { - comp.ChemicalRegenerationAbilityModifier += 1f; RemComp(uid); _popup.PopupEntity(Loc.GetString("changeling-chameleon-end"), uid, uid); return; } - comp.ChemicalRegenerationAbilityModifier -= 1f; EnsureComp(uid); EnsureComp(uid); _popup.PopupEntity(Loc.GetString("changeling-chameleon-start"), uid, uid); @@ -986,8 +1001,7 @@ public void OnEphedrineOverdose(EntityUid uid, ChangelingComponent comp, ref Act var reagents = new List<(string, FixedPoint2)>() { - ("Desoxyephedrine", 10f), - ("Stimulants", 10f) + ("Synaptizine", 5f), }; if (TryInjectReagents(uid, reagents)) _popup.PopupEntity(Loc.GetString("changeling-inject"), uid, uid); @@ -1006,7 +1020,8 @@ public void OnHealUltraSwag(EntityUid uid, ChangelingComponent comp, ref ActionF var reagents = new List<(string, FixedPoint2)>() { - ("Ichor", 10f), + ("Impedrezene", 2.5f), + ("Ichor", 15f), ("TranexamicAcid", 5f) }; if (TryInjectReagents(uid, reagents)) @@ -1026,33 +1041,12 @@ public void OnLesserForm(EntityUid uid, ChangelingComponent comp, ref ActionLess if (!TryUseAbility(uid, comp, args)) return; - var poly = new PolymorphConfiguration() - { - Entity = "MobMonkey", - Forced = true, - TransferDamage = true, - Inventory = PolymorphInventoryChange.Drop, - RevertOnCrit = false, - RevertOnDeath = false - }; - var newUid = _polymorph.PolymorphEntity(uid, poly); - + var newUid = TransformEntity(uid, protoId: "MobMonkey", comp: comp); if (newUid == null) - return; - - RemCompDeferred(newUid.Value); - - // copy our stuff - var lingCompCopy = _serialization.CreateCopy(comp, notNullableOverride: true); - AddComp(newUid.Value, lingCompCopy, true); - - if (TryComp(uid, out var storeComp)) { - var storeCompCopy = _serialization.CreateCopy(storeComp, notNullableOverride: true); - RemComp(newUid.Value); - EntityManager.AddComponent(newUid.Value, storeCompCopy); + comp.Chemicals += Comp(args.Action).ChemicalCost; + return; } - RemCompDeferred(uid); PlayMeatySound((EntityUid) newUid, comp); var loc = Loc.GetString("changeling-transform-others", ("user", Identity.Entity((EntityUid) newUid, EntityManager))); @@ -1073,15 +1067,9 @@ public void OnSpacesuit(EntityUid uid, ChangelingComponent comp, ref ActionSpace } if (comp.SpacesuitEntity != null) - { - comp.ChemicalRegenerationAbilityModifier -= .5f; _popup.PopupEntity(Loc.GetString("changeling-equip-spacesuit-start"), uid, uid); - } else - { - comp.ChemicalRegenerationAbilityModifier += .5f; _popup.PopupEntity(Loc.GetString("changeling-equip-end"), uid, uid); - } PlayMeatySound(uid, comp); } diff --git a/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs b/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs index d2a1d29053..40210e7e1d 100644 --- a/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs +++ b/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs @@ -4,7 +4,6 @@ using Content.Server.Objectives; using Content.Server.Roles; using Content.Shared.Changeling; -using Content.Shared.IdentityManagement; using Content.Shared.NPC.Prototypes; using Content.Shared.NPC.Systems; using Content.Shared.Roles; @@ -86,8 +85,8 @@ public bool MakeChangeling(EntityUid target, ChangelingRuleComponent rule) private void OnTextPrepend(EntityUid uid, ChangelingRuleComponent comp, ref ObjectivesTextPrependEvent args) { - EntityUid? mostAbsorbedUid = null; - EntityUid? mostStolenUid = null; + var mostAbsorbedName = string.Empty; + var mostStolenName = string.Empty; var mostAbsorbed = 0f; var mostStolen = 0f; @@ -96,20 +95,20 @@ private void OnTextPrepend(EntityUid uid, ChangelingRuleComponent comp, ref Obje if (ling.TotalAbsorbedEntities > mostAbsorbed) { mostAbsorbed = ling.TotalAbsorbedEntities; - mostAbsorbedUid = ling.Owner; + if (_mind.TryGetMind(ling.Owner, out var mindId, out var mind)) + mostAbsorbedName = _objective.GetTitle((mindId, mind), string.Empty); } if (ling.TotalStolenDNA > mostStolen) { mostStolen = ling.TotalStolenDNA; - mostStolenUid = ling.Owner; + if (_mind.TryGetMind(ling.Owner, out var mindId, out var mind)) + mostStolenName = _objective.GetTitle((mindId, mind), string.Empty); } } var sb = new StringBuilder(); - if (mostAbsorbedUid != null) - sb.AppendLine(Loc.GetString("roundend-prepend-changeling-absorbed", ("name", Identity.Entity((EntityUid) mostAbsorbedUid, EntityManager)), ("number", mostStolen))); - if (mostStolenUid != null) - sb.AppendLine(Loc.GetString("roundend-prepend-changeling-stolen", ("name", Identity.Entity((EntityUid) mostStolenUid, EntityManager)), ("number", mostStolen))); + sb.AppendLine(Loc.GetString($"roundend-prepend-changeling-absorbed{(!string.IsNullOrWhiteSpace(mostAbsorbedName) ? "-named" : "")}", ("name", mostAbsorbedName), ("number", mostAbsorbed))); + sb.AppendLine(Loc.GetString($"roundend-prepend-changeling-stolen{(!string.IsNullOrWhiteSpace(mostStolenName) ? "-named" : "")}", ("name", mostStolenName), ("number", mostStolen))); args.Text = sb.ToString(); } diff --git a/Content.Server/Goobstation/Objectives/Systems/ImpersonateConditionSystem.cs b/Content.Server/Goobstation/Objectives/Systems/ImpersonateConditionSystem.cs index c7cd0e3e29..086f462fb6 100644 --- a/Content.Server/Goobstation/Objectives/Systems/ImpersonateConditionSystem.cs +++ b/Content.Server/Goobstation/Objectives/Systems/ImpersonateConditionSystem.cs @@ -1,16 +1,19 @@ using Content.Server.Objectives.Components; -using Content.Shared.IdentityManagement; +using Content.Server.Shuttles.Systems; +using Content.Shared.Cuffs.Components; using Content.Shared.Mind; using Content.Shared.Objectives.Components; namespace Content.Server.Objectives.Systems; /// -/// Handles kill person condition logic and picking random kill targets. +/// Handles escaping on the shuttle while being another person detection. /// public sealed class ImpersonateConditionSystem : EntitySystem { [Dependency] private readonly TargetObjectiveSystem _target = default!; + [Dependency] private readonly EmergencyShuttleSystem _emergencyShuttle = default!; + [Dependency] private readonly SharedMindSystem _mind = default!; public override void Initialize() { @@ -53,8 +56,21 @@ private void OnAfterAssign(EntityUid uid, ImpersonateConditionComponent comp, re comp.MindId = args.MindId; } + // copypasta from escape shittle objective. eh. private void OnGetProgress(EntityUid uid, ImpersonateConditionComponent comp, ref ObjectiveGetProgressEvent args) { - args.Progress = comp.Completed ? 1f : 0f; + args.Progress = GetProgress(args.Mind, comp); + } + + public float GetProgress(MindComponent mind, ImpersonateConditionComponent comp) + { + // not escaping alive if you're deleted/dead + if (mind.OwnedEntity == null || _mind.IsCharacterDeadIc(mind)) + return 0f; + // You're not escaping if you're restrained! + if (TryComp(mind.OwnedEntity, out var cuffed) && cuffed.CuffedHandCount > 0) + return 0f; + + return (_emergencyShuttle.IsTargetEscaping(mind.OwnedEntity.Value) ? .5f : 0f) + (comp.Completed ? .5f : 0f); } } diff --git a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs index f4d2a2dfad..02fa5991f3 100644 --- a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs +++ b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs @@ -3,9 +3,6 @@ using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; -using Robust.Shared.Serialization; - - namespace Content.Shared.Changeling; @@ -75,14 +72,6 @@ public sealed partial class ChangelingComponent : Component /// public readonly float UpdateTimer = 1f; - public float ChemicalRegenerationMobStateModifier = 0f; - public float ChemicalRegenerationAbilityModifier = 0f; - /// - /// Modifier for chemical regeneration. Positive = faster, negative = slower. - /// - [ViewVariables(VVAccess.ReadOnly)] - public float ChemicalRegenerationModifier = 0f; - [ViewVariables(VVAccess.ReadOnly)] public List AbsorbedDNA = new(); @@ -125,7 +114,7 @@ public sealed partial class ChangelingComponent : Component } [DataDefinition] -public partial struct TransformData +public sealed partial class TransformData { /// /// Entity's name. @@ -150,12 +139,4 @@ public partial struct TransformData /// [ViewVariables(VVAccess.ReadOnly), NonSerialized] public HumanoidAppearanceComponent Appearance; - - public static bool operator ==(TransformData one, TransformData two) - => one.Name == two.Name && one.Fingerprint == two.Fingerprint && one.DNA == two.DNA; - public static bool operator !=(TransformData one, TransformData two) - => !(one.Name == two.Name && one.Fingerprint == two.Fingerprint && one.DNA == two.DNA); - - - } diff --git a/Resources/Locale/en-US/Goobstation/Changeling/objectives/changeling.ftl b/Resources/Locale/en-US/Goobstation/Changeling/objectives/changeling.ftl index 9245653028..47c7a326ae 100644 --- a/Resources/Locale/en-US/Goobstation/Changeling/objectives/changeling.ftl +++ b/Resources/Locale/en-US/Goobstation/Changeling/objectives/changeling.ftl @@ -1,7 +1,7 @@ objective-condition-absorb-title = Absorb {$count} humanoids. -objective-condition-absorb-description = I must absorb {$count} humanoids. It is necessary for my survival. +objective-condition-absorb-description = I must absorb {$count} humanoids. It is necessary for my survival and further evolution. -objective-condition-stealdna-title = Steal {$count} compatible genomes. -objective-condition-stealdna-description = I must steal {$count} unique genomes. The hivemind network is no good, it needs to be special. +objective-condition-stealdna-title = Extract {$count} compatible genomes. +objective-condition-stealdna-description = I must extract {$count} unique genomes. -objective-condition-escape-identity-title = Escape on the evacuation shuttle while being {$targetName}, {CAPITALIZE($job)}. \ No newline at end of file +objective-condition-escape-identity-title = Escape on the evacuation shuttle alive and unrestrained while being {$targetName}, {CAPITALIZE($job)}. \ No newline at end of file diff --git a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl index 187467367a..f37b5bcb99 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl @@ -9,7 +9,7 @@ changeling-absorb-fail-absorbed = You've already absorbed it. changeling-absorb-fail-unabsorbable = The target is not absorbable. changeling-absorb-end-self = Another organic absorbed. You are evolving. changeling-absorb-end-self-ling = Another changeling absorbed. You are evolving more rapidly. -changeling-absorb-onexamine = [color=red]It is nothing but a husk of a person, an empty shell.[/color] +changeling-absorb-onexamine = [color=red]The body feels hollow.[/color] changeling-transform-cycle = Switched to {$target}'s DNA. changeling-transform-cycle-empty = You don't have any DNA strains! @@ -22,7 +22,8 @@ changeling-sting-fail-self = You tried to sting {CAPITALIZE(THE($target))}, but changeling-sting-fail-ling = Someone just tried to silently sting you! changeling-sting = You silently sting {CAPITALIZE(THE($target))} -changeling-sting-extract-max = Disposed of the first stored DNA to free up space for new DNA. +changeling-sting-fail-simplemob = You can't sting a lesser creature! +changeling-sting-extract-max = Disposed of the first stored DNA to free up space for new DNA changeling-sting-extract-fail = Can't extract DNA, need another target changeling-stasis-enter = You enter regenerative stasis diff --git a/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl b/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl index 36b0609a13..685a835d26 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl @@ -2,8 +2,10 @@ changeling-roundend-name = changeling objective-issuer-hivemind = [color=orange]Hivemind[/color] -roundend-prepend-changeling-absorbed = [color=white]{$name}[/color] has absorbed [color=red]{$number}[/color] organics. -roundend-prepend-changeling-stolen = [color=white]{$name}[/color] has stolen [color=orange]{$number}[/color] DNA samples. +roundend-prepend-changeling-absorbed-named = [color=white]{$name}[/color] has absorbed a total of [color=red]{$number}[/color] organics. +roundend-prepend-changeling-stolen-named = [color=white]{$name}[/color] has extracted a total of [color=orange]{$number}[/color] DNA samples. +roundend-prepend-changeling-absorbed = Someone has absorbed a total of [color=red]{$number}[/color] organics. +roundend-prepend-changeling-stolen = Someone had extracted a total of [color=orange]{$number}[/color] DNA samples. changeling-gamemode-title = Changelings changeling-gamemode-description = @@ -12,8 +14,7 @@ changeling-gamemode-description = changeling-role-greeting = You are a changeling who has absorbed and taken the form of {$name}! Your objectives are listed in the character menu. - Use your abilities to the max to achieve them. + Absorb, shapeshift, evolve and use your abilities to the max to complete them! changeling-role-greeting-short = - You are a changeling. - Your starting form is {$name}. + You are a changeling who has absorbed and taken the initial form of {$name}. diff --git a/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl b/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl index 043981ddd9..0461f0ea7b 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl @@ -9,7 +9,6 @@ evolutionmenu-combat-armblade-desc = evolutionmenu-combat-boneshard-name = Bone Shard evolutionmenu-combat-boneshard-desc = Break off shards of your bone and shape them into a throwing star which embeds into your foes. But a one timer opportinuty. - Requires you to absorb at least 1 organic to use the ability. Costs 15 chemicals on activation. evolutionmenu-combat-armor-name = Chitinous Armor @@ -17,18 +16,21 @@ evolutionmenu-combat-armor-desc = Inflate your body into an all-consuming chitinous mass of armor. Provides extensive protection against physical damage, but less against other types. It massively slows your movement, and maintaining its shape slows chemical generation. + WARNING: Requires you to absorb at least 2 organics to use the ability. Costs 25 chemicals on activation, significantly slows chemical regeneration. evolutionmenu-combat-shield-name = Organic Shield evolutionmenu-combat-shield-desc = Reforms one of your arms into a large, fleshy shield. Blocks attacks automatically, but very brittle. + WARNING: Requires you to absorb at least 1 organic to use the ability. Costs 20 chemicals on activation. evolutionmenu-combat-shriek-dissonant-name = Dissonant Shriek evolutionmenu-combat-shriek-dissonant-desc = You emit an EMP blast, which disables technology in the surrounding area, including radio headsets. Good for escaping cyborgs and security. + WARNING: Requires you to absorb at least 1 organic to use the ability. Costs 30 chemicals on activation. evolutionmenu-combat-shriek-resonant-name = Resonant Shriek @@ -36,6 +38,7 @@ evolutionmenu-combat-shriek-resonant-desc = You emit a tone beyond the range of human hearing, bursting lights and causing disorientation in an area around yourself. Good for escaping groups, or hindering people from fleeing. + WARNING: Requires you to absorb at least 1 organic to use the ability. Costs 30 chemicals on activation. evolutionmenu-combat-strainedmuscles-name = Strained Muscles @@ -81,14 +84,12 @@ evolutionmenu-sting-transform-name = Transformation Sting evolutionmenu-sting-transform-desc = Inject some of your genome into an organic target, forcing their body to shapeshift into whoever you've chosen using the Cycle DNA ability. May be used while under the effects of Lesser Form. - Requires you to absorb at least 2 organics to use the ability. Costs 75 chemicals. evolutionmenu-sting-armblade-name = Fake Arm Blade Sting evolutionmenu-sting-armblade-desc = Inject some of your genome into an organic target, forcing their arm to shapeshift into a dull armblade. May be used while under the effects of Lesser Form. - Requires you to absorb at least 1 organic to use the ability. Costs 50 chemicals. # utility @@ -129,6 +130,7 @@ evolutionmenu-utility-lastresort-name = Last Resort evolutionmenu-utility-lastresort-desc = Abandon your current body and becomes a headslug in a last ditch effort to evade discovery or capture. As a headslug, you can lay eggs inside anybody, and, after some time, a monkey with your original conciousness will burst out of the body. + Requires you to absorb at least 1 organic to use the ability. Costs 20 chemicals. evolutionmenu-utility-lesserform-name = Lesser Form diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index 9b6a902649..e4bf931f64 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -149,6 +149,10 @@ - prefRoles: [ Traitor ] max: 12 playerRatio: 10 + blacklist: + components: + - AntagImmune + - Changeling # Goobstation lateJoinAdditional: true mindComponents: - type: TraitorRole diff --git a/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml index 622dd38a4d..919832d7cc 100644 --- a/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml +++ b/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml @@ -37,6 +37,7 @@ event: !type:AbsorbDNAEvent {} - type: ChangelingAction chemicalCost: 25 + useInLesserForm: true - type: entity id: ActionStingExtractDNA @@ -57,6 +58,7 @@ event: !type:StingExtractDNAEvent {} - type: ChangelingAction chemicalCost: 25 + useInLesserForm: true - type: entity id: ActionChangelingTransformCycle @@ -72,6 +74,7 @@ event: !type:ChangelingTransformCycleEvent {} - type: ChangelingAction chemicalCost: 0 + useInLesserForm: true - type: entity id: ActionChangelingTransform @@ -93,7 +96,7 @@ - type: entity id: ActionEnterStasis name: Enter regenerative stasis - description: Fake your death and enter into a regenerative stasis. Costs 15 chemicals. + description: Fake your death and enter into a regenerative stasis. Drains all your chemicals. noSpawn: true components: - type: InstantAction @@ -105,13 +108,13 @@ state: stasis_enter event: !type:EnterStasisEvent {} - type: ChangelingAction - chemicalCost: 15 + chemicalCost: 0 useInLesserForm: true - type: entity id: ActionExitStasis name: Exit stasis - description: Rise from the dead with full health. Costs 99 chemicals. + description: Rise from the dead with full health. Costs 60 chemicals. noSpawn: true components: - type: InstantAction @@ -123,7 +126,7 @@ state: stasis_exit event: !type:ExitStasisEvent {} - type: ChangelingAction - chemicalCost: 99 + chemicalCost: 60 useInLesserForm: true # combat @@ -158,7 +161,6 @@ event: !type:CreateBoneShardEvent {} - type: ChangelingAction chemicalCost: 15 - requireAbsorbed: 1 - type: entity id: ActionToggleChitinousArmor @@ -175,6 +177,7 @@ event: !type:ToggleChitinousArmorEvent {} - type: ChangelingAction chemicalCost: 25 + requireAbsorbed: 2 - type: entity id: ActionToggleOrganicShield @@ -191,6 +194,7 @@ event: !type:ToggleOrganicShieldEvent {} - type: ChangelingAction chemicalCost: 20 + requireAbsorbed: 1 - type: entity id: ActionShriekDissonant @@ -199,7 +203,7 @@ noSpawn: true components: - type: InstantAction - useDelay: 2 + useDelay: 10 itemIconStyle: NoItem icon: sprite: Goobstation/Changeling/changeling_abilities.rsi @@ -208,6 +212,7 @@ - type: ChangelingAction chemicalCost: 30 useInLesserForm: true + requireAbsorbed: 1 - type: entity id: ActionShriekResonant @@ -216,7 +221,7 @@ noSpawn: true components: - type: InstantAction - useDelay: 2 + useDelay: 10 itemIconStyle: NoItem icon: sprite: Goobstation/Changeling/changeling_abilities.rsi @@ -225,6 +230,7 @@ - type: ChangelingAction chemicalCost: 30 useInLesserForm: true + requireAbsorbed: 1 - type: entity id: ActionToggleStrainedMuscles @@ -348,7 +354,6 @@ - type: ChangelingAction chemicalCost: 50 useInLesserForm: true - requireAbsorbed: 1 - type: entity id: ActionStingTransform @@ -370,7 +375,6 @@ - type: ChangelingAction chemicalCost: 75 useInLesserForm: true - requireAbsorbed: 2 # utility @@ -381,7 +385,7 @@ noSpawn: true components: - type: InstantAction - useDelay: 5 + useDelay: 30 checkCanInteract: false checkConsciousness: false itemIconStyle: NoItem @@ -451,7 +455,7 @@ noSpawn: true components: - type: InstantAction - useDelay: 5 + useDelay: 10 checkCanInteract: false checkConsciousness: false itemIconStyle: NoItem @@ -470,9 +474,8 @@ noSpawn: true components: - type: InstantAction - useDelay: 5 + useDelay: 30 checkCanInteract: false - checkConsciousness: false itemIconStyle: NoItem icon: sprite: Goobstation/Changeling/changeling_abilities.rsi @@ -499,6 +502,7 @@ event: !type:ActionLastResortEvent {} - type: ChangelingAction chemicalCost: 20 + requireAbsorbed: 1 - type: entity id: ActionToggleLesserForm diff --git a/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml b/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml index b87dc611fc..50fee01eae 100644 --- a/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml +++ b/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml @@ -4,7 +4,7 @@ components: - type: ChangelingRule - type: GameRule - minPlayers: 10 + minPlayers: 15 delay: min: 10 max: 20 @@ -12,8 +12,8 @@ agentName: changeling-roundend-name definitions: - prefRoles: [ Changeling ] - max: 8 - playerRatio: 10 + max: 6 + playerRatio: 15 lateJoinAdditional: true mindComponents: - type: ChangelingRole diff --git a/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml index 1507faf14f..182cead716 100644 --- a/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml +++ b/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml @@ -53,5 +53,4 @@ - type: ImpersonateCondition - type: TargetObjective title: objective-condition-escape-identity-title - - type: PickRandomPerson - - type: EscapeShuttleCondition \ No newline at end of file + - type: PickRandomPerson \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/game_presets.yml b/Resources/Prototypes/Goobstation/Changeling/game_presets.yml new file mode 100644 index 0000000000..ff7ffd00bf --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/game_presets.yml @@ -0,0 +1,31 @@ +- type: gamePreset + id: Changeling + alias: + - ling + - lings + - changeling + name: changeling-gamemode-title + description: changeling-gamemode-description + showInVote: false + rules: + - Changeling + - SubGamemodesRule + - BasicStationEventScheduler + - GameRuleMeteorScheduler + - BasicRoundstartVariation + +- type: gamePreset + id: Traitorling + alias: + - lingtraitor + - traitorling + name: secret-title + description: secret-description + showInVote: false + rules: + - Changeling + - Traitor + - SubGamemodesRule + - BasicStationEventScheduler + - GameRuleMeteorScheduler + - BasicRoundstartVariation \ No newline at end of file diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index 6986b646d7..703b3b41f6 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -42,6 +42,7 @@ - Revolutionary - Zombie - RampingStationEventScheduler + - Changeling - type: gamePreset id: Extended diff --git a/Resources/Prototypes/secret_weights.yml b/Resources/Prototypes/secret_weights.yml index 4ad31cd194..e9f4b8424a 100644 --- a/Resources/Prototypes/secret_weights.yml +++ b/Resources/Prototypes/secret_weights.yml @@ -5,5 +5,6 @@ Nukeops: 0.14 Zombie: 0.03 Traitor: 0.39 + Changeling: 0.20 #Pirates: 0.15 #ahoy me bucko From 24f86fba8e73b410db360d14f6d1a6aa74a531e0 Mon Sep 17 00:00:00 2001 From: username <113782077+whateverusername0@users.noreply.github.com> Date: Sun, 14 Jul 2024 20:18:42 +1000 Subject: [PATCH 04/20] more ling fixes (#400) * ling gamemode * more shit * gaming * Update game_presets.yml * Update secret_weights.yml * gaming 2 * shittery * fart * fart 2 * gorbing * balance stasis again * fart * goobing * chat is this real? * real. * g * fucking chemical regen i fucking hate it i hate you i hate everythi * fart * fart 2 * Add _timing * fart 3 * fart 5 * fart 6: electrical boogaloo --------- Co-authored-by: whateverusername0 Co-authored-by: Aidenkrz --- .../Changeling/ChangelingSystem.cs | 32 +++++++++++++++---- .../Changeling/ChangelingComponent.cs | 20 ++++++------ 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index 7c31d50ca7..7eb502e79e 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -56,12 +56,14 @@ using Robust.Shared.Player; using System.Numerics; using Content.Shared.Camera; +using Robust.Shared.Timing; namespace Content.Server.Changeling; public sealed partial class ChangelingSystem : EntitySystem { // this is one hell of a star wars intro text + [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedMindSystem _mind = default!; [Dependency] private readonly IRobustRandom _rand = default!; [Dependency] private readonly ActionsSystem _actions = default!; @@ -119,6 +121,7 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnMobStateChange); SubscribeLocalEvent(OnOpenEvolutionMenu); SubscribeLocalEvent(OnAbsorb); @@ -160,16 +163,17 @@ public override void Update(float frameTime) { base.Update(frameTime); + if (!_timing.IsFirstTimePredicted) + return; + foreach (var comp in EntityManager.EntityQuery()) { var uid = comp.Owner; - comp.UpdateAccumulator += frameTime; - - if (comp.UpdateAccumulator < comp.UpdateTimer) - return; + if (_timing.CurTime < comp.RegenTime) + continue; - comp.UpdateAccumulator -= comp.UpdateTimer; + comp.RegenTime = _timing.CurTime + TimeSpan.FromSeconds(comp.RegenCooldown); Cycle(uid, comp); } @@ -232,7 +236,6 @@ public bool IsIncapacitated(EntityUid uid) private void UpdateChemicals(EntityUid uid, ChangelingComponent comp, float? amount = null) { - //var regen = (float) Math.Abs(1 * (1 + Math.Clamp(comp.ChemicalRegenerationModifier, -.75f, float.PositiveInfinity))); var chemicals = comp.Chemicals; chemicals += amount ?? 1 /*regen*/; @@ -508,6 +511,17 @@ public bool TryTransform(EntityUid target, ChangelingComponent comp, bool sting return true; } + public void RemoveAllChangelingEquipment(EntityUid target, ChangelingComponent comp) + { + // yanderedev type shit + EntityManager.DeleteEntity(comp.ShieldEntity); + EntityManager.DeleteEntity(comp.ArmbladeEntity); + EntityManager.DeleteEntity(comp.ArmorEntity); + EntityManager.DeleteEntity(comp.ArmorHelmetEntity); + EntityManager.DeleteEntity(comp.SpacesuitEntity); + EntityManager.DeleteEntity(comp.SpacesuitHelmetEntity); + PlayMeatySound(target, comp); + } #endregion #region Event Handlers @@ -523,6 +537,12 @@ private void OnStartup(EntityUid uid, ChangelingComponent comp, ref ComponentSta _actions.AddAction(uid, actionId); } + private void OnMobStateChange(EntityUid uid, ChangelingComponent comp, ref MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead) + RemoveAllChangelingEquipment(uid, comp); + } + #endregion #region Basic Abilities diff --git a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs index 02fa5991f3..946d8d7dba 100644 --- a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs +++ b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs @@ -55,29 +55,27 @@ public sealed partial class ChangelingComponent : Component /// /// Current amount of chemicals changeling currently has. /// - [DataField("chemicals"), AutoNetworkedField] + [DataField, AutoNetworkedField] public float Chemicals = 100f; /// /// Maximum amount of chemicals changeling can have. /// - [DataField("maxChemicals"), AutoNetworkedField] + [DataField, AutoNetworkedField] public float MaxChemicals = 100f; - public float ChemicalDrain = 0f; - - public float UpdateAccumulator = 0f; /// - /// Time in seconds to take before the update cycle. + /// Cooldown between chem regen events. /// - public readonly float UpdateTimer = 1f; - + public TimeSpan RegenTime = TimeSpan.Zero; + public float RegenCooldown = 1f; [ViewVariables(VVAccess.ReadOnly)] public List AbsorbedDNA = new(); /// /// Index of . Used for switching forms. /// + [ViewVariables(VVAccess.ReadOnly)] public int AbsorbedDNAIndex = 0; /// @@ -89,13 +87,13 @@ public sealed partial class ChangelingComponent : Component /// /// Total absorbed DNA. Counts towards objectives. /// - [ViewVariables(VVAccess.ReadOnly)] + [ViewVariables(VVAccess.ReadWrite)] public int TotalAbsorbedEntities = 0; /// /// Total stolen DNA. Counts towards objectives. /// - [ViewVariables(VVAccess.ReadOnly)] + [ViewVariables(VVAccess.ReadWrite)] public int TotalStolenDNA = 0; [ViewVariables(VVAccess.ReadOnly)] @@ -109,7 +107,7 @@ public sealed partial class ChangelingComponent : Component /// The status icon corresponding to the Changlings. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField, ViewVariables(VVAccess.ReadOnly)] public ProtoId StatusIcon { get; set; } = "HivemindFaction"; } From 00c63c1661e7e4030205d26ff839abd82865b745 Mon Sep 17 00:00:00 2001 From: username <113782077+whateverusername0@users.noreply.github.com> Date: Mon, 15 Jul 2024 17:34:09 +1000 Subject: [PATCH 05/20] ling balance!!! (#405) * ling gamemode * more shit * gaming * Update game_presets.yml * Update secret_weights.yml * gaming 2 * shittery * fart * fart 2 * gorbing * balance stasis again * fart * goobing * chat is this real? * real. * g * fucking chemical regen i fucking hate it i hate you i hate everythi * fart * fart 2 * Add _timing * fart 3 * fart 5 * fart 6: electrical boogaloo * balance issue --------- Co-authored-by: whateverusername0 Co-authored-by: Aidenkrz --- .../Changeling/ChangelingSystem.cs | 18 ++++--- .../Changeling/ChangelingComponent.cs | 2 +- .../changeling/store/changeling-catalog.ftl | 52 ++++++------------- .../Changeling/Actions/changeling.yml | 18 +++---- 4 files changed, 36 insertions(+), 54 deletions(-) diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index 7eb502e79e..b063a68950 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -57,6 +57,7 @@ using System.Numerics; using Content.Shared.Camera; using Robust.Shared.Timing; +using Content.Shared.Gravity; namespace Content.Server.Changeling; @@ -182,11 +183,12 @@ public void Cycle(EntityUid uid, ChangelingComponent comp) { UpdateChemicals(uid, comp); - if (comp.StrainedMusclesActivated) + if (comp.StrainedMusclesActive) { var stamina = EnsureComp(uid); _stamina.TakeStaminaDamage(uid, 7.5f, visual: false); - if (_stamina.GetStaminaDamage(uid) >= stamina.CritThreshold) + if (_stamina.GetStaminaDamage(uid) >= stamina.CritThreshold + || !HasComp(uid)) ToggleStrainedMuscles(uid, comp); } } @@ -613,12 +615,12 @@ private void OnAbsorbDoAfter(EntityUid uid, ChangelingComponent comp, ref Absorb EnsureComp(target); var popup = Loc.GetString("changeling-absorb-end-self-ling"); - var bonusChemicals = 10; + var bonusChemicals = 0; var bonusEvolutionPoints = 0; if (HasComp(target)) { - bonusChemicals += 30; - bonusEvolutionPoints += 8; + bonusChemicals += 60; + bonusEvolutionPoints += 10; } else { @@ -842,17 +844,17 @@ private void OnToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp, re } private void ToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp) { - if (!comp.StrainedMusclesActivated) + if (!comp.StrainedMusclesActive) { _speed.ChangeBaseSpeed(uid, 125f, 150f, 1f); _popup.PopupEntity(Loc.GetString("changeling-muscles-start"), uid, uid); - comp.StrainedMusclesActivated = true; + comp.StrainedMusclesActive = true; } else { _speed.ChangeBaseSpeed(uid, 100f, 100f, 1f); _popup.PopupEntity(Loc.GetString("changeling-muscles-end"), uid, uid); - comp.StrainedMusclesActivated = false; + comp.StrainedMusclesActive = false; } PlayMeatySound(uid, comp); diff --git a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs index 946d8d7dba..b148ff100a 100644 --- a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs +++ b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs @@ -44,7 +44,7 @@ public sealed partial class ChangelingComponent : Component public EntityUid? ArmorEntity, ArmorHelmetEntity; public EntityUid? SpacesuitEntity, SpacesuitHelmetEntity; - public bool StrainedMusclesActivated = false; + public bool StrainedMusclesActive = false; public bool IsInLesserForm = false; diff --git a/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl b/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl index 0461f0ea7b..9819f6b3fa 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl @@ -4,12 +4,12 @@ evolutionmenu-combat-armblade-name = Arm Blade evolutionmenu-combat-armblade-desc = Reform one of your arms into a grotesque blade, composed of bone and flesh, able to pry open airlocks and cut through your foes like butter. - Costs 15 chemicals on activation, slows chemical regeneration. + Costs 15 chemicals. evolutionmenu-combat-boneshard-name = Bone Shard evolutionmenu-combat-boneshard-desc = Break off shards of your bone and shape them into a throwing star which embeds into your foes. But a one timer opportinuty. - Costs 15 chemicals on activation. + Costs 15 chemicals. evolutionmenu-combat-armor-name = Chitinous Armor evolutionmenu-combat-armor-desc = @@ -17,21 +17,21 @@ evolutionmenu-combat-armor-desc = Provides extensive protection against physical damage, but less against other types. It massively slows your movement, and maintaining its shape slows chemical generation. WARNING: Requires you to absorb at least 2 organics to use the ability. - Costs 25 chemicals on activation, significantly slows chemical regeneration. + Costs 25 chemicals. evolutionmenu-combat-shield-name = Organic Shield evolutionmenu-combat-shield-desc = Reforms one of your arms into a large, fleshy shield. Blocks attacks automatically, but very brittle. WARNING: Requires you to absorb at least 1 organic to use the ability. - Costs 20 chemicals on activation. + Costs 20 chemicals. evolutionmenu-combat-shriek-dissonant-name = Dissonant Shriek evolutionmenu-combat-shriek-dissonant-desc = You emit an EMP blast, which disables technology in the surrounding area, including radio headsets. Good for escaping cyborgs and security. WARNING: Requires you to absorb at least 1 organic to use the ability. - Costs 30 chemicals on activation. + Costs 30 chemicals. evolutionmenu-combat-shriek-resonant-name = Resonant Shriek evolutionmenu-combat-shriek-resonant-desc = @@ -39,20 +39,13 @@ evolutionmenu-combat-shriek-resonant-desc = bursting lights and causing disorientation in an area around yourself. Good for escaping groups, or hindering people from fleeing. WARNING: Requires you to absorb at least 1 organic to use the ability. - Costs 30 chemicals on activation. + Costs 30 chemicals. evolutionmenu-combat-strainedmuscles-name = Strained Muscles evolutionmenu-combat-strainedmuscles-desc = You reduce lactic acid buildup in your leg muscles, allowing you to move at extremely fast speeds. While active, you will take steadily increments of stamina damage and eventually pass out. - Cost-free on activation, halts chemical regeneration. - -evolutionmenu-combat-spiders-name = Spider Infestation -evolutionmenu-combat-spiders-desc = - One full grown hunter spider spawns from your mouth, being loyal to only you, and friendly with other spiders. - You can spawn up to three spiders total, and change their orders via other actions. - Costs 50 chemicals. - IMPORTANT: You must absorb at least 5 organics to activate the ability. + Cost-free. # sting @@ -60,25 +53,25 @@ evolutionmenu-sting-blind-name = Blind Sting evolutionmenu-sting-blind-desc = Silently sting an organic target, completely blinding them for a short time, and rendering them near-sighted until oculine is applied. May be used while under the effects of Lesser Form. - Costs 30 chemicals. + Costs 35 chemicals. evolutionmenu-sting-cryo-name = Cryogenic Sting evolutionmenu-sting-cryo-desc = Inject an organic target with a cocktail of chemicals that chills the blood. May be used while under the effects of Lesser Form. - Costs 30 chemicals. + Costs 35 chemicals. evolutionmenu-sting-lethargic-name = Lethargic Sting evolutionmenu-sting-lethargic-desc = Inject an organic target with a cocktail of anesthetics, slowing the victim down for a decent amount of time. May be used while under the effects of Lesser Form. - Costs 50 chemicals. + Costs 35 chemicals. evolutionmenu-sting-mute-name = Mute Sting evolutionmenu-sting-mute-desc = Inject mute toxin into an organic target, completely silencing them for a while. May be used while under the effects of Lesser Form. - Costs 30 chemicals. + Costs 35 chemicals. evolutionmenu-sting-transform-name = Transformation Sting evolutionmenu-sting-transform-desc = @@ -101,7 +94,7 @@ evolutionmenu-utility-panacea-desc = evolutionmenu-utility-eyesight-name = Augmented Eyesight evolutionmenu-utility-eyesight-desc = Evolve additional features in your eyes, such as flash protection. - Cost-free on activation. + Cost-free. evolutionmenu-utility-biodegrade-name = Biodegrade evolutionmenu-utility-biodegrade-desc = @@ -111,8 +104,7 @@ evolutionmenu-utility-biodegrade-desc = evolutionmenu-utility-chameleon-name = Chameleon Skin evolutionmenu-utility-chameleon-desc = - Alter the pigment in your skin to match your surroundings, rendering you invisible. - WARNING: Halts chemical regeneration! + Alter the pigment in your skin to match your surroundings, rendering you invisible.p Costs 20 chemicals. evolutionmenu-utility-stims-name = Ephedrine Overdose @@ -123,16 +115,9 @@ evolutionmenu-utility-stims-desc = evolutionmenu-utility-fleshmend-name = Fleshmend evolutionmenu-utility-fleshmend-desc = - Rapidly heal yourself of all bruises and burns. Significantly weaker when below 20 normal body temperature. + Rapidly heal yourself of all bruises and burns. Costs 35 chemicals. -evolutionmenu-utility-lastresort-name = Last Resort -evolutionmenu-utility-lastresort-desc = - Abandon your current body and becomes a headslug in a last ditch effort to evade discovery or capture. - As a headslug, you can lay eggs inside anybody, and, after some time, a monkey with your original conciousness will burst out of the body. - Requires you to absorb at least 1 organic to use the ability. - Costs 20 chemicals. - evolutionmenu-utility-lesserform-name = Lesser Form evolutionmenu-utility-lesserform-desc = Abandon your current form and turn into a sentient monkey. @@ -140,14 +125,9 @@ evolutionmenu-utility-lesserform-desc = evolutionmenu-utility-spacesuit-name = Space Adaptation evolutionmenu-utility-spacesuit-desc = - Get rid of useless tissue in order to facilitate space travel. A source of oxygen is still required for space walking. Slows chemical regeneration while active. + Get rid of useless tissue in order to facilitate space travel. A source of oxygen is still required for space walking. Costs 20 chemicals. evolutionmenu-utility-hivemindaccess-name = Hivemind Access evolutionmenu-utility-hivemindaccess-desc = - Tunes our chemical receptors for hivemind communication, giving us access to the hivemind network, allowing us to recognize other changelings who have also bought this ability. - -evolutionmenu-utility-contort-name = Contort Body -evolutionmenu-utility-contort-desc = - Allows you to contort your body, making you capable of fitting yourself in and under things where you normally don't fit, such as tables and closed airlocks. - Costs 25 chemicals. + Tunes our chemical receptors for hivemind communication, allowing us to recognize other changelings who have also bought this ability. diff --git a/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml index 919832d7cc..5ff5fe22ab 100644 --- a/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml +++ b/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml @@ -253,7 +253,7 @@ - type: entity id: ActionStingBlind name: Blind Sting - description: Silently sting your target, blinding them for a short time and rendering them near sighted. Costs 30 chemicals. + description: Silently sting your target, blinding them for a short time and rendering them near sighted. Costs 35 chemicals. noSpawn: true components: - type: EntityTargetAction @@ -268,13 +268,13 @@ state: sting_blind event: !type:StingBlindEvent {} - type: ChangelingAction - chemicalCost: 30 + chemicalCost: 35 useInLesserForm: true - type: entity id: ActionStingCryo name: Cryogenic Sting - description: Silently sting your target, constantly slowing and freezing them. Costs 30 chemicals. + description: Silently sting your target, constantly slowing and freezing them. Costs 35 chemicals. noSpawn: true components: - type: EntityTargetAction @@ -289,13 +289,13 @@ state: sting_cryo event: !type:StingCryoEvent {} - type: ChangelingAction - chemicalCost: 30 + chemicalCost: 35 useInLesserForm: true - type: entity id: ActionStingLethargic name: Lethargic Sting - description: Silently inject a cocktail of anesthetics into the target. Costs 50 chemicals. + description: Silently inject a cocktail of anesthetics into the target. Costs 35 chemicals. noSpawn: true components: - type: EntityTargetAction @@ -310,13 +310,13 @@ state: sting_lethargic event: !type:StingLethargicEvent {} - type: ChangelingAction - chemicalCost: 50 + chemicalCost: 35 useInLesserForm: true - type: entity id: ActionStingMute name: Mute Sting - description: Silently sting your target, completely silencing them for a short time. Costs 30 chemicals. + description: Silently sting your target, completely silencing them for a short time. Costs 35 chemicals. noSpawn: true components: - type: EntityTargetAction @@ -331,7 +331,7 @@ state: sting_mute event: !type:StingMuteEvent {} - type: ChangelingAction - chemicalCost: 30 + chemicalCost: 35 useInLesserForm: true - type: entity @@ -540,7 +540,7 @@ - type: entity id: ActionHivemindAccess name: Hivemind Access - description: Tune your chemical receptors for hivemind communication. Costs 0 chemicals. + description: Tune your chemical receptors for hivemind communication. noSpawn: true components: - type: InstantAction From d4cf48c9d16f7b58b6a2e3e6e2fda25fe6d036f9 Mon Sep 17 00:00:00 2001 From: Fenn <162015305+TooSillyFennec@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:39:51 +0000 Subject: [PATCH 06/20] Dual Antag Gamemodes, Calm Variants of game rules, and changeling delay matches traitor (#412) * Adds "Calm" Variants of gamerules, for dual antag gamemodes/admin usage * Create presets-dualantag.ftl * Update presets-dualantag.ftl * Update presets-dualantag.ftl * Update presets-dualantag.ftl * Adds Dual Antag Presets * Update secret_weights.yml * Update roundstart.yml * Update roundstart.yml * forgot about that~ - Thieves removed from dual antag * Update presets-dualantag.ftl * Piras weights - These seem to push the no major pretty low at 60%, may have to change later based on player feedback but that requires these weights to be played first. * really, me? --- .../game-presets/presets-dualantag.ftl | 8 + .../Changeling/GameRules/roundstart.yml | 20 --- .../Goobstation/Changeling/game_presets.yml | 31 ---- .../Goobstation/GameRules/roundstart.yml | 149 ++++++++++++++++++ .../Prototypes/Goobstation/game_presets.yml | 92 +++++++++++ Resources/Prototypes/secret_weights.yml | 20 ++- 6 files changed, 262 insertions(+), 58 deletions(-) create mode 100644 Resources/Locale/en-US/Goobstation/game-ticking/game-presets/presets-dualantag.ftl delete mode 100644 Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml delete mode 100644 Resources/Prototypes/Goobstation/Changeling/game_presets.yml create mode 100644 Resources/Prototypes/Goobstation/GameRules/roundstart.yml create mode 100644 Resources/Prototypes/Goobstation/game_presets.yml diff --git a/Resources/Locale/en-US/Goobstation/game-ticking/game-presets/presets-dualantag.ftl b/Resources/Locale/en-US/Goobstation/game-ticking/game-presets/presets-dualantag.ftl new file mode 100644 index 0000000000..dcb51e9198 --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/game-ticking/game-presets/presets-dualantag.ftl @@ -0,0 +1,8 @@ +traitorling-title = Traitorling +traitorling-description = Attention. Known enemy signals and strange biosigns detected. Confirmed Syndicate Agents and Changelings on board. + +revtraitor-title = Revolutionary Traitors +revtraitor-description = A revolution has been provoked by a member of the Syndicate, but not every agent got the hint... + +revling-title = Revolutionary Changelings +revling-description = A revolution has been provoked by the Syndicate, and opportunistic changelings have come to feast on the carnage. diff --git a/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml b/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml deleted file mode 100644 index 50fee01eae..0000000000 --- a/Resources/Prototypes/Goobstation/Changeling/GameRules/roundstart.yml +++ /dev/null @@ -1,20 +0,0 @@ -- type: entity - parent: BaseGameRule - id: Changeling - components: - - type: ChangelingRule - - type: GameRule - minPlayers: 15 - delay: - min: 10 - max: 20 - - type: AntagSelection - agentName: changeling-roundend-name - definitions: - - prefRoles: [ Changeling ] - max: 6 - playerRatio: 15 - lateJoinAdditional: true - mindComponents: - - type: ChangelingRole - prototype: Changeling \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/game_presets.yml b/Resources/Prototypes/Goobstation/Changeling/game_presets.yml deleted file mode 100644 index ff7ffd00bf..0000000000 --- a/Resources/Prototypes/Goobstation/Changeling/game_presets.yml +++ /dev/null @@ -1,31 +0,0 @@ -- type: gamePreset - id: Changeling - alias: - - ling - - lings - - changeling - name: changeling-gamemode-title - description: changeling-gamemode-description - showInVote: false - rules: - - Changeling - - SubGamemodesRule - - BasicStationEventScheduler - - GameRuleMeteorScheduler - - BasicRoundstartVariation - -- type: gamePreset - id: Traitorling - alias: - - lingtraitor - - traitorling - name: secret-title - description: secret-description - showInVote: false - rules: - - Changeling - - Traitor - - SubGamemodesRule - - BasicStationEventScheduler - - GameRuleMeteorScheduler - - BasicRoundstartVariation \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/GameRules/roundstart.yml b/Resources/Prototypes/Goobstation/GameRules/roundstart.yml new file mode 100644 index 0000000000..eb9cdda8f4 --- /dev/null +++ b/Resources/Prototypes/Goobstation/GameRules/roundstart.yml @@ -0,0 +1,149 @@ +- type: entity + parent: BaseGameRule + id: Changeling + components: + - type: ChangelingRule + - type: GameRule + minPlayers: 15 + delay: + min: 240 + max: 420 + - type: AntagSelection + agentName: changeling-roundend-name + definitions: + - prefRoles: [ Changeling ] + max: 5 + playerRatio: 15 + lateJoinAdditional: true + mindComponents: + - type: ChangelingRole + prototype: Changeling + +- type: entity + parent: BaseTraitorRule + id: CalmTraitor # For Dual Antag Gamemodes + components: + - type: GameRule + minPlayers: 30 + delay: + min: 240 + max: 420 + - type: AntagSelection + definitions: + - prefRoles: [ Traitor ] + max: 5 + playerRatio: 15 + blacklist: + components: + - AntagImmune + - Changeling + lateJoinAdditional: true + mindComponents: + - type: TraitorRole + prototype: Traitor + +- type: entity + parent: Changeling + id: CalmLing # For Dual Antag Gamemodes + components: + - type: GameRule + minPlayers: 30 + delay: + min: 240 + max: 420 + - type: AntagSelection + agentName: changeling-roundend-name + definitions: + - prefRoles: [ Changeling ] + max: 2 + playerRatio: 20 + lateJoinAdditional: true + mindComponents: + - type: ChangelingRole + prototype: Changeling + +- type: entity + parent: BaseNukeopsRule + id: Calmops # For Dual Antag Gamemodes + components: + - type: GameRule + minPlayers: 30 + - type: LoadMapRule + gameMap: NukieOutpost + - type: AntagSelection + selectionTime: PrePlayerSpawn + definitions: + - prefRoles: [ NukeopsCommander ] + fallbackRoles: [ Nukeops, NukeopsMedic ] + spawnerPrototype: SpawnPointNukeopsCommander + startingGear: SyndicateCommanderGearFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-commander + - SyndicateNamesElite + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: NukeopsCommander + - prefRoles: [ NukeopsMedic ] + fallbackRoles: [ Nukeops, NukeopsCommander ] + spawnerPrototype: SpawnPointNukeopsMedic + startingGear: SyndicateOperativeMedicFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-agent + - SyndicateNamesNormal + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: NukeopsMedic + - prefRoles: [ Nukeops ] + fallbackRoles: [ NukeopsCommander, NukeopsMedic ] + spawnerPrototype: SpawnPointNukeopsOperative + max: 1 + playerRatio: 15 + startingGear: SyndicateOperativeGearFull + components: + - type: NukeOperative + - type: RandomMetadata + nameSegments: + - nukeops-role-operator + - SyndicateNamesNormal + - type: NpcFactionMember + factions: + - Syndicate + mindComponents: + - type: NukeopsRole + prototype: Nukeops + +- type: entity + id: CalmRevs # For Dual Antag Gamemodes + parent: BaseGameRule + components: + - type: GameRule + minPlayers: 30 + - type: RevolutionaryRule + - type: AntagSelection + definitions: + - prefRoles: [ HeadRev ] + max: 1 + playerRatio: 25 + briefing: + text: head-rev-role-greeting + color: CornflowerBlue + sound: "/Audio/Ambience/Antag/headrev_start.ogg" + startingGear: HeadRevGear + components: + - type: Revolutionary + - type: HeadRevolutionary + mindComponents: + - type: RevolutionaryRole + prototype: HeadRev diff --git a/Resources/Prototypes/Goobstation/game_presets.yml b/Resources/Prototypes/Goobstation/game_presets.yml new file mode 100644 index 0000000000..771b0d6cee --- /dev/null +++ b/Resources/Prototypes/Goobstation/game_presets.yml @@ -0,0 +1,92 @@ +- type: gamePreset + id: Changeling + alias: + - ling + - lings + - changeling + name: changeling-gamemode-title + description: changeling-gamemode-description + showInVote: false + rules: + - Changeling + - SubGamemodesRule + - BasicStationEventScheduler + - GameRuleMeteorScheduler + - BasicRoundstartVariation + +- type: gamePreset + id: Traitorling + alias: + - lingtraitor + - traitorling + name: traitorling-title + description: traitorling-description + showInVote: false + rules: + - CalmLing + - CalmTraitor + - BasicStationEventScheduler + - GameRuleMeteorScheduler + - BasicRoundstartVariation + +- type: gamePreset + id: NukeTraitor + alias: + - nuketot + - optraitor + - optot + name: nukeops-title + description: nukeops-description + showInVote: false + rules: + - Calmops + - CalmTraitor + - BasicStationEventScheduler + - GameRuleMeteorScheduler + - BasicRoundstartVariation + +- type: gamePreset + id: NukeLing + alias: + - nukeling + - opling + name: nukeops-title + description: nukeops-description + showInVote: false + rules: + - Calmops + - CalmLing + - BasicStationEventScheduler + - GameRuleMeteorScheduler + - BasicRoundstartVariation + +- type: gamePreset + id: RevTraitor + alias: + - revtraitor + - revtot + - totrevs + name: revtraitor-title + description: revtraitor-description + showInVote: false + rules: + - CalmRevs + - CalmTraitor + - BasicStationEventScheduler + - GameRuleMeteorScheduler + +- type: gamePreset + id: RevLing + alias: + - revling + - lingrevs + name: revling-title + description: revling-description + showInVote: false + rules: + - CalmRevs + - CalmLing + - BasicStationEventScheduler + - GameRuleMeteorScheduler + - BasicRoundstartVariation + - BasicRoundstartVariation diff --git a/Resources/Prototypes/secret_weights.yml b/Resources/Prototypes/secret_weights.yml index e9f4b8424a..8a9f9a766a 100644 --- a/Resources/Prototypes/secret_weights.yml +++ b/Resources/Prototypes/secret_weights.yml @@ -1,10 +1,16 @@ - type: weightedRandom id: Secret weights: - Survival: 0.44 - Nukeops: 0.14 - Zombie: 0.03 - Traitor: 0.39 - Changeling: 0.20 - #Pirates: 0.15 #ahoy me bucko - + Traitor: 0.40 + Changeling: 0.10 + Traitorling: 0.05 + Nukeops: 0.20 + NukeTraitor: 0.01 + NukeLing: 0.01 + Revolutionary: 0.10 + RevTraitor: 0.02 + RevLing: 0.01 + Zombie: 0.05 + Survival: 0.05 + # Wizard: 0.05 + # Cult: 0.05 From 79f59ffb91fb9578832a9f969b7310f49404fca9 Mon Sep 17 00:00:00 2001 From: RealFakeSoof <131112412+RealFakeSoof@users.noreply.github.com> Date: Thu, 18 Jul 2024 17:37:17 +0000 Subject: [PATCH 07/20] #fixel (#415) * fixel * Fuck the pixel up harder. --------- Co-authored-by: Aidenkrz --- .../ling_armor.rsi/equipped-OUTERCLOTHING.png | Bin 2340 -> 1103 bytes .../Changeling/ling_armor.rsi/icon.png | Bin 1020 -> 494 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Resources/Textures/Goobstation/Changeling/ling_armor.rsi/equipped-OUTERCLOTHING.png b/Resources/Textures/Goobstation/Changeling/ling_armor.rsi/equipped-OUTERCLOTHING.png index 5510da2679e702a45002e31dd5c8db5ae96f02aa..cee98c3e609cc2ed24690bf79c49cf2a7db073e4 100644 GIT binary patch delta 1093 zcmV-L1iJgA63+;b8Gi!+005o0f$RVP00DDSM?wIu&K&6g000DMK}|sb0I`n?{9y$E z001peOjJda2??@JPX7P^QW_d85D<|#ID0oYE)Wn-9v&kN4L27TITjW$4h}355&!@I zKI6^00000FbW%=J|NsC0|NsC0|NsC0|Nj8g+?b&N000SaNPk�id000id0mpBs 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 KPDHLkV1fXTA8pA1 diff --git a/Resources/Textures/Goobstation/Changeling/ling_armor.rsi/icon.png b/Resources/Textures/Goobstation/Changeling/ling_armor.rsi/icon.png index c0ab6299110eef8d99fe643c942834ee11694749..18f0de148d299fdcb1723b0d59522b913bba40cb 100644 GIT binary patch delta 479 zcmeyv{*HNqWIZzj1B1(wu46!ou{g-xiDBJ2nU_EgOS+@4BLl<6e(pbstU$hQfKQ0) zCO^N)oSgp|7=omvbOZz@T3A$@o9hV(_{qsB^YWUDi(86`8SwGx2nhjosMxK@22%Vb zL4LviQ2@i0J2Msnr8o;b>LZI882Ao@Fyrz36)8Yh=zF?2hFJLTo%Fisr~*feW9Npd zzW4uk#6~CQEx%l!I?3}+jULCuf9`+mKRkKg^`hqHW2PniV&aXvD`RIK_sWwyZWO(J zqmbR_ev>4|ZKB&JI-EV77HrG7lf^@0zw)WXjm%*ojc2czb+|-|aZjk{dU#EVvGo{p zfn#XDK-`Le`M>xg6fQ=s3X9@de>aI~9_wCLU(Kr-vXjcYr#f9c?Y-KodTQSnkA*k( z?Amgx#F15tMPjKEpIHF z$ya%$K!H8s&r~5svtr(+jSR`Gk89?XUixzI;Jvk$-#_KO-OIeUp3&v4PTjrDA3@Q? N;OXk;vd$@?2>`{v#EVX#|(zLa4=!iY~QX`dKRKMylXKTneVR>Y|X1 zEp*Y3rHNEgiY;k{gc(dSZIY4m>>XmInMqRU#@S>t@6DWZ&woAl-Y3x^U(zAt(|-ar zD-erD&1fuE^ZT6ox;_9B->0VNW-eFrcdCBTBr+xXZhsh-@z{tjWSLw;v)gTv z;Eyx-uVRsUT3V>Lt&LXJ)*Pu&kpQB<7!1cC%XGr$6Omr)>l5I7&-*|YB(QhU#s~W<0Qpb(d?K<{U8hb( zu_Oi{Q~{wkJ|B&R!}_bZjM&7*@`Mu^Z-bV8ivR#2#kfp=7*IP8rj(qkwbyw zLqqgvW`=V4JYDGShBATxR12wUeJOy;0S?!{N+if5%ak(=f-eACk|c7OCfywvpz(Oz zS|8c13itq3QmG$Q^uSUkL*wDFkQ#m9o2H3-5Pw15ks9;`{4X~sN)Q*yw=M!u;H{z% z@(wexDcRwy2*8jkE>9?F8^qR02D#ydox}QBeiQ+-~|&EdJ9YK$b7Nc5K^< znFt2Vg071uG_~^ZCq@%O#o<29sUy z-|LI22*3lbg$0^!X%X&c55W0@=`^ti0BYhi4UPzKKfsX-ryib;On|2!x*{&S5nPoA z0FIW-xyQ?VGGYQef`?-7Mazx-al8rV$T(PZQ00000NkvXXu0mjfqW99! From 08c29c08d3f04214438ffd8faf26a414b4c5b50d Mon Sep 17 00:00:00 2001 From: username <113782077+whateverusername0@users.noreply.github.com> Date: Wed, 24 Jul 2024 14:30:32 +1000 Subject: [PATCH 08/20] more ling changes (#435) * ling gamemode * more shit * gaming * Update game_presets.yml * Update secret_weights.yml * gaming 2 * shittery * fart * fart 2 * gorbing * balance stasis again * fart * goobing * chat is this real? * real. * g * fucking chemical regen i fucking hate it i hate you i hate everythi * fart * fart 2 * Add _timing * fart 3 * fart 5 * fart 6: electrical boogaloo * balance issue * fart * holy shit!! * gorber * gorbing time * init * fart 10 --------- Co-authored-by: whateverusername0 Co-authored-by: Aidenkrz Co-authored-by: Fishbait --- .../Goobstation/Changeling/ChangelingSystem.cs | 5 +++-- Content.Server/Roles/RoleSystem.cs | 1 + .../Goobstation/Changeling/AbsorbedSystem.cs | 11 ++++++++++- .../Goobstation/changeling/abilities/changeling.ftl | 4 ++-- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index b063a68950..491493271f 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -57,7 +57,8 @@ using System.Numerics; using Content.Shared.Camera; using Robust.Shared.Timing; -using Content.Shared.Gravity; +using Content.Shared.Damage.Components; +using Content.Server.Gravity; namespace Content.Server.Changeling; @@ -357,8 +358,8 @@ public void AddDNA(EntityUid uid, ChangelingComponent comp, TransformData data, { if (comp.AbsorbedDNA.Count >= comp.MaxAbsorbedDNA) { - comp.AbsorbedDNA.RemoveAt(0); _popup.PopupEntity(Loc.GetString("changeling-sting-extract-max"), uid, uid); + return; } comp.AbsorbedDNA.Add(data); diff --git a/Content.Server/Roles/RoleSystem.cs b/Content.Server/Roles/RoleSystem.cs index f7a5177357..7b18485787 100644 --- a/Content.Server/Roles/RoleSystem.cs +++ b/Content.Server/Roles/RoleSystem.cs @@ -19,6 +19,7 @@ public override void Initialize() SubscribeAntagEvents(); SubscribeAntagEvents(); SubscribeAntagEvents(); + SubscribeAntagEvents(); } public string? MindGetBriefing(EntityUid? mindId) diff --git a/Content.Shared/Goobstation/Changeling/AbsorbedSystem.cs b/Content.Shared/Goobstation/Changeling/AbsorbedSystem.cs index f5d94cf7f8..0496dc63be 100644 --- a/Content.Shared/Goobstation/Changeling/AbsorbedSystem.cs +++ b/Content.Shared/Goobstation/Changeling/AbsorbedSystem.cs @@ -1,4 +1,5 @@ using Content.Shared.Examine; +using Content.Shared.Mobs; namespace Content.Shared.Changeling; @@ -9,10 +10,18 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnExamine); + SubscribeLocalEvent(OnMobStateChange); } - private void OnExamine(EntityUid uid, AbsorbedComponent comp, ref ExaminedEvent args) + private void OnExamine(Entity ent, ref ExaminedEvent args) { args.PushMarkup(Loc.GetString("changeling-absorb-onexamine")); } + + private void OnMobStateChange(Entity ent, ref MobStateChangedEvent args) + { + // in case one somehow manages to dehusk someone + if (args.NewMobState != MobState.Dead) + RemComp(ent); + } } diff --git a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl index f37b5bcb99..38e4724361 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl @@ -23,8 +23,8 @@ changeling-sting-fail-ling = Someone just tried to silently sting you! changeling-sting = You silently sting {CAPITALIZE(THE($target))} changeling-sting-fail-simplemob = You can't sting a lesser creature! -changeling-sting-extract-max = Disposed of the first stored DNA to free up space for new DNA -changeling-sting-extract-fail = Can't extract DNA, need another target +changeling-sting-extract-fail = Unable to extract DNA +changeling-sting-extract-max = Need to get rid of the stored DNA beforehand changeling-stasis-enter = You enter regenerative stasis changeling-stasis-enter-fail = Can't enter stasis! From 24c8b00955f86cd5e22b64152a42da9074a82334 Mon Sep 17 00:00:00 2001 From: username <113782077+whateverusername0@users.noreply.github.com> Date: Mon, 29 Jul 2024 07:40:47 +1000 Subject: [PATCH 09/20] drastic ling changes (#440) * gaming * real! * real 3! * fuck * great * ungreat * gnomes are here. * g * fuck * raaagh cleanup * fart * ghfghdfghfbvd * boobooboo --------- Co-authored-by: whateverusername0 --- .../Changeling/ChangelingSystem.cs | 19 +- .../Changeling/ChangelingSystem.Abilities.cs | 618 ++++++++++++++ .../Changeling/ChangelingSystem.cs | 786 ++++-------------- .../GameTicking/Rules/ChangelingRuleSystem.cs | 30 +- .../Components/ChangelingRuleComponent.cs | 3 +- .../Changeling/Changeling.Actions.cs | 13 +- .../Changeling/ChangelingComponent.cs | 61 +- .../Goobstation/Changeling/alerts/alerts.ftl | 4 + .../Changeling/popup/changeling.ftl | 3 + .../changeling/abilities/changeling.ftl | 2 + .../game-presets/preset-changeling.ftl | 2 +- .../changeling/store/changeling-catalog.ftl | 3 +- Resources/Prototypes/Alerts/alerts.yml | 2 + .../Changeling/Actions/changeling.yml | 33 +- .../Changeling/Alerts/changeling.yml | 24 +- .../Changeling/Objectives/changeling.yml | 11 + .../Goobstation/Changeling/radio_channels.yml | 7 + .../apex_predator.png | Bin 672 -> 0 bytes .../augmented_eyesight.png | Bin 701 -> 717 bytes .../chameleon_skin.png | Bin 689 -> 1554 bytes .../changeling_abilities.rsi/contort_body.png | Bin 502 -> 0 bytes .../changeling_abilities.rsi/last_resort.png | Bin 821 -> 0 bytes .../changeling_abilities.rsi/meta.json | 18 +- .../changeling_abilities.rsi/stasis_enter.png | Bin 747 -> 408 bytes .../changeling_abilities.rsi/stasis_exit.png | Bin 767 -> 558 bytes .../changeling_abilities.rsi/swap_forms.png | Bin 778 -> 0 bytes .../Changeling/changeling_biomass.rsi/0.png | Bin 0 -> 1356 bytes .../Changeling/changeling_biomass.rsi/1.png | Bin 0 -> 1084 bytes .../Changeling/changeling_biomass.rsi/10.png | Bin 0 -> 1389 bytes .../Changeling/changeling_biomass.rsi/11.png | Bin 0 -> 1387 bytes .../Changeling/changeling_biomass.rsi/12.png | Bin 0 -> 1397 bytes .../Changeling/changeling_biomass.rsi/13.png | Bin 0 -> 1401 bytes .../Changeling/changeling_biomass.rsi/14.png | Bin 0 -> 1393 bytes .../Changeling/changeling_biomass.rsi/15.png | Bin 0 -> 1396 bytes .../Changeling/changeling_biomass.rsi/16.png | Bin 0 -> 1363 bytes .../Changeling/changeling_biomass.rsi/2.png | Bin 0 -> 1106 bytes .../Changeling/changeling_biomass.rsi/3.png | Bin 0 -> 1131 bytes .../Changeling/changeling_biomass.rsi/4.png | Bin 0 -> 1140 bytes .../Changeling/changeling_biomass.rsi/5.png | Bin 0 -> 1191 bytes .../Changeling/changeling_biomass.rsi/6.png | Bin 0 -> 1225 bytes .../Changeling/changeling_biomass.rsi/7.png | Bin 0 -> 1252 bytes .../Changeling/changeling_biomass.rsi/8.png | Bin 0 -> 1282 bytes .../Changeling/changeling_biomass.rsi/9.png | Bin 0 -> 1318 bytes .../changeling_biomass.rsi/meta.json | 63 ++ .../Changeling/changeling_chemicals.rsi/0.png | Bin 1252 -> 861 bytes .../Changeling/changeling_chemicals.rsi/1.png | Bin 1254 -> 958 bytes .../changeling_chemicals.rsi/10.png | Bin 1389 -> 1009 bytes .../changeling_chemicals.rsi/11.png | Bin 1387 -> 1010 bytes .../changeling_chemicals.rsi/12.png | Bin 1397 -> 1024 bytes .../changeling_chemicals.rsi/13.png | Bin 1401 -> 1024 bytes .../changeling_chemicals.rsi/14.png | Bin 1393 -> 1021 bytes .../changeling_chemicals.rsi/15.png | Bin 1396 -> 1029 bytes .../changeling_chemicals.rsi/16.png | Bin 1363 -> 1033 bytes .../changeling_chemicals.rsi/17.png | Bin 0 -> 1031 bytes .../changeling_chemicals.rsi/18.png | Bin 0 -> 1029 bytes .../Changeling/changeling_chemicals.rsi/2.png | Bin 1276 -> 968 bytes .../Changeling/changeling_chemicals.rsi/3.png | Bin 1301 -> 980 bytes .../Changeling/changeling_chemicals.rsi/4.png | Bin 1323 -> 991 bytes .../Changeling/changeling_chemicals.rsi/5.png | Bin 1332 -> 998 bytes .../Changeling/changeling_chemicals.rsi/6.png | Bin 1348 -> 992 bytes .../Changeling/changeling_chemicals.rsi/7.png | Bin 1369 -> 1000 bytes .../Changeling/changeling_chemicals.rsi/8.png | Bin 1388 -> 1009 bytes .../Changeling/changeling_chemicals.rsi/9.png | Bin 1404 -> 1007 bytes .../changeling_chemicals.rsi/meta.json | 146 ++-- 64 files changed, 1054 insertions(+), 794 deletions(-) create mode 100644 Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs create mode 100644 Resources/Locale/en-US/Goobstation/Changeling/popup/changeling.ftl create mode 100644 Resources/Prototypes/Goobstation/Changeling/radio_channels.yml delete mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/apex_predator.png delete mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/contort_body.png delete mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/last_resort.png delete mode 100644 Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/swap_forms.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/0.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/1.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/10.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/11.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/12.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/13.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/14.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/15.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/16.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/2.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/3.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/4.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/5.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/6.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/7.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/8.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/9.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/meta.json create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/17.png create mode 100644 Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/18.png diff --git a/Content.Client/Goobstation/Changeling/ChangelingSystem.cs b/Content.Client/Goobstation/Changeling/ChangelingSystem.cs index e838809e92..6d8ee47793 100644 --- a/Content.Client/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Client/Goobstation/Changeling/ChangelingSystem.cs @@ -20,12 +20,23 @@ public override void Initialize() private void OnUpdateAlert(EntityUid uid, ChangelingComponent comp, ref UpdateAlertSpriteEvent args) { - if (args.Alert.AlertKey.AlertType != "Chemicals") - return; + var stateNormalized = 0f; - var chemicalsNormalised = (int) (comp.Chemicals / comp.MaxChemicals * 16); // hardcoded because uhh umm + // hardcoded because uhh umm i don't know. send help. + switch (args.Alert.AlertKey.AlertType) + { + case "ChangelingChemicals": + stateNormalized = (int) (comp.Chemicals / comp.MaxChemicals * 18); + break; + + case "ChangelingBiomass": + stateNormalized = (int) (comp.Biomass / comp.MaxBiomass * 16); + break; + default: + return; + } var sprite = args.SpriteViewEnt.Comp; - sprite.LayerSetState(AlertVisualLayers.Base, $"{chemicalsNormalised}"); + sprite.LayerSetState(AlertVisualLayers.Base, $"{stateNormalized}"); } private void GetChanglingIcon(Entity ent, ref GetStatusIconsEvent args) diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs new file mode 100644 index 0000000000..1feee55e81 --- /dev/null +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs @@ -0,0 +1,618 @@ +using Content.Shared.Changeling; +using Content.Shared.Chemistry.Components; +using Content.Shared.Cuffs.Components; +using Content.Shared.DoAfter; +using Content.Shared.FixedPoint; +using Content.Shared.IdentityManagement; +using Content.Shared.Mobs; +using Content.Shared.Store.Components; +using Content.Shared.Popups; +using Content.Shared.Damage; +using Robust.Shared.Prototypes; +using Content.Shared.Damage.Prototypes; +using Content.Server.Objectives.Components; +using Content.Server.Light.Components; +using Content.Shared.Eye.Blinding.Systems; +using Content.Shared.Eye.Blinding.Components; +using Content.Server.Flash.Components; +using Content.Shared.Movement.Pulling.Components; +using Content.Shared.Stealth.Components; +using Content.Shared.Damage.Components; +using Content.Server.Radio.Components; + +namespace Content.Server.Changeling; + +public sealed partial class ChangelingSystem : EntitySystem +{ + public void SubscribeAbilities() + { + SubscribeLocalEvent(OnOpenEvolutionMenu); + SubscribeLocalEvent(OnAbsorb); + SubscribeLocalEvent(OnAbsorbDoAfter); + SubscribeLocalEvent(OnStingExtractDNA); + SubscribeLocalEvent(OnTransformCycle); + SubscribeLocalEvent(OnTransform); + SubscribeLocalEvent(OnEnterStasis); + SubscribeLocalEvent(OnExitStasis); + + SubscribeLocalEvent(OnToggleArmblade); + SubscribeLocalEvent(OnCreateBoneShard); + SubscribeLocalEvent(OnToggleArmor); + SubscribeLocalEvent(OnToggleShield); + SubscribeLocalEvent(OnShriekDissonant); + SubscribeLocalEvent(OnShriekResonant); + SubscribeLocalEvent(OnToggleStrainedMuscles); + + SubscribeLocalEvent(OnStingBlind); + SubscribeLocalEvent(OnStingCryo); + SubscribeLocalEvent(OnStingLethargic); + SubscribeLocalEvent(OnStingMute); + SubscribeLocalEvent(OnStingTransform); + SubscribeLocalEvent(OnStingFakeArmblade); + + SubscribeLocalEvent(OnAnatomicPanacea); + SubscribeLocalEvent(OnAugmentedEyesight); + SubscribeLocalEvent(OnBiodegrade); + SubscribeLocalEvent(OnChameleonSkin); + SubscribeLocalEvent(OnEphedrineOverdose); + SubscribeLocalEvent(OnHealUltraSwag); + SubscribeLocalEvent(OnLastResort); + SubscribeLocalEvent(OnLesserForm); + SubscribeLocalEvent(OnSpacesuit); + SubscribeLocalEvent(OnHivemindAccess); + } + + #region Basic Abilities + + private void OnOpenEvolutionMenu(EntityUid uid, ChangelingComponent comp, ref OpenEvolutionMenuEvent args) + { + if (!TryComp(uid, out var store)) + return; + + _store.ToggleUi(uid, uid, store); + } + + private void OnAbsorb(EntityUid uid, ChangelingComponent comp, ref AbsorbDNAEvent args) + { + var target = args.Target; + + if (!IsIncapacitated(target)) + { + _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-incapacitated"), uid, uid); + return; + } + if (HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-absorbed"), uid, uid); + return; + } + if (!HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-unabsorbable"), uid, uid); + return; + } + + if (!TryUseAbility(uid, comp, args)) + return; + + var popupOthers = Loc.GetString("changeling-absorb-start", ("user", Identity.Entity(uid, EntityManager)), ("target", Identity.Entity(target, EntityManager))); + _popup.PopupEntity(popupOthers, uid, PopupType.LargeCaution); + PlayMeatySound(uid, comp); + var dargs = new DoAfterArgs(EntityManager, uid, TimeSpan.FromSeconds(15), new AbsorbDNADoAfterEvent(), uid, target) + { + DistanceThreshold = 1.5f, + BreakOnDamage = true, + BreakOnHandChange = false, + BreakOnMove = true, + BreakOnWeightlessMove = true, + AttemptFrequency = AttemptFrequency.StartAndEnd + }; + _doAfter.TryStartDoAfter(dargs); + } + public ProtoId AbsorbedDamageGroup = "Genetic"; + private void OnAbsorbDoAfter(EntityUid uid, ChangelingComponent comp, ref AbsorbDNADoAfterEvent args) + { + if (args.Args.Target == null) + return; + + var target = args.Args.Target.Value; + + if (args.Cancelled || !IsIncapacitated(target) || HasComp(target)) + return; + + PlayMeatySound(args.User, comp); + + UpdateBiomass(uid, comp, comp.MaxBiomass - comp.TotalAbsorbedEntities); + + var dmg = new DamageSpecifier(_proto.Index(AbsorbedDamageGroup), 200); + _damage.TryChangeDamage(target, dmg, false, false); + _blood.ChangeBloodReagent(target, "FerrochromicAcid"); + _blood.SpillAllSolutions(target); + + EnsureComp(target); + + var popup = Loc.GetString("changeling-absorb-end-self-ling"); + var bonusChemicals = 0f; + var bonusEvolutionPoints = 0f; + if (TryComp(target, out var targetComp)) + { + bonusChemicals += targetComp.MaxChemicals / 2; + bonusEvolutionPoints += 10; + comp.MaxBiomass += targetComp.MaxBiomass / 2; + } + else + { + popup = Loc.GetString("changeling-absorb-end-self"); + bonusChemicals += 10; + bonusEvolutionPoints += 2; + } + TryStealDNA(uid, target, comp, true); + comp.TotalAbsorbedEntities++; + + _popup.PopupEntity(popup, args.User, args.User); + comp.MaxChemicals += bonusChemicals; + + if (TryComp(args.User, out var store)) + { + _store.TryAddCurrency(new Dictionary { { "EvolutionPoint", bonusEvolutionPoints } }, args.User, store); + _store.UpdateUserInterface(args.User, args.User, store); + } + + if (_mind.TryGetMind(uid, out var mindId, out var mind)) + if (_mind.TryGetObjectiveComp(mindId, out var objective, mind)) + objective.Absorbed += 1; + } + + private void OnStingExtractDNA(EntityUid uid, ChangelingComponent comp, ref StingExtractDNAEvent args) + { + if (!TrySting(uid, comp, args, true)) + return; + + var target = args.Target; + if (!TryStealDNA(uid, target, comp, true)) + { + _popup.PopupEntity(Loc.GetString("changeling-sting-extract-fail"), uid, uid); + // royal cashback + comp.Chemicals += Comp(args.Action).ChemicalCost; + } + else _popup.PopupEntity(Loc.GetString("changeling-sting", ("target", Identity.Entity(target, EntityManager))), uid, uid); + } + + private void OnTransformCycle(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformCycleEvent args) + { + comp.AbsorbedDNAIndex += 1; + if (comp.AbsorbedDNAIndex >= comp.MaxAbsorbedDNA || comp.AbsorbedDNAIndex >= comp.AbsorbedDNA.Count) + comp.AbsorbedDNAIndex = 0; + + if (comp.AbsorbedDNA.Count == 0) + { + _popup.PopupEntity(Loc.GetString("changeling-transform-cycle-empty"), uid, uid); + return; + } + + var selected = comp.AbsorbedDNA.ToArray()[comp.AbsorbedDNAIndex]; + comp.SelectedForm = selected; + _popup.PopupEntity(Loc.GetString("changeling-transform-cycle", ("target", selected.Name)), uid, uid); + } + private void OnTransform(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryTransform(uid, comp)) + comp.Chemicals += Comp(args.Action).ChemicalCost; + } + + private void OnEnterStasis(EntityUid uid, ChangelingComponent comp, ref EnterStasisEvent args) + { + if (comp.IsInStasis || HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("changeling-stasis-enter-fail"), uid, uid); + return; + } + + if (!TryUseAbility(uid, comp, args)) + return; + + comp.Chemicals = 0f; + + if (_mobState.IsAlive(uid)) + { + // fake our death + var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", uid)); + _popup.PopupEntity(othersMessage, uid, Robust.Shared.Player.Filter.PvsExcept(uid), true); + + var selfMessage = Loc.GetString("changeling-stasis-enter"); + _popup.PopupEntity(selfMessage, uid, uid); + } + + if (!_mobState.IsDead(uid)) + _mobState.ChangeMobState(uid, MobState.Dead); + + comp.IsInStasis = true; + } + private void OnExitStasis(EntityUid uid, ChangelingComponent comp, ref ExitStasisEvent args) + { + if (!comp.IsInStasis) + { + _popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail"), uid, uid); + return; + } + if (HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail-dead"), uid, uid); + return; + } + + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryComp(uid, out var damageable)) + return; + + // heal of everything + _damage.SetAllDamage(uid, damageable, 0); + _mobState.ChangeMobState(uid, MobState.Alive); + _blood.TryModifyBloodLevel(uid, 1000); + _blood.TryModifyBleedAmount(uid, -1000); + + _popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), uid, uid); + + comp.IsInStasis = false; + } + + #endregion + + #region Combat Abilities + + private void OnToggleArmblade(EntityUid uid, ChangelingComponent comp, ref ToggleArmbladeEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryToggleItem(uid, ArmbladePrototype, comp)) + return; + + PlayMeatySound(uid, comp); + } + private void OnCreateBoneShard(EntityUid uid, ChangelingComponent comp, ref CreateBoneShardEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var star = Spawn(BoneShardPrototype, Transform(uid).Coordinates); + _hands.TryPickupAnyHand(uid, star); + + PlayMeatySound(uid, comp); + } + private void OnToggleArmor(EntityUid uid, ChangelingComponent comp, ref ToggleChitinousArmorEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryToggleItem(uid, ArmorPrototype, comp, "outerClothing") + || !TryToggleItem(uid, ArmorHelmetPrototype, comp, "head")) + { + _popup.PopupEntity(Loc.GetString("changeling-equip-armor-fail"), uid, uid); + comp.Chemicals += Comp(args.Action).ChemicalCost; + return; + } + + PlayMeatySound(uid, comp); + } + private void OnToggleShield(EntityUid uid, ChangelingComponent comp, ref ToggleOrganicShieldEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryToggleItem(uid, ShieldPrototype, comp)) + return; + + PlayMeatySound(uid, comp); + } + private void OnShriekDissonant(EntityUid uid, ChangelingComponent comp, ref ShriekDissonantEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + DoScreech(uid, comp); + + var pos = _transform.GetMapCoordinates(uid); + var power = comp.ShriekPower; + _emp.EmpPulse(pos, power, 5000f, power * 2); + } + private void OnShriekResonant(EntityUid uid, ChangelingComponent comp, ref ShriekResonantEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + DoScreech(uid, comp); + + var power = comp.ShriekPower; + _flash.FlashArea(uid, uid, power, power * 2f * 1000f); + + var lookup = _lookup.GetEntitiesInRange(uid, power); + var lights = GetEntityQuery(); + + foreach (var ent in lookup) + if (lights.HasComponent(ent)) + _light.TryDestroyBulb(ent); + } + private void OnToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp, ref ToggleStrainedMusclesEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + ToggleStrainedMuscles(uid, comp); + } + private void ToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp) + { + if (!comp.StrainedMusclesActive) + { + _speed.ChangeBaseSpeed(uid, 125f, 150f, 1f); + _popup.PopupEntity(Loc.GetString("changeling-muscles-start"), uid, uid); + comp.StrainedMusclesActive = true; + } + else + { + _speed.ChangeBaseSpeed(uid, 100f, 100f, 1f); + _popup.PopupEntity(Loc.GetString("changeling-muscles-end"), uid, uid); + comp.StrainedMusclesActive = false; + } + + PlayMeatySound(uid, comp); + } + + #endregion + + #region Stings + + private void OnStingBlind(EntityUid uid, ChangelingComponent comp, ref StingBlindEvent args) + { + if (!TrySting(uid, comp, args)) + return; + + var target = args.Target; + if (!TryComp(target, out var blindable) || blindable.IsBlind) + return; + + _blindable.AdjustEyeDamage((target, blindable), 2); + var timeSpan = TimeSpan.FromSeconds(5f); + _statusEffect.TryAddStatusEffect(target, TemporaryBlindnessSystem.BlindingStatusEffect, timeSpan, false, TemporaryBlindnessSystem.BlindingStatusEffect); + } + private void OnStingCryo(EntityUid uid, ChangelingComponent comp, ref StingCryoEvent args) + { + var reagents = new List<(string, FixedPoint2)>() + { + ("Fresium", 20f), + ("ChloralHydrate", 10f) + }; + + if (!TryReagentSting(uid, comp, args, reagents)) + return; + } + private void OnStingLethargic(EntityUid uid, ChangelingComponent comp, ref StingLethargicEvent args) + { + var reagents = new List<(string, FixedPoint2)>() + { + ("Impedrezene", 10f), + ("MuteToxin", 5f) + }; + + if (!TryReagentSting(uid, comp, args, reagents)) + return; + } + private void OnStingMute(EntityUid uid, ChangelingComponent comp, ref StingMuteEvent args) + { + var reagents = new List<(string, FixedPoint2)>() + { + ("MuteToxin", 15f) + }; + + if (!TryReagentSting(uid, comp, args, reagents)) + return; + } + private void OnStingTransform(EntityUid uid, ChangelingComponent comp, ref StingTransformEvent args) + { + if (!TrySting(uid, comp, args, true)) + return; + + var target = args.Target; + if (!TryTransform(target, comp, true, true)) + comp.Chemicals += Comp(args.Action).ChemicalCost; + } + private void OnStingFakeArmblade(EntityUid uid, ChangelingComponent comp, ref StingFakeArmbladeEvent args) + { + if (!TrySting(uid, comp, args)) + return; + + var target = args.Target; + var fakeArmblade = EntityManager.SpawnEntity(FakeArmbladePrototype, Transform(target).Coordinates); + if (!_hands.TryPickupAnyHand(target, fakeArmblade)) + { + QueueDel(fakeArmblade); + comp.Chemicals += Comp(args.Action).ChemicalCost; + _popup.PopupEntity(Loc.GetString("changeling-sting-fail-simplemob"), uid, uid); + return; + } + + PlayMeatySound(target, comp); + } + + #endregion + + #region Utilities + + public void OnAnatomicPanacea(EntityUid uid, ChangelingComponent comp, ref ActionAnatomicPanaceaEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var reagents = new List<(string, FixedPoint2)>() + { + ("Diphenhydramine", 5f), + ("Arithrazine", 5f), + ("Ethylredoxrazine", 5f) + }; + if (TryInjectReagents(uid, reagents)) + _popup.PopupEntity(Loc.GetString("changeling-panacea"), uid, uid); + else return; + PlayMeatySound(uid, comp); + } + public void OnAugmentedEyesight(EntityUid uid, ChangelingComponent comp, ref ActionAugmentedEyesightEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("changeling-passive-active"), uid, uid); + return; + } + + EnsureComp(uid); + _popup.PopupEntity(Loc.GetString("changeling-passive-activate"), uid, uid); + } + public void OnBiodegrade(EntityUid uid, ChangelingComponent comp, ref ActionBiodegradeEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (TryComp(uid, out var cuffs) && cuffs.Container.ContainedEntities.Count > 0) + { + var cuff = cuffs.LastAddedCuffs; + + _cuffs.Uncuff(uid, cuffs.LastAddedCuffs, cuff); + QueueDel(cuff); + } + + var soln = new Solution(); + soln.AddReagent("PolytrinicAcid", 10f); + + if (_pull.IsPulled(uid)) + { + var puller = Comp(uid).Puller; + if (puller != null) + { + _puddle.TrySplashSpillAt((EntityUid) puller, Transform((EntityUid) puller).Coordinates, soln, out _); + return; + } + } + _puddle.TrySplashSpillAt(uid, Transform(uid).Coordinates, soln, out _); + } + public void OnChameleonSkin(EntityUid uid, ChangelingComponent comp, ref ActionChameleonSkinEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (HasComp(uid) && HasComp(uid)) + { + RemComp(uid); + RemComp(uid); + _popup.PopupEntity(Loc.GetString("changeling-chameleon-end"), uid, uid); + return; + } + + EnsureComp(uid); + EnsureComp(uid); + _popup.PopupEntity(Loc.GetString("changeling-chameleon-start"), uid, uid); + } + public void OnEphedrineOverdose(EntityUid uid, ChangelingComponent comp, ref ActionEphedrineOverdoseEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var stam = EnsureComp(uid); + stam.StaminaDamage = 0; + + var reagents = new List<(string, FixedPoint2)>() + { + ("Synaptizine", 5f) + }; + if (TryInjectReagents(uid, reagents)) + _popup.PopupEntity(Loc.GetString("changeling-inject"), uid, uid); + else + { + _popup.PopupEntity(Loc.GetString("changeling-inject-fail"), uid, uid); + return; + } + } + // john space made me do this + public void OnHealUltraSwag(EntityUid uid, ChangelingComponent comp, ref ActionFleshmendEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var reagents = new List<(string, FixedPoint2)>() + { + ("Ichor", 10f), + ("TranexamicAcid", 5f) + }; + if (TryInjectReagents(uid, reagents)) + _popup.PopupEntity(Loc.GetString("changeling-fleshmend"), uid, uid); + else return; + PlayMeatySound(uid, comp); + } + public void OnLastResort(EntityUid uid, ChangelingComponent comp, ref ActionLastResortEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + // todo: implement + } + public void OnLesserForm(EntityUid uid, ChangelingComponent comp, ref ActionLesserFormEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + var newUid = TransformEntity(uid, protoId: "MobMonkey", comp: comp); + if (newUid == null) + { + comp.Chemicals += Comp(args.Action).ChemicalCost; + return; + } + + PlayMeatySound((EntityUid) newUid, comp); + var loc = Loc.GetString("changeling-transform-others", ("user", Identity.Entity((EntityUid) newUid, EntityManager))); + _popup.PopupEntity(loc, (EntityUid) newUid, PopupType.LargeCaution); + + comp.IsInLesserForm = true; + } + public void OnSpacesuit(EntityUid uid, ChangelingComponent comp, ref ActionSpacesuitEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (!TryToggleItem(uid, SpacesuitPrototype, comp, "outerClothing") + || !TryToggleItem(uid, SpacesuitHelmetPrototype, comp, "head")) + { + _popup.PopupEntity(Loc.GetString("changeling-equip-armor-fail"), uid, uid); + comp.Chemicals += Comp(args.Action).ChemicalCost; + return; + } + + PlayMeatySound(uid, comp); + } + public void OnHivemindAccess(EntityUid uid, ChangelingComponent comp, ref ActionHivemindAccessEvent args) + { + if (!TryUseAbility(uid, comp, args)) + return; + + if (HasComp(uid)) + { + _popup.PopupEntity(Loc.GetString("changeling-passive-active"), uid, uid); + return; + } + + EnsureComp(uid); + var reciever = EnsureComp(uid); + var transmitter = EnsureComp(uid); + var radio = EnsureComp(uid); + radio.Channels = new() { "Hivemind" }; + transmitter.Channels = new() { "Hivemind" }; + + _popup.PopupEntity(Loc.GetString("changeling-hivemind-start"), uid, uid); + } + + #endregion +} diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index 491493271f..c648bbce82 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -8,7 +8,6 @@ using Content.Shared.Changeling; using Content.Shared.Chemistry.Components; using Content.Shared.Cuffs.Components; -using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.Humanoid; using Content.Shared.IdentityManagement; @@ -22,7 +21,6 @@ using Content.Shared.Popups; using Content.Shared.Damage; using Robust.Shared.Prototypes; -using Content.Shared.Damage.Prototypes; using Content.Server.Body.Systems; using Content.Shared.Actions; using Content.Shared.Polymorph; @@ -41,17 +39,12 @@ using Content.Shared.Mind; using Content.Shared.Damage.Components; using Content.Server.Objectives.Components; -using Content.Server.Light.Components; using Content.Server.Light.EntitySystems; using Content.Shared.Eye.Blinding.Systems; -using Content.Shared.Eye.Blinding.Components; using Content.Shared.StatusEffect; -using Content.Server.Flash.Components; using Content.Shared.Movement.Pulling.Systems; -using Content.Shared.Movement.Pulling.Components; using Content.Shared.Cuffs; using Content.Shared.Fluids; -using Content.Shared.Stealth.Components; using Content.Shared.Revolutionary.Components; using Robust.Shared.Player; using System.Numerics; @@ -59,6 +52,10 @@ using Robust.Shared.Timing; using Content.Shared.Damage.Components; using Content.Server.Gravity; +using Content.Shared.Mobs.Components; +using Content.Server.Stunnable; +using Content.Shared.Jittering; +using System.Linq; namespace Content.Server.Changeling; @@ -74,7 +71,6 @@ public sealed partial class ChangelingSystem : EntitySystem [Dependency] private readonly PolymorphSystem _polymorph = default!; [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly PopupSystem _popup = default!; - [Dependency] private readonly DoAfterSystem _doAfter = default!; [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly IPrototypeManager _proto = default!; @@ -84,7 +80,6 @@ public sealed partial class ChangelingSystem : EntitySystem [Dependency] private readonly MetaDataSystem _metaData = default!; [Dependency] private readonly HumanoidAppearanceSystem _humanoid = default!; [Dependency] private readonly SharedSolutionContainerSystem _solution = default!; - [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly FlashSystem _flash = default!; [Dependency] private readonly EmpSystem _emp = default!; @@ -92,19 +87,18 @@ public sealed partial class ChangelingSystem : EntitySystem [Dependency] private readonly PoweredLightSystem _light = default!; [Dependency] private readonly ISharedPlayerManager _player = default!; [Dependency] private readonly SharedCameraRecoilSystem _recoil = default!; - [Dependency] private readonly SharedHandsSystem _hands = default!; [Dependency] private readonly InventorySystem _inventory = default!; - [Dependency] private readonly MovementSpeedModifierSystem _speed = default!; [Dependency] private readonly StaminaSystem _stamina = default!; - + [Dependency] private readonly GravitySystem _gravity = default!; [Dependency] private readonly BlindableSystem _blindable = default!; [Dependency] private readonly StatusEffectsSystem _statusEffect = default!; - [Dependency] private readonly PullingSystem _pull = default!; [Dependency] private readonly SharedCuffableSystem _cuffs = default!; [Dependency] private readonly SharedPuddleSystem _puddle = default!; + [Dependency] private readonly StunSystem _stun = default!; + [Dependency] private readonly SharedJitteringSystem _jitter = default!; public EntProtoId ArmbladePrototype = "ArmBladeChangeling"; public EntProtoId FakeArmbladePrototype = "FakeArmBladeChangeling"; @@ -124,41 +118,10 @@ public override void Initialize() SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnMobStateChange); + SubscribeLocalEvent(OnDamageChange); + SubscribeLocalEvent(OnComponentRemove); - SubscribeLocalEvent(OnOpenEvolutionMenu); - SubscribeLocalEvent(OnAbsorb); - SubscribeLocalEvent(OnAbsorbDoAfter); - SubscribeLocalEvent(OnStingExtractDNA); - SubscribeLocalEvent(OnTransformCycle); - SubscribeLocalEvent(OnTransform); - SubscribeLocalEvent(OnEnterStasis); - SubscribeLocalEvent(OnExitStasis); - - SubscribeLocalEvent(OnToggleArmblade); - SubscribeLocalEvent(OnCreateBoneShard); - SubscribeLocalEvent(OnToggleArmor); - SubscribeLocalEvent(OnToggleShield); - SubscribeLocalEvent(OnShriekDissonant); - SubscribeLocalEvent(OnShriekResonant); - SubscribeLocalEvent(OnToggleStrainedMuscles); - - SubscribeLocalEvent(OnStingBlind); - SubscribeLocalEvent(OnStingCryo); - SubscribeLocalEvent(OnStingLethargic); - SubscribeLocalEvent(OnStingMute); - SubscribeLocalEvent(OnStingTransform); - SubscribeLocalEvent(OnStingFakeArmblade); - - SubscribeLocalEvent(OnAnatomicPanacea); - SubscribeLocalEvent(OnAugmentedEyesight); - SubscribeLocalEvent(OnBiodegrade); - SubscribeLocalEvent(OnChameleonSkin); - SubscribeLocalEvent(OnEphedrineOverdose); - SubscribeLocalEvent(OnHealUltraSwag); - SubscribeLocalEvent(OnLastResort); - SubscribeLocalEvent(OnLesserForm); - SubscribeLocalEvent(OnSpacesuit); - SubscribeLocalEvent(OnHivemindAccess); + SubscribeAbilities(); } public override void Update(float frameTime) @@ -172,10 +135,10 @@ public override void Update(float frameTime) { var uid = comp.Owner; - if (_timing.CurTime < comp.RegenTime) + if (_timing.CurTime < comp.UpdateTimer) continue; - comp.RegenTime = _timing.CurTime + TimeSpan.FromSeconds(comp.RegenCooldown); + comp.UpdateTimer = _timing.CurTime + TimeSpan.FromSeconds(comp.UpdateCooldown); Cycle(uid, comp); } @@ -184,6 +147,80 @@ public void Cycle(EntityUid uid, ChangelingComponent comp) { UpdateChemicals(uid, comp); + comp.BiomassUpdateTimer += 1; + if (comp.BiomassUpdateTimer >= comp.BiomassUpdateCooldown) + { + comp.BiomassUpdateTimer = 0; + UpdateBiomass(uid, comp); + } + + UpdateAbilities(uid, comp); + } + + private void UpdateChemicals(EntityUid uid, ChangelingComponent comp, float? amount = null) + { + var chemicals = comp.Chemicals; + // either amount or regen + chemicals += amount ?? 1 + comp.BonusChemicalRegen; + comp.Chemicals = Math.Clamp(chemicals, 0, comp.MaxChemicals); + Dirty(uid, comp); + _alerts.ShowAlert(uid, "ChangelingChemicals"); + } + private void UpdateBiomass(EntityUid uid, ChangelingComponent comp, float? amount = null) + { + comp.Biomass += amount ?? -1; + comp.Biomass = Math.Clamp(comp.Biomass, 0, comp.MaxBiomass); + Dirty(uid, comp); + _alerts.ShowAlert(uid, "ChangelingBiomass"); + + var random = (int) _rand.Next(1, 3); + + if (comp.Biomass <= 0) + // game over, man + _damage.TryChangeDamage(uid, new DamageSpecifier(_proto.Index(AbsorbedDamageGroup), 50), true); + + if (comp.Biomass <= comp.MaxBiomass / 10) + { + // THE FUNNY ITCH IS REAL!! + comp.BonusChemicalRegen = 3f; + _popup.PopupEntity(Loc.GetString("popup-changeling-biomass-deficit-high"), uid, uid, PopupType.LargeCaution); + _jitter.DoJitter(uid, TimeSpan.FromSeconds(comp.BiomassUpdateCooldown), true, amplitude: 5, frequency: 10); + } + else if (comp.Biomass <= comp.MaxBiomass / 3) + { + // vomit blood + if (random == 1) + { + if (TryComp(uid, out var status)) + _stun.TrySlowdown(uid, TimeSpan.FromSeconds(1.5f), true, 0.5f, 0.5f, status); + + var solution = new Solution(); + + var vomitAmount = 15f; + _blood.TryModifyBloodLevel(uid, -vomitAmount); + solution.AddReagent("Blood", vomitAmount); + + _puddle.TrySplashSpillAt(uid, Transform(uid).Coordinates, solution, out _); + + _popup.PopupEntity(Loc.GetString("disease-vomit", ("person", Identity.Entity(uid, EntityManager))), uid); + } + + // the funny itch is not real + if (random == 3) + { + _popup.PopupEntity(Loc.GetString("popup-changeling-biomass-deficit-medium"), uid, uid, PopupType.MediumCaution); + _jitter.DoJitter(uid, TimeSpan.FromSeconds(.5f), true, amplitude: 5, frequency: 10); + } + } + else if (comp.Biomass <= comp.MaxBiomass / 2 && random == 3) + { + if (random == 1) + _popup.PopupEntity(Loc.GetString("popup-changeling-biomass-deficit-low"), uid, uid, PopupType.SmallCaution); + } + else comp.BonusChemicalRegen = 0f; + } + private void UpdateAbilities(EntityUid uid, ChangelingComponent comp) + { if (comp.StrainedMusclesActive) { var stamina = EnsureComp(uid); @@ -237,19 +274,6 @@ public bool IsIncapacitated(EntityUid uid) return false; } - private void UpdateChemicals(EntityUid uid, ChangelingComponent comp, float? amount = null) - { - var chemicals = comp.Chemicals; - - chemicals += amount ?? 1 /*regen*/; - - comp.Chemicals = Math.Clamp(chemicals, 0, comp.MaxChemicals); - - Dirty(uid, comp); - - _alerts.ShowAlert(uid, "Chemicals"); - } - public bool TryUseAbility(EntityUid uid, ChangelingComponent comp, BaseActionEvent action) { if (action.Handled) @@ -258,14 +282,19 @@ public bool TryUseAbility(EntityUid uid, ChangelingComponent comp, BaseActionEve if (!TryComp(action.Action, out var lingAction)) return false; - if (!lingAction.UseWhileLesserForm && comp.IsInLesserForm) + if (comp.Biomass < 1 && lingAction.RequireBiomass) + { + _popup.PopupEntity(Loc.GetString("changeling-biomass-deficit"), uid, uid); + return false; + } + + if (!lingAction.UseInLesserForm && comp.IsInLesserForm) { _popup.PopupEntity(Loc.GetString("changeling-action-fail-lesserform"), uid, uid); return false; } - var price = lingAction.ChemicalCost; - if (comp.Chemicals < price) + if (comp.Chemicals < lingAction.ChemicalCost) { _popup.PopupEntity(Loc.GetString("changeling-chemicals-deficit"), uid, uid); return false; @@ -278,7 +307,8 @@ public bool TryUseAbility(EntityUid uid, ChangelingComponent comp, BaseActionEve return false; } - UpdateChemicals(uid, comp, -price); + UpdateChemicals(uid, comp, -lingAction.ChemicalCost); + UpdateBiomass(uid, comp, -lingAction.BiomassCost); action.Handled = true; @@ -354,22 +384,6 @@ public bool TryToggleItem(EntityUid uid, EntProtoId proto, ref EntityUid? outIte return true; } - public void AddDNA(EntityUid uid, ChangelingComponent comp, TransformData data, bool countObjective = false) - { - if (comp.AbsorbedDNA.Count >= comp.MaxAbsorbedDNA) - { - _popup.PopupEntity(Loc.GetString("changeling-sting-extract-max"), uid, uid); - return; - } - comp.AbsorbedDNA.Add(data); - - if (countObjective) - { - if (_mind.TryGetMind(uid, out var mindId, out var mind)) - if (_mind.TryGetObjectiveComp(mindId, out var objective, mind)) - objective.DNAStolen += 1; - } - } public bool TryStealDNA(EntityUid uid, EntityUid target, ChangelingComponent comp, bool countObjective = false) { if (!TryComp(target, out var appearance) @@ -394,7 +408,18 @@ public bool TryStealDNA(EntityUid uid, EntityUid target, ChangelingComponent com if (fingerprint.Fingerprint != null) data.Fingerprint = fingerprint.Fingerprint; - AddDNA(uid, comp, data, countObjective); + if (comp.AbsorbedDNA.Count >= comp.MaxAbsorbedDNA) + _popup.PopupEntity(Loc.GetString("changeling-sting-extract-max"), uid, uid); + else comp.AbsorbedDNA.Add(data); + + if (countObjective + && _mind.TryGetMind(uid, out var mindId, out var mind) + && _mind.TryGetObjectiveComp(mindId, out var objective, mind)) + { + objective.DNAStolen += 1; + } + + comp.TotalStolenDNA++; return true; } @@ -404,7 +429,12 @@ public bool TryStealDNA(EntityUid uid, EntityUid target, ChangelingComponent com var newComp = EnsureComp(target); newComp.AbsorbedDNA = comp.AbsorbedDNA; newComp.AbsorbedDNAIndex = comp.AbsorbedDNAIndex; + newComp.Chemicals = comp.Chemicals; + newComp.MaxChemicals = comp.MaxChemicals; + + newComp.Biomass = comp.Biomass; + newComp.MaxBiomass = comp.MaxBiomass; newComp.IsInLesserForm = comp.IsInLesserForm; newComp.CurrentForm = comp.CurrentForm; @@ -485,6 +515,12 @@ public bool TryStealDNA(EntityUid uid, EntityUid target, ChangelingComponent com } public bool TryTransform(EntityUid target, ChangelingComponent comp, bool sting = false, bool persistentDna = false) { + if (HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("changeling-transform-fail-absorbed"), target, target); + return false; + } + var data = comp.SelectedForm; if (data == null) @@ -516,15 +552,17 @@ public bool TryTransform(EntityUid target, ChangelingComponent comp, bool sting public void RemoveAllChangelingEquipment(EntityUid target, ChangelingComponent comp) { - // yanderedev type shit - EntityManager.DeleteEntity(comp.ShieldEntity); - EntityManager.DeleteEntity(comp.ArmbladeEntity); - EntityManager.DeleteEntity(comp.ArmorEntity); - EntityManager.DeleteEntity(comp.ArmorHelmetEntity); - EntityManager.DeleteEntity(comp.SpacesuitEntity); - EntityManager.DeleteEntity(comp.SpacesuitHelmetEntity); + // check if there's no entities or all entities are null + if (comp.Equipment.Values.Count == 0 + || comp.Equipment.Values.All(ent => ent == null ? true : false)) + return; + + foreach (var equip in comp.Equipment.Values) + QueueDel(equip); + PlayMeatySound(target, comp); } + #endregion #region Event Handlers @@ -538,6 +576,14 @@ private void OnStartup(EntityUid uid, ChangelingComponent comp, ref ComponentSta // add actions foreach (var actionId in comp.BaseChangelingActions) _actions.AddAction(uid, actionId); + + // making sure things are right in this world + comp.Chemicals = comp.MaxChemicals; + comp.Biomass = comp.MaxBiomass; + + // show alerts + UpdateChemicals(uid, comp, 0); + UpdateBiomass(uid, comp, 0); } private void OnMobStateChange(EntityUid uid, ChangelingComponent comp, ref MobStateChangedEvent args) @@ -546,569 +592,25 @@ private void OnMobStateChange(EntityUid uid, ChangelingComponent comp, ref MobSt RemoveAllChangelingEquipment(uid, comp); } - #endregion - - #region Basic Abilities - - private void OnOpenEvolutionMenu(EntityUid uid, ChangelingComponent comp, ref OpenEvolutionMenuEvent args) - { - if (!TryComp(uid, out var store)) - return; - - _store.ToggleUi(uid, uid, store); - } - - private void OnAbsorb(EntityUid uid, ChangelingComponent comp, ref AbsorbDNAEvent args) - { - var target = args.Target; - - if (!IsIncapacitated(target)) - { - _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-incapacitated"), uid, uid); - return; - } - if (HasComp(target)) - { - _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-absorbed"), uid, uid); - return; - } - if (!HasComp(target)) - { - _popup.PopupEntity(Loc.GetString("changeling-absorb-fail-unabsorbable"), uid, uid); - return; - } - - if (!TryUseAbility(uid, comp, args)) - return; - - var popupOthers = Loc.GetString("changeling-absorb-start", ("user", Identity.Entity(uid, EntityManager)), ("target", Identity.Entity(target, EntityManager))); - _popup.PopupEntity(popupOthers, uid, PopupType.LargeCaution); - PlayMeatySound(uid, comp); - var dargs = new DoAfterArgs(EntityManager, uid, TimeSpan.FromSeconds(15), new AbsorbDNADoAfterEvent(), uid, target) - { - DistanceThreshold = 1.5f, - BreakOnDamage = true, - BreakOnHandChange = false, - BreakOnMove = true, - BreakOnWeightlessMove = true, - AttemptFrequency = AttemptFrequency.StartAndEnd - }; - _doAfter.TryStartDoAfter(dargs); - } - public ProtoId AbsorbedDamageGroup = "Genetic"; - private void OnAbsorbDoAfter(EntityUid uid, ChangelingComponent comp, ref AbsorbDNADoAfterEvent args) - { - if (args.Args.Target == null) - return; - - var target = args.Args.Target.Value; - - PlayMeatySound(args.User, comp); - - if (args.Cancelled || !IsIncapacitated(target) || HasComp(target)) - return; - - var dmg = new DamageSpecifier(_proto.Index(AbsorbedDamageGroup), 200); - _damage.TryChangeDamage(target, dmg, false, false); - _blood.ChangeBloodReagent(target, "FerrochromicAcid"); - _blood.SpillAllSolutions(target); - - EnsureComp(target); - - var popup = Loc.GetString("changeling-absorb-end-self-ling"); - var bonusChemicals = 0; - var bonusEvolutionPoints = 0; - if (HasComp(target)) - { - bonusChemicals += 60; - bonusEvolutionPoints += 10; - } - else - { - popup = Loc.GetString("changeling-absorb-end-self", ("target", Identity.Entity(target, EntityManager))); - bonusChemicals += 10; - bonusEvolutionPoints += 2; - } - TryStealDNA(uid, target, comp, true); - comp.TotalAbsorbedEntities++; - comp.TotalStolenDNA++; - - _popup.PopupEntity(popup, args.User, args.User); - comp.MaxChemicals += bonusChemicals; - - if (TryComp(args.User, out var store)) - { - _store.TryAddCurrency(new Dictionary { { "EvolutionPoint", bonusEvolutionPoints } }, args.User, store); - _store.UpdateUserInterface(args.User, args.User, store); - } - - if (_mind.TryGetMind(uid, out var mindId, out var mind)) - if (_mind.TryGetObjectiveComp(mindId, out var objective, mind)) - objective.Absorbed += 1; - } - - private void OnStingExtractDNA(EntityUid uid, ChangelingComponent comp, ref StingExtractDNAEvent args) - { - if (!TrySting(uid, comp, args, true)) - return; - - var target = args.Target; - if (!TryStealDNA(uid, target, comp, true)) - { - _popup.PopupEntity(Loc.GetString("changeling-sting-extract-fail"), uid, uid); - // royal cashback - comp.Chemicals += Comp(args.Action).ChemicalCost; - } - else _popup.PopupEntity(Loc.GetString("changeling-sting", ("target", Identity.Entity(target, EntityManager))), uid, uid); - } - - private void OnTransformCycle(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformCycleEvent args) - { - comp.AbsorbedDNAIndex += 1; - if (comp.AbsorbedDNAIndex >= comp.MaxAbsorbedDNA || comp.AbsorbedDNAIndex >= comp.AbsorbedDNA.Count) - comp.AbsorbedDNAIndex = 0; - - if (comp.AbsorbedDNA.Count == 0) - { - _popup.PopupEntity(Loc.GetString("changeling-transform-cycle-empty"), uid, uid); - return; - } - - var selected = comp.AbsorbedDNA.ToArray()[comp.AbsorbedDNAIndex]; - comp.SelectedForm = selected; - _popup.PopupEntity(Loc.GetString("changeling-transform-cycle", ("target", selected.Name)), uid, uid); - } - private void OnTransform(EntityUid uid, ChangelingComponent comp, ref ChangelingTransformEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (!TryTransform(uid, comp)) - comp.Chemicals += Comp(args.Action).ChemicalCost; - } - - private void OnEnterStasis(EntityUid uid, ChangelingComponent comp, ref EnterStasisEvent args) - { - if (comp.IsInStasis || HasComp(uid)) - { - _popup.PopupEntity(Loc.GetString("changeling-stasis-enter-fail"), uid, uid); - return; - } - - if (!TryUseAbility(uid, comp, args)) - return; - - comp.Chemicals = 0f; - - if (_mobState.IsAlive(uid)) - { - // fake our death - var othersMessage = Loc.GetString("suicide-command-default-text-others", ("name", uid)); - _popup.PopupEntity(othersMessage, uid, Robust.Shared.Player.Filter.PvsExcept(uid), true); - - var selfMessage = Loc.GetString("changeling-stasis-enter"); - _popup.PopupEntity(selfMessage, uid, uid); - } - - if (!_mobState.IsDead(uid)) - _mobState.ChangeMobState(uid, MobState.Dead); - - comp.IsInStasis = true; - } - private void OnExitStasis(EntityUid uid, ChangelingComponent comp, ref ExitStasisEvent args) - { - if (!comp.IsInStasis) - { - _popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail"), uid, uid); - return; - } - if (HasComp(uid)) - { - _popup.PopupEntity(Loc.GetString("changeling-stasis-exit-fail-dead"), uid, uid); - return; - } - - if (!TryUseAbility(uid, comp, args)) - return; - - if (!TryComp(uid, out var damageable)) - return; - - // heal of everything - _damage.SetAllDamage(uid, damageable, 0); - _mobState.ChangeMobState(uid, MobState.Alive); - _blood.TryModifyBloodLevel(uid, 1000); - _blood.TryModifyBleedAmount(uid, -1000); - - _popup.PopupEntity(Loc.GetString("changeling-stasis-exit"), uid, uid); - - comp.IsInStasis = false; - } - - #endregion - - #region Combat Abilities - - private void OnToggleArmblade(EntityUid uid, ChangelingComponent comp, ref ToggleArmbladeEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (!TryToggleItem(uid, ArmbladePrototype, ref comp.ArmbladeEntity)) - return; - - if (comp.ArmbladeEntity != null) - _popup.PopupEntity(Loc.GetString("changeling-armblade-start"), uid, uid); - else - _popup.PopupEntity(Loc.GetString("changeling-hand-transform-end"), uid, uid); - - PlayMeatySound(uid, comp); - } - private void OnCreateBoneShard(EntityUid uid, ChangelingComponent comp, ref CreateBoneShardEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - var star = EntityManager.SpawnEntity(BoneShardPrototype, Transform(uid).Coordinates); - _hands.TryPickupAnyHand(uid, star); - - PlayMeatySound(uid, comp); - } - private void OnToggleArmor(EntityUid uid, ChangelingComponent comp, ref ToggleChitinousArmorEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (!TryToggleItem(uid, ArmorPrototype, ref comp.ArmorEntity, "outerClothing") - || !TryToggleItem(uid, ArmorHelmetPrototype, ref comp.ArmorHelmetEntity, "head")) - { - _popup.PopupEntity(Loc.GetString("changeling-equip-armor-fail"), uid, uid); - return; - } - - if (comp.ArmorEntity != null) - _popup.PopupEntity(Loc.GetString("changeling-equip-armor-start"), uid, uid); - else - _popup.PopupEntity(Loc.GetString("changeling-equip-end"), uid, uid); - - PlayMeatySound(uid, comp); - } - private void OnToggleShield(EntityUid uid, ChangelingComponent comp, ref ToggleOrganicShieldEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (!TryToggleItem(uid, ShieldPrototype, ref comp.ShieldEntity)) - return; - - if (comp.ShieldEntity != null) - _popup.PopupEntity(Loc.GetString("changeling-shield-start"), uid, uid); - else - _popup.PopupEntity(Loc.GetString("changeling-hand-transform-end"), uid, uid); - - PlayMeatySound(uid, comp); - } - private void OnShriekDissonant(EntityUid uid, ChangelingComponent comp, ref ShriekDissonantEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - DoScreech(uid, comp); - - var pos = _transform.GetMapCoordinates(uid); - var power = comp.ShriekPower; - _emp.EmpPulse(pos, power, 5000f, power * 2); - } - private void OnShriekResonant(EntityUid uid, ChangelingComponent comp, ref ShriekResonantEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - DoScreech(uid, comp); - - var power = comp.ShriekPower; - _flash.FlashArea(uid, uid, power, power * 2f * 1000f); - - var lookup = _lookup.GetEntitiesInRange(uid, power); - var lights = GetEntityQuery(); - - foreach (var ent in lookup) - if (lights.HasComponent(ent)) - _light.TryDestroyBulb(ent); - } - private void OnToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp, ref ToggleStrainedMusclesEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - ToggleStrainedMuscles(uid, comp); - } - private void ToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp) - { - if (!comp.StrainedMusclesActive) - { - _speed.ChangeBaseSpeed(uid, 125f, 150f, 1f); - _popup.PopupEntity(Loc.GetString("changeling-muscles-start"), uid, uid); - comp.StrainedMusclesActive = true; - } - else - { - _speed.ChangeBaseSpeed(uid, 100f, 100f, 1f); - _popup.PopupEntity(Loc.GetString("changeling-muscles-end"), uid, uid); - comp.StrainedMusclesActive = false; - } - - PlayMeatySound(uid, comp); - } - - #endregion - - #region Stings - - private void OnStingBlind(EntityUid uid, ChangelingComponent comp, ref StingBlindEvent args) + private void OnDamageChange(Entity ent, ref DamageChangedEvent args) { - if (!TrySting(uid, comp, args)) - return; + var target = args.Damageable; - var target = args.Target; - if (!TryComp(target, out var blindable) || blindable.IsBlind) + if (!TryComp(ent, out var mobState)) return; - _blindable.AdjustEyeDamage((target, blindable), 5); - var timeSpan = TimeSpan.FromSeconds(5f); - _statusEffect.TryAddStatusEffect(target, TemporaryBlindnessSystem.BlindingStatusEffect, timeSpan, false, TemporaryBlindnessSystem.BlindingStatusEffect); - } - private void OnStingCryo(EntityUid uid, ChangelingComponent comp, ref StingCryoEvent args) - { - var reagents = new List<(string, FixedPoint2)>() - { - ("Fresium", 20f), - ("ChloralHydrate", 10f) - }; - - if (!TryReagentSting(uid, comp, args, reagents)) + if (mobState.CurrentState != MobState.Dead) return; - } - private void OnStingLethargic(EntityUid uid, ChangelingComponent comp, ref StingLethargicEvent args) - { - var reagents = new List<(string, FixedPoint2)>() - { - ("Impedrezene", 10f), - ("Happiness", 5f), - ("MuteToxin", 5f) - }; - if (!TryReagentSting(uid, comp, args, reagents)) + if (!args.DamageIncreased) return; - } - private void OnStingMute(EntityUid uid, ChangelingComponent comp, ref StingMuteEvent args) - { - var reagents = new List<(string, FixedPoint2)>() - { - ("MuteToxin", 15f) - }; - if (!TryReagentSting(uid, comp, args, reagents)) - return; + target.Damage.ClampMax(200); // we never die. UNLESS?? } - private void OnStingTransform(EntityUid uid, ChangelingComponent comp, ref StingTransformEvent args) - { - if (!TrySting(uid, comp, args, true)) - return; - var target = args.Target; - if (!TryTransform(target, comp, true, true)) - comp.Chemicals += Comp(args.Action).ChemicalCost; - } - private void OnStingFakeArmblade(EntityUid uid, ChangelingComponent comp, ref StingFakeArmbladeEvent args) + private void OnComponentRemove(Entity ent, ref ComponentRemove args) { - if (!TrySting(uid, comp, args)) - return; - - var target = args.Target; - var fakeArmblade = EntityManager.SpawnEntity(FakeArmbladePrototype, Transform(target).Coordinates); - if (!_hands.TryPickupAnyHand(target, fakeArmblade)) - { - EntityManager.DeleteEntity(fakeArmblade); - comp.Chemicals += Comp(args.Action).ChemicalCost; - _popup.PopupEntity(Loc.GetString("changeling-sting-fail-simplemob"), uid, uid); - return; - } - - PlayMeatySound(target, comp); - } - - #endregion - - #region Utilities - - public void OnAnatomicPanacea(EntityUid uid, ChangelingComponent comp, ref ActionAnatomicPanaceaEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - var reagents = new List<(string, FixedPoint2)>() - { - ("Diphenhydramine", 5f), - ("Arithrazine", 10f), - ("Ethylredoxrazine", 5f) - }; - if (TryInjectReagents(uid, reagents)) - _popup.PopupEntity(Loc.GetString("changeling-panacea"), uid, uid); - else return; - PlayMeatySound(uid, comp); - } - public void OnAugmentedEyesight(EntityUid uid, ChangelingComponent comp, ref ActionAugmentedEyesightEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (HasComp(uid)) - { - _popup.PopupEntity(Loc.GetString("changeling-passive-active"), uid, uid); - return; - } - - PlayMeatySound(uid, comp); - EnsureComp(uid); - _popup.PopupEntity(Loc.GetString("changeling-passive-activate"), uid, uid); - PlayMeatySound(uid, comp); - } - public void OnBiodegrade(EntityUid uid, ChangelingComponent comp, ref ActionBiodegradeEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (TryComp(uid, out var cuffs) && cuffs.Container.ContainedEntities.Count > 0) - { - var cuff = cuffs.LastAddedCuffs; - - _cuffs.Uncuff(uid, cuffs.LastAddedCuffs, cuff); - QueueDel(cuff); - } - - var soln = new Solution(); - soln.AddReagent("PolytrinicAcid", 10f); - - if (_pull.IsPulled(uid)) - { - var puller = Comp(uid).Puller; - if (puller != null) - { - _puddle.TrySplashSpillAt((EntityUid) puller, Transform((EntityUid) puller).Coordinates, soln, out _); - return; - } - } - _puddle.TrySplashSpillAt(uid, Transform(uid).Coordinates, soln, out _); - } - public void OnChameleonSkin(EntityUid uid, ChangelingComponent comp, ref ActionChameleonSkinEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (HasComp(uid) && HasComp(uid)) - { - RemComp(uid); - _popup.PopupEntity(Loc.GetString("changeling-chameleon-end"), uid, uid); - return; - } - - EnsureComp(uid); - EnsureComp(uid); - _popup.PopupEntity(Loc.GetString("changeling-chameleon-start"), uid, uid); - } - public void OnEphedrineOverdose(EntityUid uid, ChangelingComponent comp, ref ActionEphedrineOverdoseEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - var reagents = new List<(string, FixedPoint2)>() - { - ("Synaptizine", 5f), - }; - if (TryInjectReagents(uid, reagents)) - _popup.PopupEntity(Loc.GetString("changeling-inject"), uid, uid); - else - { - _popup.PopupEntity(Loc.GetString("changeling-inject-fail"), uid, uid); - return; - } - PlayMeatySound(uid, comp); - } - // john space made me do this - public void OnHealUltraSwag(EntityUid uid, ChangelingComponent comp, ref ActionFleshmendEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - var reagents = new List<(string, FixedPoint2)>() - { - ("Impedrezene", 2.5f), - ("Ichor", 15f), - ("TranexamicAcid", 5f) - }; - if (TryInjectReagents(uid, reagents)) - _popup.PopupEntity(Loc.GetString("changeling-fleshmend"), uid, uid); - else return; - PlayMeatySound(uid, comp); - } - public void OnLastResort(EntityUid uid, ChangelingComponent comp, ref ActionLastResortEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - // todo: implement - } - public void OnLesserForm(EntityUid uid, ChangelingComponent comp, ref ActionLesserFormEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - var newUid = TransformEntity(uid, protoId: "MobMonkey", comp: comp); - if (newUid == null) - { - comp.Chemicals += Comp(args.Action).ChemicalCost; - return; - } - - PlayMeatySound((EntityUid) newUid, comp); - var loc = Loc.GetString("changeling-transform-others", ("user", Identity.Entity((EntityUid) newUid, EntityManager))); - _popup.PopupEntity(loc, (EntityUid) newUid, PopupType.LargeCaution); - - comp.IsInLesserForm = true; - } - public void OnSpacesuit(EntityUid uid, ChangelingComponent comp, ref ActionSpacesuitEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (!TryToggleItem(uid, SpacesuitPrototype, ref comp.SpacesuitEntity, "outerClothing") - || !TryToggleItem(uid, SpacesuitHelmetPrototype, ref comp.SpacesuitHelmetEntity, "head")) - { - _popup.PopupEntity(Loc.GetString("changeling-equip-armor-fail"), uid, uid); - return; - } - - if (comp.SpacesuitEntity != null) - _popup.PopupEntity(Loc.GetString("changeling-equip-spacesuit-start"), uid, uid); - else - _popup.PopupEntity(Loc.GetString("changeling-equip-end"), uid, uid); - - PlayMeatySound(uid, comp); - } - public void OnHivemindAccess(EntityUid uid, ChangelingComponent comp, ref ActionHivemindAccessEvent args) - { - if (!TryUseAbility(uid, comp, args)) - return; - - if (HasComp(uid)) - { - _popup.PopupEntity(Loc.GetString("changeling-passive-active"), uid, uid); - return; - } - - EnsureComp(uid); - _popup.PopupEntity(Loc.GetString("changeling-hivemind-start"), uid, uid); + RemoveAllChangelingEquipment(ent, ent.Comp); } #endregion diff --git a/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs b/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs index 40210e7e1d..32d44dd530 100644 --- a/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs +++ b/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs @@ -51,22 +51,20 @@ public bool MakeChangeling(EntityUid target, ChangelingRuleComponent rule) return false; // briefing - TryComp(target, out var metaData); - - var briefing = Loc.GetString("changeling-role-greeting", ("name", metaData?.EntityName ?? "Unknown")); - var briefingShort = Loc.GetString("changeling-role-greeting-short", ("name", metaData?.EntityName ?? "Unknown")); - - _antag.SendBriefing(target, briefing, Color.Yellow, BriefingSound); - - _role.MindAddRole(mindId, new RoleBriefingComponent { Briefing = briefingShort }, mind, true); + if (TryComp(target, out var metaData)) + { + var briefing = Loc.GetString("changeling-role-greeting", ("name", metaData?.EntityName ?? "Unknown")); + var briefingShort = Loc.GetString("changeling-role-greeting-short", ("name", metaData?.EntityName ?? "Unknown")); + _antag.SendBriefing(target, briefing, Color.Yellow, BriefingSound); + _role.MindAddRole(mindId, new RoleBriefingComponent { Briefing = briefingShort }, mind, true); + } // hivemind stuff _npcFaction.RemoveFaction(target, NanotrasenFactionId, false); _npcFaction.AddFaction(target, ChangelingFactionId); // make sure it's initial chems are set to max - var lingComp = EnsureComp(target); - lingComp.Chemicals = lingComp.MaxChemicals; + EnsureComp(target); // add store var store = EnsureComp(target); @@ -92,17 +90,21 @@ private void OnTextPrepend(EntityUid uid, ChangelingRuleComponent comp, ref Obje foreach (var ling in EntityQuery()) { + if (!_mind.TryGetMind(ling.Owner, out var mindId, out var mind)) + continue; + + if (!TryComp(ling.Owner, out var metaData)) + continue; + if (ling.TotalAbsorbedEntities > mostAbsorbed) { mostAbsorbed = ling.TotalAbsorbedEntities; - if (_mind.TryGetMind(ling.Owner, out var mindId, out var mind)) - mostAbsorbedName = _objective.GetTitle((mindId, mind), string.Empty); + mostAbsorbedName = _objective.GetTitle((mindId, mind), metaData.EntityName); } if (ling.TotalStolenDNA > mostStolen) { mostStolen = ling.TotalStolenDNA; - if (_mind.TryGetMind(ling.Owner, out var mindId, out var mind)) - mostStolenName = _objective.GetTitle((mindId, mind), string.Empty); + mostStolenName = _objective.GetTitle((mindId, mind), metaData.EntityName); } } diff --git a/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs b/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs index e53bcaaa77..47fb15299b 100644 --- a/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs +++ b/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs @@ -20,8 +20,7 @@ public sealed partial class ChangelingRuleComponent : Component public readonly List> Objectives = new() { - "ChangelingAbsorbObjective", - "ChangelingStealDNAObjective", + "ChangelingSurviveObjective", "EscapeIdentityObjective" }; } diff --git a/Content.Shared/Goobstation/Changeling/Changeling.Actions.cs b/Content.Shared/Goobstation/Changeling/Changeling.Actions.cs index 2c7bb2b86b..ef2406b405 100644 --- a/Content.Shared/Goobstation/Changeling/Changeling.Actions.cs +++ b/Content.Shared/Goobstation/Changeling/Changeling.Actions.cs @@ -6,14 +6,15 @@ namespace Content.Shared.Changeling; [RegisterComponent, NetworkedComponent] public sealed partial class ChangelingActionComponent : Component { - [DataField("chemicalCost")] - public float ChemicalCost = 0; + [DataField] public bool RequireBiomass = true; - [DataField("useInLesserForm")] - public bool UseWhileLesserForm = false; + [DataField] public float ChemicalCost = 0; - [DataField("requireAbsorbed")] - public float RequireAbsorbed = 0; + [DataField] public float BiomassCost = 0; + + [DataField] public bool UseInLesserForm = false; + + [DataField] public float RequireAbsorbed = 0; } #region Events - Basic diff --git a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs index b148ff100a..5eaa37b5e7 100644 --- a/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs +++ b/Content.Shared/Goobstation/Changeling/ChangelingComponent.cs @@ -10,6 +10,8 @@ namespace Content.Shared.Changeling; [AutoGenerateComponentState] public sealed partial class ChangelingComponent : Component { + #region Prototypes + [DataField("soundMeatPool")] public List SoundPool = new() { @@ -18,8 +20,6 @@ public sealed partial class ChangelingComponent : Component new SoundPathSpecifier("/Audio/Effects/gib3.ogg"), }; - #region Abilities - [DataField("soundShriek")] public SoundSpecifier ShriekSound = new SoundPathSpecifier("/Audio/Goobstation/Changeling/Effects/changeling_shriek.ogg"); @@ -37,20 +37,41 @@ public sealed partial class ChangelingComponent : Component "ActionExitStasis" }; - public bool IsInStasis = false; + /// + /// The status icon corresponding to the Changlings. + /// + + [DataField, ViewVariables(VVAccess.ReadOnly)] + public ProtoId StatusIcon { get; set; } = "HivemindFaction"; + + #endregion - public EntityUid? ArmbladeEntity; - public EntityUid? ShieldEntity; - public EntityUid? ArmorEntity, ArmorHelmetEntity; - public EntityUid? SpacesuitEntity, SpacesuitHelmetEntity; + public bool IsInStasis = false; public bool StrainedMusclesActive = false; public bool IsInLesserForm = false; - #endregion - #region Base + public Dictionary Equipment = new(); + + /// + /// Amount of biomass changeling currently has. + /// + [DataField, AutoNetworkedField] + public float Biomass = 30f; + + /// + /// Maximum amount of biomass a changeling can have. + /// + [DataField, AutoNetworkedField] + public float MaxBiomass = 30f; + + /// + /// How much biomass should be removed per cycle. + /// + [DataField, AutoNetworkedField] + public float BiomassDrain = 1f; /// /// Current amount of chemicals changeling currently has. @@ -64,11 +85,20 @@ public sealed partial class ChangelingComponent : Component [DataField, AutoNetworkedField] public float MaxChemicals = 100f; + /// + /// Bonus chemicals regeneration. In case + /// + [DataField, AutoNetworkedField] + public float BonusChemicalRegen = 0f; + /// /// Cooldown between chem regen events. /// - public TimeSpan RegenTime = TimeSpan.Zero; - public float RegenCooldown = 1f; + public TimeSpan UpdateTimer = TimeSpan.Zero; + public float UpdateCooldown = 1f; + + public float BiomassUpdateTimer = 0f; + public float BiomassUpdateCooldown = 60f; [ViewVariables(VVAccess.ReadOnly)] public List AbsorbedDNA = new(); @@ -81,7 +111,6 @@ public sealed partial class ChangelingComponent : Component /// /// Maximum amount of DNA a changeling can absorb. /// - [DataField("maxDna")] public int MaxAbsorbedDNA = 5; /// @@ -101,14 +130,6 @@ public sealed partial class ChangelingComponent : Component [ViewVariables(VVAccess.ReadOnly)] public TransformData? SelectedForm; - - #endregion - /// - /// The status icon corresponding to the Changlings. - /// - - [DataField, ViewVariables(VVAccess.ReadOnly)] - public ProtoId StatusIcon { get; set; } = "HivemindFaction"; } [DataDefinition] diff --git a/Resources/Locale/en-US/Goobstation/Changeling/alerts/alerts.ftl b/Resources/Locale/en-US/Goobstation/Changeling/alerts/alerts.ftl index 0bcf510741..9429bc7f53 100644 --- a/Resources/Locale/en-US/Goobstation/Changeling/alerts/alerts.ftl +++ b/Resources/Locale/en-US/Goobstation/Changeling/alerts/alerts.ftl @@ -1,2 +1,6 @@ alerts-changeling-chemicals-name = Chemicals alerts-changeling-chemicals-desc = Spend chemicals to use your abilities. Slowly regenerates. + +alerts-changeling-biomass-name = Biomass +alerts-changeling-biomass-desc = + This is your health. If it reaches 0 - it's [color=red]game over[/color]. Absorb humanoids to recover some of it. \ No newline at end of file diff --git a/Resources/Locale/en-US/Goobstation/Changeling/popup/changeling.ftl b/Resources/Locale/en-US/Goobstation/Changeling/popup/changeling.ftl new file mode 100644 index 0000000000..9ad650ccec --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/Changeling/popup/changeling.ftl @@ -0,0 +1,3 @@ +popup-changeling-biomass-deficit-low = Your skin itches. +popup-changeling-biomass-deficit-medium = Must find a food source... +popup-changeling-biomass-deficit-high = Must eat... NOW!! \ No newline at end of file diff --git a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl index 38e4724361..33af66e772 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl @@ -1,4 +1,5 @@ # Abilities +changeling-biomass-deficit = Not enough biomass! changeling-chemicals-deficit = Not enough chemicals! changeling-action-fail-lesserform = Can't use it while in lesser form! changeling-action-fail-absorbed = Need to absorb {$number} more organics to use it! @@ -16,6 +17,7 @@ changeling-transform-cycle-empty = You don't have any DNA strains! changeling-transform-others = {CAPITALIZE(THE($user))}'s body twists and takes shape of another being! changeling-transform-fail-self = You can't transform into your current form! changeling-transform-fail-choose = You did not choose a form to transform into! +changeling-transform-fail-absorbed = You can't transform a husk! changeling-transform-finish = You are now {$target}. changeling-sting-fail-self = You tried to sting {CAPITALIZE(THE($target))}, but failed! diff --git a/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl b/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl index 685a835d26..1e550cb4f1 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/game-ticking/game-presets/preset-changeling.ftl @@ -14,7 +14,7 @@ changeling-gamemode-description = changeling-role-greeting = You are a changeling who has absorbed and taken the form of {$name}! Your objectives are listed in the character menu. - Absorb, shapeshift, evolve and use your abilities to the max to complete them! + Absorb, shapeshift and evolve to complete them! changeling-role-greeting-short = You are a changeling who has absorbed and taken the initial form of {$name}. diff --git a/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl b/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl index 9819f6b3fa..f1897c18de 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/store/changeling-catalog.ftl @@ -130,4 +130,5 @@ evolutionmenu-utility-spacesuit-desc = evolutionmenu-utility-hivemindaccess-name = Hivemind Access evolutionmenu-utility-hivemindaccess-desc = - Tunes our chemical receptors for hivemind communication, allowing us to recognize other changelings who have also bought this ability. + Tunes our chemical receptors for hivemind communication, allowing us to recognize and communicate with other changelings who have also evolved this ability. + Default radio key is :g diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 00b799670f..d699229b02 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -4,8 +4,10 @@ # If item is not in list it will go at the bottom (ties broken by alert type enum value) id: BaseAlertOrder order: + - alertType: ChangelingBiomass # goobstation - changelings - category: Health - category: Stamina + - alertType: ChangelingChemicals # goobstation - changelings - alertType: SuitPower - category: Internals - alertType: Fire diff --git a/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml index 5ff5fe22ab..3ae08abd7d 100644 --- a/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml +++ b/Resources/Prototypes/Goobstation/Changeling/Actions/changeling.yml @@ -14,7 +14,7 @@ state: evolution_menu event: !type:OpenEvolutionMenuEvent {} - type: ChangelingAction - chemicalCost: 0 + requireBiomass: false useInLesserForm: true - type: entity @@ -37,12 +37,13 @@ event: !type:AbsorbDNAEvent {} - type: ChangelingAction chemicalCost: 25 + requireBiomass: false useInLesserForm: true - type: entity id: ActionStingExtractDNA name: Extract DNA sting - description: Silently sting an organic target to steal their genetic information, allowing you to later transform into them. Costs 25 chemicals. + description: Steal your target's genetic information. Costs 25 chemicals. noSpawn: true components: - type: EntityTargetAction @@ -73,13 +74,13 @@ state: transform_cycle event: !type:ChangelingTransformCycleEvent {} - type: ChangelingAction - chemicalCost: 0 + requireBiomass: false useInLesserForm: true - type: entity id: ActionChangelingTransform name: Transform - description: Use stolen genetic information to transform into another humanoid. Resets your abilities. Doesn't come with clothes. Costs 5 chemicals. + description: Transform into another humanoid. Doesn't come with clothes. Costs 5 chemicals. noSpawn: true components: - type: InstantAction @@ -96,7 +97,7 @@ - type: entity id: ActionEnterStasis name: Enter regenerative stasis - description: Fake your death and enter into a regenerative stasis. Drains all your chemicals. + description: Fake your death and start regenerating. Drains all your chemicals. Consumes biomass. noSpawn: true components: - type: InstantAction @@ -108,7 +109,7 @@ state: stasis_enter event: !type:EnterStasisEvent {} - type: ChangelingAction - chemicalCost: 0 + biomassCost: 1 useInLesserForm: true - type: entity @@ -377,7 +378,6 @@ useInLesserForm: true # utility - - type: entity id: ActionAnatomicPanacea name: Anatomic Panacea @@ -485,25 +485,6 @@ chemicalCost: 35 useInLesserForm: true -- type: entity - id: ActionLastResort - name: Last Resort - description: Abandon your current body and become a headslug. Costs 20 chemicals. - noSpawn: true - components: - - type: InstantAction - useDelay: 5 - checkCanInteract: false - checkConsciousness: false - itemIconStyle: NoItem - icon: - sprite: Goobstation/Changeling/changeling_abilities.rsi - state: last_resort - event: !type:ActionLastResortEvent {} - - type: ChangelingAction - chemicalCost: 20 - requireAbsorbed: 1 - - type: entity id: ActionToggleLesserForm name: Lesser Form diff --git a/Resources/Prototypes/Goobstation/Changeling/Alerts/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Alerts/changeling.yml index 4207e5610d..6d14d49c06 100644 --- a/Resources/Prototypes/Goobstation/Changeling/Alerts/changeling.yml +++ b/Resources/Prototypes/Goobstation/Changeling/Alerts/changeling.yml @@ -1,17 +1,35 @@ - type: alert - id: Chemicals + id: ChangelingChemicals icons: - sprite: /Textures/Goobstation/Changeling/changeling_chemicals.rsi state: 0 - alertViewEntity: AlertChemicalsSpriteView + alertViewEntity: AlertChangelingChemicalsSpriteView name: alerts-changeling-chemicals-name description: alerts-changeling-chemicals-desc +- type: alert + id: ChangelingBiomass + icons: + - sprite: /Textures/Goobstation/Changeling/changeling_biomass.rsi + state: 0 + alertViewEntity: AlertChangelingBiomassSpriteView + name: alerts-changeling-biomass-name + description: alerts-changeling-biomass-desc + - type: entity - id: AlertChemicalsSpriteView + id: AlertChangelingChemicalsSpriteView categories: [ HideSpawnMenu ] components: - type: Sprite sprite: /Textures/Goobstation/Changeling/changeling_chemicals.rsi layers: - map: [ "enum.AlertVisualLayers.Base" ] + +- type: entity + id: AlertChangelingBiomassSpriteView + categories: [ HideSpawnMenu ] + components: + - type: Sprite + sprite: /Textures/Goobstation/Changeling/changeling_biomass.rsi + layers: + - map: [ "enum.AlertVisualLayers.Base" ] diff --git a/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml index 182cead716..fbbc93c84b 100644 --- a/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml +++ b/Resources/Prototypes/Goobstation/Changeling/Objectives/changeling.yml @@ -11,6 +11,17 @@ components: - ChangelingRole +- type: entity + parent: [BaseChangelingObjective, BaseSurviveObjective] + id: ChangelingSurviveObjective + name: Survive + description: I must survive no matter what. + components: + - type: Objective + icon: + sprite: Goobstation/Changeling/changeling_abilities.rsi + state: stasis_exit + - type: entity parent: BaseChangelingObjective id: ChangelingAbsorbObjective diff --git a/Resources/Prototypes/Goobstation/Changeling/radio_channels.yml b/Resources/Prototypes/Goobstation/Changeling/radio_channels.yml new file mode 100644 index 0000000000..efb3b8da0f --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/radio_channels.yml @@ -0,0 +1,7 @@ +- type: radioChannel + id: Hivemind + name: chat-radio-hivemind + keycode: 'g' + frequency: 19840 + color: "#e36b00" + longRange: true \ No newline at end of file diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/apex_predator.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/apex_predator.png deleted file mode 100644 index f3e5a390b0bcb30d2034c14f61f646d785fa5acd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 672 zcmV;R0$=@!P)Px%T1iAfR9HvNmp>?lQ5eS04U16zltPIV8CfLD9kAGq$}XdU(H(4LS4O*u!6M^b zl!!<%SPcH9BzjIgx95BB`R;cl} zZuxU=+byo~Tt?X&WTpqAXD+B=* z&}hVv2}8kiDX=xO=+;gTe4xKBLtWOP11WVYM$ppOoNMmstUxG;Rf+F*{j*;;w9IA5 zA6WqGYH3HY0&({X1&=PlcE_Z9@9Xw+PzCPuSr9soCCIG+06yoZ-1qr)q6}#e8YrYd z?o|*1&?6ehEVHt=CiK@TL$n82hV{#%#QlKhJxf7v=pBF%D51SYWdhjK%VwyCqY7$J zJ%TY-)yTXNF&MJ5N3+TvT865GUdOZ~nE;$5WGbz>RYA>ZOQ9E5B$iqNOsY3yUOsi% zsVW$+wZ^;p!m42R06#t?Wkdg%q;0YF>i?hr4eOO{Xx?AXOEf=Om;~wo0000}WR3YVPs+J}g=GHop5hGiT1(v+KmIxg~4H zcbiFIr_-FRLYcuZopX3@0N}lYBlmQ5;qZ%wZ?yMrFYr3vet+eD-~RP|{YL-X{qf_v zpEfOt3<33=8x$BoXbQ|e=X73)&>{RPi8%twnjTx@+FukZlB6Q)P%Z%)jDeTQiy0wnU9 zg6kftP#6Lwpnr+<`Rjg0i5Lk5|9)~kcVu}g3X>4=|Irz^X0z#y)M~Y0Y&06VyDuM| zRRZ8|zWsFVb~~({PN)7mO;h)2xD)C_A%UmvRt8kPRil^vU7r&mtH3dJu&?+0=%-2( zfVXTGK#6dUW3$=JO(v7f-pT|Z8(O2$2!`tQdd4{dYJZ3@rr_8BLI`Ii1xNs!6##_T z2bcxNdTk_AnSeC}LP_$!Y`~ZWX0maOl2}>+7_(l|7zscTc!_mNQui%I002xv8h}>- zqXNtiC1CRgb4b$4Nodc$Jq<9}YPJ0EcsvdvS#)wr6X1pM(CLOkr17LOC@idwsCrts zV(VWIpMR@gM%Ji;14Id+&w`}K6N+dREG$G+0OeROB$nRT{b1`pKzJ|k2CZ}(Dha)W zU|0=O>R;Q3;hMI$A;>9sX$5E;N+^kUC#SJe@5eyI-iY>HSV#a1>}yyN^=+r$3t7F` z#?Ud_r=KOKFhgrt$$4Nq#<0z+-CkaT!p8LZ2kivA22^<^Op1g O0000~5BPc*D(alvO$u-OIc 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 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/meta.json index 850ba214e9..e85f2a54fa 100644 --- a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/meta.json +++ b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/meta.json @@ -13,9 +13,6 @@ { "name": "anatomic_panacea" }, - { - "name": "apex_predator" - }, { "name": "armblade" }, @@ -29,14 +26,12 @@ "name": "bone_shard" }, { - "name": "chameleon_skin" + "name": "chameleon_skin", + "delays": [ [ 0.1, 0.1, 0.1, 0.1 ] ] }, { "name": "chitinous_armor" }, - { - "name": "contort_body" - }, { "name": "epinephrine_overdose" }, @@ -49,9 +44,6 @@ { "name": "hivemind_access" }, - { - "name": "last_resort" - }, { "name": "lesser_form" }, @@ -71,7 +63,8 @@ "name": "stasis_enter" }, { - "name": "stasis_exit" + "name": "stasis_exit", + "delays": [ [ 0.5, 0.5 ] ] }, { "name": "sting_armblade" @@ -97,9 +90,6 @@ { "name": "strained_muscles" }, - { - "name": "swap_forms" - }, { "name": "tentacle" }, diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_enter.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_enter.png index a4161f355b593986115d8e2f6c7e92a2c56c2c4b..95b6448748748473b0cb4b23162841f8f9638c52 100644 GIT binary patch delta 369 zcmV-%0gnFb1(*YnFnE z6B5Ddh)?g{Fwo2aFyG^{9Ofco96?qH47dZ9?%B=|?c&J*qJQy40Za{u#s$arg3Sau0t8?V0R=kWI0G5*--%V4J_!dUpNOA(lju_hzfkA6-5q!B`}yH zU>1RVPAKug)M`@X09ZCe4{BJ{;LCngaR4l(qn8Ds#7T_hR7nFcM zLUI(P0YZioBR_Z@0CGL8TgG^s<*?wRl_QAim=F_()KoJ#`-ar?AtAE>QVr%IlY7-{ P00000NkvXXu0mjf^VgL( delta 711 zcmV;&0yzDc1M3BlFnQX8Q-GYjsODUG`wcp3d$<4dZ2cma* z$<2HB`#In5x$ot5$Rm%AN7k3836SgVx_t;|j&J_76x=fLP=Deq$U-y$gv94Eoo=1A z#$Ul_9<^KcH+;84L$n3Z0$ONzq+fp2FGL?HfcbVXdOir?6Ney1Zlq6+>z7T($lm&8 zq~n9}#n9qFNF0k~-p_b@w6VVD<^hPIonmzEch!tu0KK}doWf&;PhNoU)g?C%5DQVN zG@`yq6af~}Ie(9r-$^@RO9K!Kk;(x8AamLdfb!~!w|Y*N<`;wjHV#4rBhX#{`t6|f zyd09^o3Bp%SvDIB@cZmcEX2;%Y2+r>P8rq0d7CtfV;J-yvi4x4k0W6L@{`-c7S~l?16IeuEDVVP(3WP zF0fh%VGxXP0HHY7U^4~*a4SaTxS+6@m+F_AJ7OC`kWB_L_5?ELcenx|0PtG&0nR}$ z1e=gIAb;xs?Pcb)Tt`ATGyp*O5DNkz0{g_H1J?SJqnev~F`93QLPr1q#yYe7Gv0x{ zkI$nAiH$%v81p9-D%Xul2UrZxOXL7g=Ed3p*v*`yz4yQK5e;Lp5!@TVC(cC(p#iiw z=Y1|Db|4Ob+f8>rE7wq|0Yc(hB=p%r#5&+QwLrV;mW^n;KP2Y+T?5!1fb+d?Mxnpw tX+UsIq8F32i>JNrX}%k#ttQQN{{Zn>azEX!ZWI6j002ovPDHLkV1oQFOxyqf diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_exit.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/stasis_exit.png index 1ad4b4fd98068b060302da08759a15e7336ed300..ad5a30b7c388ed334383fad8213d09a58f1a7ca9 100644 GIT binary patch literal 558 zcmV+}0@3}6P)Px$=1D|BRA@u(nY~H_K@i6`g^h)U*acDwT1n6s2sR3Kc7lbCZ{S1t1~wK#Iy*rd z@dcD%rC=Gb6AKF)3uO*QHtXK*%eSVREH);QmAT8qvGzOt*`!fKvjmIFgVZQ*dYrTyKaP6O-p9R~~$yh4FM?tmX=q%5I)}DOd|j!1yBJX{5spX4h2F|251Q= z04xHbCOp2qF)#avzoS<#FAQb_MBV-DV0)*wS0rVCmH=u7TutuH!)SNLsm;6V>fZN! zZ@483KuZ7>0E}~k-~#}e5sE&nlav8k0yqF52-xIc9&U-y`SBjgA{6~B02%@~0AMU? zfNP92WCk1*=d%E42;c%h4RC&boK(}-Hip$X7J0OlTmrZNL_tbG3IH1d&Zjyrk^x{0 zA=m=o8AV9lD{&j|BJ6hos5f7&VQt1bup0qx281A|1b__ycLVn;DRgIw^SJ<|1CRs& zA=ujw)%jci(g8>YV5ctkTA~L)CP)O}-vZ!5x5sC1gE__}NMaD3{0fsaH0000Px%xk*GpR9HvNmp^DzQ542+zzjN6kPd1!pjb6dAy5$M(143bP!I=kb8&757iSj* zJG;1vLn$bwbSZQZ5rojm`i~Z((M1G1=#WC?cgy!Ux$l2O(!1REUhcc+cfRwT_wp)k z#G^9e_-=m!TsTzS{U6G_``wQv9J`Pe{Nwq$=Q{g-!*M)$3D)o*sT$%GIC z05&)Q!p+#|z_GsD`Cj$Guirf9_kOYPAqHYW*(VJm5g;izff=VRjrp12FB{MO@wN3^ z5yaN(ZGR0&J1m4%AqG$;IDr{}q(MkADZ-o!V99Ofk~|D3H3URStP<>TdhN6K4N7DJ z5L{*xJ@f4RRHEd9O)5!P4wa#>0t60gt}Lzjq)c8pREAstxt|+w`^3e=?pu4!J$(Aa z-Me!)W;~m{)>u!_rc$$13DoBbz~&RRn!A1ZiZ`7d_~O~>itBZ|9#R05!FEJp0D?n4 z^XO=lJ;BG0jQhCl=F|YdQs=Ed-~Rby0AhZ2?>Z2LJz$@7N!cs1f?61>!tte+>%IBq zJEC+KNPg9yjgfx&#NGqu>es%r0QS1phGi(MfPLHwfM8@=1VC%80`ftw2R2)0e1^0i z!I3ajf!{G?z0ve*H`Y5*cPM)W*B&xsE`Z*0+%P?N(!F~8#m}@MnaN}r(hl4&lcE#= zx>r76JzmIt4(I_CD!>MU;22LZ$eKZi$dDEQ5EPXeGXW@p7HgIQq5F}Zp;Fjxuq6OM zWvx8uWGk?HzZtzq!V*XU0;vN8rE8%ANV(n&dBMX#S?8e&g%lVW0GaU57$b(QS0?MU xQiR#FF7eA~q#q{#lIbw_?$2+-VKN!^p1<4Ks6R!a8~6YK002ovPDHLkV1n2FVUqv= diff --git a/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/swap_forms.png b/Resources/Textures/Goobstation/Changeling/changeling_abilities.rsi/swap_forms.png deleted file mode 100644 index 7256f103ae454d0638257f75fc8a9024879f96df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 778 zcmV+l1NHogP)Px%#7RU!R9HvNm%mF}Q545-h74^XE`=7wLc~GDNf2zXLzZ??MBAV#(w{?}6mrxqao+&vck*`b2_c6i~(gyLH09sSH2@zrfR z3y}bzI9>z<0noEOdNJb1zt;B?8W7j-;-NMGV+)Z2z(*l*3O`v|_M)4MZ)L1OULO8T zKz$y1CUe5!x%NWP1J-hgQ+Q{0-V2P6t;!f6gs=d786Z&}(DBE|6sPd{rwv!D)@9$Z zk2BH(L_<(i!?XmzIBN0B+JXDGbu8i##{5ok*!Qsz(IIFi4lmq)HR;7U&w_v#q5$R> zOA>Rqoty8@eHY6yN4qyMknlhV0%8=!H!TBHzC0BX#=Tg|i!egLMo(Z3W9@~|jcCaO z8?OhwrL+*jh(8)ZdJ5AX2%h7CywM@#82|*@yfE>m--~l@A);;pLSRoY4<*F8d1c{DEXN$J zSSlnu5Q4}AXygF`T)i6=5yn0Lpf17)1sgqqHH@_vS^^*pdIB7m2fd}V5WduglB=@V>F+#2!Km zfw|5NOiCI=TaNdmt|7~@W|fW24ZNc0ZCYlf#M&^kCbSh%D4&NaWkTs}xw=$M6thKXt3bEC2ui07*qo IM6N<$f~%ZdI{*Lx diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/0.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/0.png new file mode 100644 index 0000000000000000000000000000000000000000..e6dea8ec5698c800e20493e5f0950709c5cd3ccf GIT binary patch literal 1356 zcmV-S1+)5zP)Px)1xZ9fRA@uRS>J0ER}?-wQZ&iplR_U%;t!CHmb{1*B`Qg+DWuX0g6~q=r@Z%1 z@TI=wp-_A;OCKb(K@uggt!$A9meORcwC-kC#JZsYyO~WZWXpGF&fRnG-r3D=bmqRy z%)NK!oAaGt_nw;}B06w*sNucz=)!v!>Bj7I27O7kH1UuR4@rh314d#W|)1omx6jt6QGFL42T*^Hr>#FW@KDFv%n7@-;WI& z2i`xb#`3v&bv^&l1U*`562NYl2ELPk{=Te11V8hYGMN@q(AU3bfP&2pZAs;|63r~! zqEc~{?ylUYzn(s$d~S}ePJK@Ev$wPb*aRer-fPM@ zOjiUXf!~&Zq5X%n(Dw?91_56T4$`$sh4_lWQGd*sgLi)ZRe0v7FHm)+jk0;XGk9FxN~^HHBjW)` z_-5{=$_Q8>N$qb3|F?2zQQY1g>K$JZ(9Z7xVr$c70n%FRc){;;MhFAqbxlCG_$~|R z6<-t3F5*KVsTr3Ae4X8*&uX4hikxf50uG3$Mo#Fs00O$fcUeHM_$?M-YcDRsvlZNq zGJ~BV_&v@V{4DnbydIDiuw8t%g2jVjX6hAR5pd$|Q{-3jH12sSigQGlzHeuM52ADk z|Cj8$vIh%~I52o*m@0pUsdRjDf*uzN)C>MtDf|Zfe<%2w0PNV%e(-(zHwcV;Z!?TJ zXA1>29|2ghkFj&n7z-}Pu@gi?07QdDfPilCPp^dd$N)RRR|I_Me@s99x}c+~WR&l1 zmg6iy&cg@!8QOQHK@iXjew+n#i?7ole&WXLv?f7nlJA*m*BDz5YoF)Hxts-DX)rB( zy%4gsVEZzdMMI>=J-fh%JgNgS-&x7N1=pe{5&?#!^^n!Op8v!!ibd z09)&EksSSY8k9@nZtx`mJHc<+j7UwmB6^&UNq@&ZzYEQ~0$)@fOC8_29YsMjDux;n z*7ox_zn2!^st9%34kwAwCk=jB6%!<%< z3nEG28w3pem!)7cpn<3+C`(eKBVo12RrO^uVD5HF%vBMJs|luoA6pd;>KjfXWYxIE z6rv7QUf&D|HNpJDC91Bkk=y`vDvF}&oD_cNvUq)iuTteUtdklNo&;0Erq6vV}$Wy~3iw$*4bO?~*kiD6zRVL2)NTN#eH> z5FLbA71`vD#uCPFIUHrdZ4Jly`|IeJhoed38w3D6Nx_*sCLj&`{{gin2`P*K{__9; O002ovPDHLkV1fY7R)b;y literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/1.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/1.png new file mode 100644 index 0000000000000000000000000000000000000000..626313e67417d97c6af2ad780b9283c2c8257a25 GIT binary patch literal 1084 zcmV-C1jGA@P)Px&_DMuRR9Hv7SItXQQ4~Kj2B|Z;swhzV5CREAun5B>Eiy8xMc_i(MBvIze?^zo!5&kkr-&4M)QN zla;JvG_9{De0cynWmHjKsQ^eICKz^&3F<*tzd9Iz%{CkfgJmdGOP6{&DKQt#17J4< ze^pa@e^&!gKr(5ftU6ma%wKNoP-9lOurlizHZ@nzsA^<8Yf-WFD z=AJ;D0Sa@KU8`@oaAoFfJ|Y0S6)aQ-|w` zt(DecZJvnEJNxnd-eG~yF!%F3SRlWTdhXn%#A-}tL_Q5!Ia?h{E>W+r80&J*T*nxV zWh66T!ZDKSE;JBPR%e9%55V$!(Mjc3Zqj0#b>^HL+x8seBpavl=;csTaw#Mu+GrULc z%`sR?Ci*4mh>c5GEI zV*uS|NoK2v4y*}L^``gttUzSd?8SsC?=xvaO&D32qtwsvGVl~?DhRn>JGw1uxxidQlFF20YhIG%i2j|-^;nu)646hJgb@0nrKR&t)J~JjFzuP;AP)#)+&L|nk1yx9>tj6dB%W~Rp z#x{gtM*sq8ynpf(dRO`~)*L9Hl*mlN(b-|gTuV1FdBbiAXBs}R1@e|MP+nq;BTGxx z+UQc`U;&bNBW71d9>_rgfyqXCTVe*KLNSQD*pj#Ora_!4u%$X6-wK^lF=!|~Y zns4b2TNyIOynSS519o5H+gsg;MwT%YwoimtMlaNJ#pF~fzjN6TF*ylGv=S{XVJqUy zv6?MIjNFhP^1q&y+x91*=&kSWQtpmS#aYF+Yr>f!+9Nj~*?R*w>~&SOHCEsH1jjpS z(e~LpXuH;o>(`8TMTN!os_V##_g-Z06PmWRMLYKW6m~iuI5s4u-ywkDFYNa{&E(>JkJxPlGs ziSR3N^~x3N2!8a*1w+6aua{#T%XnGL>wj=benj*I{8KRH(o5^E3}`!h(9>yXh>J)i zWb_0XZ56A-f<9x7-FCE|{0M*Cuf(y|dhth}U}#{_5KzW5FuP+I#GmcLTj0fWwuK-& z%-!b+cPbHqQm+b9}fxw&Ix3FY7B}g$(`6eTj@Qtd@6aGv-#%gpGi}#|!pBq_^p_PUH0W@D~ z6pIO~{HY98S_@)%+c?Zhc-%=bew>aVma<8Rm>|ig`okjqgq+7vW zsKloJ1c|&DQhP?y_Ph!Bk^!u(WfBvJ-1z;6ASDrWCzxQ4QxM!Q7L^W_=TvG<^^c49 zBj4Q@S()O$a#B$7ks#9i9K|-`R+=TLgxq-Uim3i%L-y-fVsv^~lwKr+po%#N9Yq%{ zdm#Xe=1gWqB#Tss09{WjC4()5YQc^sr}Kk^%i58Uts=@<6EbETFkL)O(5I1(MYXKO zkqZ=v-8VcL6;UQ~)`U_BAkkXl)k(m)sX}3Z=X_kz5|&VAYxa9ZnA9nI~%#{IrQIt v9JB;EFC&HcWk~-em@3F2fXM$x;;i@!8tJ{joTrXZ00000NkvXXu0mjfX^)s| literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/11.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/11.png new file mode 100644 index 0000000000000000000000000000000000000000..d5060df98bbac9f9fa1b14a12cd8617565f5aa0a GIT binary patch literal 1387 zcmV-x1(f=UP)>4w0Y$);+T~Ylp_rxuO)Y8gN0ge> z#~Sg$DRH!8J;1$`rwDrK74rX6O;PS zN{B;%-(Ms|5Tuaxtu^#M==m?j2){h*SXqoRTL7~;bRljr5^hnsY&2U*D8_A_R}meX z5q%+ml+Zq4`_4c~NMbMU5F_uq3Eeh2+B!Rs;Sp^hX3u$ZEnv`um?i@LAmRx_wYfx5 z1_Omfi0{SVFAN}&h#|TWLGR%Ah%7DR=jl-q`GcMTglcOrJFR6PA9Nw1idw4^tjKG> zVXOihLf8jC54-Fv)YjJ{a{vV7esBmdj_sf(Sg)eh zqXTbmkB9Mft2Mmc!sj@dH6M1_hn3IE7h3Ufavbn5tc)#)d-C}!0dxYWt?|{1C@7-W z?}l;zMicMr676WIPm#)bvKK*Q=JiaFhzyU|h?I#;227O!B2Roagt<(_bFI#ZDLShk zR`V@gVU;0k%-dfx*?`@b`07pL`+P;5v@c^OISsm zJ=U{jiIE%fE&l6XxobZHihiwP8J9k7#w#71mA^MiPXyKCX7cj^a-w-Y|9HE@5bfi8 zvzYtX&PH`zt?Fl;f}-aNQCw7l8qdopW0mmox3z~~M@5=nDKmvla}AMG3)qeAi2G;S z>l_=B(w`;3U+e?(c;H8ogVc*-NMTm4U==5@tG$1Y#mm2^l>> zMqAD5v0zR+GNs$mdh#Ru?IzXz)2$8SM~`5zf4~w@!80(kYZ=7;*n_vwi*2@rAS>nW z3xqrOKA_a=f+T60pK*E&+vXM$2XWz#Fh9H~K1^B1kS6cV^y1yd_c7ev4`1Lou5`YJ z&4UdbXFl=vto5AHqCDV~%rW)~k(QX{v~>(N1*F5BwOx$# zJVM>&5dQ=MC%mVyl(x^76tih=h_;NCJLU<$rJi6lx{AezQQ^;xoX1dQp|2kot~ZIr zgjIf3g{ss7qhJT6tc=H<65}W72#f=pgop`}e6lYr(oe{H3}qH{5i%PkmBq^W$&*#s zFn>q9z`#M$N?Otzhc6Yt)2G?Q1R}TI{}7}kg1!kEFvlqf?w5#4hstv*wI=(<#QouK z9*L|>^4F^rRPtgFX?~83^_WVtB$bdG&s`BUoNLVeIhGii8WN=!2_fiW4nuqKHDxaZ zU@GNXMdDPQ;K8 z6o}ooJXsY{HuBblQV1c&Nog#jB63xvWO0?o>RCt`dSKm)uMHK$--CLGj7TO}9rLw?oY@sAg6`HoB!5`7q zq&CJT4e_ZDHucHGKfp)-1LFgxKKNh=@u49|6g8lvX-hyuhzkW0SXQ7FHpRmJV9T=C zIdgaB&b_-!(P+P$&CJ|6bIy0poilUR0KCbSQ<9jBv2B)y$-kP+|Q2Hi3$I=e3*Z-=-3q&;U8TcAN9Vw&*#0!XD3 zvMol6BIqx#Kx#V)U%4OYbP}<66#c{BA-cGPpQp#T=Xd&s5NvM3{b?lvrJxcC);3$6 zKy69+cw(Isb~wP1!)qr_qi?ytV9lNcYRFjL;O&_a$6R|4@au!|MrqLy@7xr)`Q<1jifNlonh*@JYlaXQ4S9Mm_;J#C~uPNF3Ni zQy?t8RlNgiZ%jn+MTa%K(az?WF>5~Jl8;E8=T3Ft?&JiZYgiti=kLV#mjoySsBQ9w zxfkTotG6Qf<-Io6S1H=wQlC5(^CZum$j_^hAQE{yd?QjM@)0m)1ju;$;}Oi}J)Z4w zdQ9F~{fL}z?}^9=S!34zk&g!KzQE@w2eD_(osB;^5 z!8mFSK0qN{?;c)Tu?N@K=Iy(O(J${|ZgfFz$T!%pclj3Y8^Zgw+9jNM{}sH{6~eZe zLcI~hQN0P}njbn#E1f-0LykQSWnsR4B^SkNHpca3QcpMmfyPj?>=$G~#i4TeJboP1 z4xoVV3ljbonhFuQ`)*eVw8#<)H= zil(8A!oVO-U2fxx39Ia>57x^C3C|`=v1;bGm0pzZiCx71-0pwU7CDxNN%)+RE*m&-WsO3a!@#|RN_S6V3y@&`w zC9@a0DlbWSP5|QgEv$${k?J5|nmMIpu!&GD*mcsWd@tgnb|e(5h)ULknr0=K$sZ@^ z(+I`9T23cX3gn30w=`K5Q6Wm!gn|em$&Auictyr#k(vk9e*)=DnzzB4CUn1b6@5Q` z1I?q$onKHBivNbDPw|_b*;IZL8ePT+tZZ|XzN{cMQ39PWpH_Fi1BDSF!!8fq#){!- zGGf;=m_L0xBnde$BgOE3jQ&e#Dj^2}GX6grE{eYaj4rL;sq|Qi00000NkvXXu0mjf DIi8Q& literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/13.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/13.png new file mode 100644 index 0000000000000000000000000000000000000000..2df6df47dd92c6e461dbc2efa66c53048b423dde GIT binary patch literal 1401 zcmV-<1%~>GP)LP;95G_|C`m)O>% z5@VBw_|=&9lmEbv{sZHOn);a#KQsi1;tN$m+7i$Z;zET4mJOu}n_^*KSXg$PGk53i z+`GG2jrJj%nYnXjp68r9XYRp-zzgjmZfqO4c(xxmM?YnFiZJ!Tw^Jkd@bX8T`cF%U zLxA63B}EXFkjwZI!Z#)yD5hl_1sP9w$af$a1nVP*#?q@IkVIP22BX(2>63Yr4x2rN)$yf z;HyGvHwnKlfOI;ESUig1@vjkGS;dd@Q_}N0Lt_ZFwc`G~7J+img@o$ctWL1LtbIJO zDFr(mkjUZnlV>orHe9skPyuyBmM5HCxb2ubeFOOUdZ8tp?|h>mP_z_*hFWVJU0Jc$ z##W+-3sA(HiNb**qQoa%npFfRn!B_X(mwcc#ARopEgVKZ0VLFUa0p0LY@;<8w!PK8 zgJ`eKMDTf!HN19O%n34UKH{>E*n6Jq?!n#J8Ngszn_iO7)cdyuPzF%j>J3XT$fMV8 zMex&mU81i`EO<+O@>I=}y>uc!uSbGJdp zgENdxq3h^fDvb61dTia=$6nKqWnT>28#~}5X)I1G%h8n5IQW&3;ffe_q;$)S6;tvZlzir?VcE!j79 z@6W#f3SR1K$1YEy$qb>MHNwXNf+QzMjr;X+O>uCL!CywmVteiV8gy;v7!e1o7U+o1m z4ERyyAT?1TLcec&*}+a>ng51%b^`bLoEDu5>>*=|p?wlYLhh?@^~x1H1>bt-k|p4o zV-48ADxMUJ{8!+0W(lYl5m?x<43dBD!t|MV%(f6@N5u9Xxt;qQQ0R3*ij=Z~ zUc+O)h4ev6?nj_QNFS!6G^ELI7KZV5=ew8~9ECSfDbw)LK^&FLD{s%k(vZd~Gf2#8 zJ`HI-WzfvT9Hs_;kBA*?J*mpqL zG*nU0MW|@d&8X1W*n~L$11V1e2N^4988ZcMHh_l@i-`$DZr%Toq$GmA33)I_76kWe zWTivpIh9(oBh&Kv#25EuRA$8oNC~QWHi(?aqr_&?4znUvPzTRl5p|sGEd4oFn4G&U zOD_^a(8U~vzUs@iy%Yd-{T5e5sz`MR;5-NY%cO)*E!cO;x%pwjRqaS9RS}h~2^q5z zjLSbK=+&r=Wwo46q8!MPLf_iSs)&kFwkA|U2uVRoW91c*+eK;~RR1ZYGilj|>RK^y z;VOo{`x4A!*eAcJCRG0$a=+%cxU;GJCbYQ72%>Crl)j=M z5|f%rd@?bnKKUQ`=zm~*sHx9{_+SVUMGZBCv?ZV+#DxkGmX%V4O|h^)SXgGf=gyv) zJF~l3jrJpO=gypazwey6=gh$mfoD1*roOG?;<Iuc%TsiY*h$dGEj_B&HYS zwG==~=p1PKZbwN-b~ob^!}q;}UKETjzBR|1RCqCd3VQOsj!p*v_y$AG7y7f4T>^XJ-Ii!`k$cyvOg~7C;$5V_P7KjfPdc z{a!z68*9*UK7zLv9f8e}X7uhl^WNvgpLeb6^~Aay*?#$U0^1uqm>r2p5d|K35GfLc z2$*&RnEZ5qw@prjPx+s=30w52Q0yxTZypEut>agF{(JtOErRwVEKV%r(;L^3h_7Ni z=46DQy=M^lg@G64HIujN$6aK)a>@2! z{keBuz_WcF*fld~_D9g5HK9ri2@*q)>V*aF25w`kw2Q3&yEWr>Vc&27YPW~1fc0U zf~a>6E|p3dTprECCH70t-8qLHf^K_^bSQsBIyv9TD66lmj{9Tw6O?9Z@mL_Jk~ikQSsgXRnCdZc+=N{*RH(W#t&DZ^OWAS26V67tnmV zee#QXLjK>-3@Uz$noaFDq18i15N(^Q^%V`Ni8APY{;YcPT_}zK5qmuJl9j^W+3=|s zFu(t5%ogPS7%9Y$Li#VEse)Vri2Q#fE{VSYo`0yNqb+b800000NkvXXu0mjf6;qad literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/15.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/15.png new file mode 100644 index 0000000000000000000000000000000000000000..36ca36b9fa49dc99137d2b7692c8425529bfe26a GIT binary patch literal 1396 zcmV-)1&jKLP)I{b!NRiRoHKi8 z?#%9DHQJAC?wy%)&-dLk_nbLsF!6juH`E;!S1t_V*7&Cio*^{(;@g=qynpROgXYgl zh)X~qP|G3+Lde$kCPwd!{Fh?buNif$uO<{nfZ}k-LcC%GyrRn4NVbBIult9tA~Czf za~42K=o}~q?v0X=>|VwtM%?!jdTnI%4_!f#hsR*rd8U!T%Wjug;HWJSX1rQ5D&uiONF`oI|pmwGNc0?m;k(A;2M$73;j zZ6X#wT7V$lN;wCLh*F>QN>&Rv-QFvWVEfR=3m!WQ;b;`a1Yn5s;1ZCk+CgV1ip{1q zzV3Uk7xm3`#iuVK@`El}4+58+arJJ(w%hq=7jt6^=n6-0vF{x2&fN!81?#gbY$4vi zEr2qB#!kOQ-Zy9wM=n}~t$Hhux)#MD@;vu>g4e45#^Xj}gGXAwv$@N25>e#ggGiAm zM!>MWlp`nNQ(Bu%a#C$GX?-Q(&6~3JwLPAninQVP{6n5oRLCD;d1@7(-MWcHd<~Ne zPDW^A_X0$IY3NnUS&=-fcDcw@>691D-H<3@6DurR>vSt}(LFXM%Q2=vPIm2Tl-KY3a@fcx*k3NQ&k+#l zh=lEVNfy)|uR*{U#4+Urs^RC|sxF$!u;U`#@6oO9TY!{7_UC z0o1xkHB_0QKd^V%t(?WG@hc+AX*@9IrRY>)A30kL`AMlM_A7AX`gJ=6-+23~CE&Re z&Dg{mo)*i-FXkQth#M>Tr%s~VF_q5Bd}zpEYg4OL95a5MA=42IcB=|u{-ZMpwP>L1Sw?& zy@n^oHnN8q_D7&YNFSz>G^B^$EREu=o_8=cJPv=bn$z&{VG`AfpR4ClX-L;1Gf2#u znT7;fPZ@N7Y92GgKl8n^X()OD;gG5qopkf+|D99p2 zG@OqS(9+V1r11wbz7!5~R?u=<2L5~yj~Q9DHF!Lz2mA1nyNCm{f^L9ktXL`zijs>RYZ*%KKLI|>$ zqcBi+&9-L&5aqYDBN9!jOMqb%Ab**J5b6a7&bpl+C0z85gmN8G#hy@6tOPUo&kcGt z!ZCNt*)%GFHsZ`JPgX}%ii$m<5JE_sq;xHIMdWsqS_Jif0@+-aN4>EVLvP-|$ah~r z@u~L7FX;)z|At0D+P7J=sr@E&c*qE5lj>@H&4$!K1@ymkUOxFQltzGvJv;Q0mBZiJ zh*NLE`2MR2Taf!>BoRLi>A!>`3vvk{^8b;zEdBz+D4?^#&>w#Q00005Z`_EuQ5KD)Mr9`Fa(LBh8jZJ5*kB@uoWV^tdv^VKnweWg=NP%XZFtCncW37 z+K+7Rotb;j_nmX^x%Xhe!t3FXsqJdGe&rVKO?)o!3Sp=ZKg^Ef)0>}})PGe%Tmt<5 zdKN*DLbi4`G4^2ezZAoEt*B#tH6cm@M2SNc;uRy|6;)}BGFB4u^Wg9eB<7;rX91*y z_JKHbJ4!;b`x%!Q`QA(D9iw7!_&SO_+y>I6Z$_mF0*VmRM8F?JCYy5FN}?!&fx3ER z_S5j!1(40Akx0fdHuW9iu{Hd>G|Qep7@b6@w+By_ln7LVDkRj}Yj=XJRmUe&TP)b+ z0Hc6+&tJyq`dHbVV+FJjS&?wFbqyz>4c#SyR!b3RYqp=`v6wS15sM!$KoYB@1Gg@4 z+t}7nHhy<=kK6h2MfZnjsoNWgpqKy*xgT5tQZ>8i2}T@ZA|=A`)et{7cQtVv(CeWI z-JX-*<#zjz7Z|vG0jm!aEQrLM8w%s%iFO{@HLR{>(q+K1D+=-SYG*zPR@#67e-mEtWFEklw4xuMNLr8!M9cYUf;JnzG9a zmNz6yj)@hP?Rg5x=s9fU6KpS+Ip7FLbccJLepwdOH`T$f2XIQ9MlF2ot>&Vs z4trdI=h6HfJ&Amiy=S}|lEPmmz~A75&@}i_?;Oiu)| zdpVee0Gqxocq?d|29_g$Cz_(w}HH8E)mXtg2`wNn=9 z7yB^k47?OO2#Qmdd7aI9_W^}o6(mV1D`**BnmfoIW!N5pHX&V1WobwszmJaLqyF2N z9+`kIP|In!eUwD4@bT$+TpH4|%nTBI8sL?5+Ww3cY-_vs-~fgf+|8rL%sdl+dGgnpChBEaFnxymNPQ&3)kkt|YO0!*_2^=6Vns1_W$=r(?w za9KMNDpf>PYeFIH1T*>14O$v$n5*S%8r49Kl=`+MyCNz_)tXQWA*3x*dX`=hxl^PT zLG_wnKg z6%4+0S>5?Alt+MwJsx_=D&eFta@SihfBJUP5#+v%6ym=_`Y$0=K`sGA{y!2|#9tJ| Vnnf6pQCa{1002ovPDHLkV1g)BgOvaP literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/2.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/2.png new file mode 100644 index 0000000000000000000000000000000000000000..a7f6210c9ec8e060b7d28b79b9ac2ad8dd02ddc3 GIT binary patch literal 1106 zcmV-Y1g-mtP)Px(3`s;mR9Hv7SIM=Nq9!5^a(IPu+i=gxh1-W!X;v$^m7Jm2}wIq#ku z0D$tU%1m%Ah%4th(9_o);MiV?QYHZSO%Rm50^$NtTwIE+txW|$fTUKJ(Ho8YPgXLH z(O8;K266z_i%@)^L;)~?*kHspHmG~;57fZ~Y)fx6LYApeO`X5phU8={2SD5q`c;PX z)`kY4fNZCMj5=Ej$1l{is=g?EKR4kUHszO2tGQ@oP_>t@x8dV#0RSb%Ar}zoOA8Wb zf#O)nfl{QmH#L*Vl>~Z+`jLn)U}Saz-z%Y z0hj$b0CBZ9GT8ff04fik(n`l-!!7_%0o(^5Ehn35T3l`#N!&kt}YP8 zsf}DfJzShYS4FK>Abr1aF8s7OqX7hhK|Ff$Sj7PWym>XiCD|;HAY^-Te>KEf(>l&g z0XXBV=l45@1v$fBFXLnZ!2oXGyoKa^+{}nv8j5nUI=nc8PQzl(n`8Do`xMz@GD9XD zBT;#_20%tZefE^D6@oeCQ$_NFZkl(r4&$o6=NPO7wBKsvHvH{uU22d#Cd7u|Fw05Mq9v4Ta zb!)G+V?hG4v93W8na6;Q-Y>$%db^eVvd?5i0#WV+rlSJTaI6v6q7e`it&)f~eP1X8 zgatcOjCfX`%!Z2U6IlGbp!%)t1_0hzly9A#_!17g@|{+g{^pwF;jm}6*mE|cya_1` zK<$F7H8=3~!;n7Ew8+cHcJzC#m&0U^))!1eg6Qe%)*wtv*OLe`sS) zeLsoR>I%#Y=#K!H?*rb~_RGYq7YT&QT;f#C+#7lQ9D$-BPx(B}qgE!VuxuBI1E8WJi1qa~1t1{lr3LiFqW{w> zSxafmPNsZ$fRz$dlm`@m4r0Q{(U?#-+q%@o1U5@gEJ`a=pc*>fejcf@L>@qnA@pMh zGV7~0fCAab09m!SUD$u3>9lH#z_$~lo^Dfn=AasjMf+8K_R@KLcwYn{P!V!~NLy;q za0V3n0_8zuHr8xQrWU8rGw=*k$!WZNKZ-B2bBIOzaHFde{k_k;Eno^{duOO>%fMJ% zO)GF&p9e^)wbA~bzXJ$Y9k7KCCk7n=_W^VQkeY)n4W}G)xMpPhH5R_6F_lc(09zh` z2u`o&9qQIh9Nl{xZ2{8u>gU9dGjSWh=lA3Gy*tVe2=L-*AE)G^K%9{E{<3O_eUDW{D1c(;&c$A5Yx;!vhv_e}ULqprkljvzVjj;8-7G_GU$!2? z$)Rz(Hs_kYAP(8wbXXa(zyc<{mxqh?W-aSwoo*FzM7a|fjtb!Lo@QK(ML{NNB_6Gx zeX$4-E0~d@`Lp_DH0-V4hnY{)s(rdm1K^FtGT+gG&yk2D-l>It-keh%iMU3KIc8dv zHz5lh&_03-4Oj5$-GIHJu94dro8Ip>UpA95YF{)A3Gtx!t_`7UBHv;Kw=uDvSVrdP zb0rEKVQ4u<*0h3~mYi#Pd@=3yJqqsyD+~pIH6eypdU5D-BX;bnL|t>(^dr=SYYO`2 zQga1;lOriCFRWnvNgNYHllFK)7|MwJ5e0(%CP;O44Zf9T5GV=a*SB1j#CF-w0Osd& z5V~NtDcp2CEj@GIhLVyoA!VwSC>^uO3By9msCst@o10mzrq`6FEYwyn6z-N0d0xi? zAQ_FG8S5|w_O?1&E8EnUa0K!+lLv|Y9^j7IEKFhS9!?B8m|7Z6IUf%i4O2Y*VNx8xdWaYq x@isiomi>xZ1e+Rqo%g@?J07PLzFmR^{5M*oQtd=bFT(%;002ovPDHLkV1iyY38Mf2 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/4.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/4.png new file mode 100644 index 0000000000000000000000000000000000000000..36d867d3b85128fbbedf9973129f57e6df73fad7 GIT binary patch literal 1140 zcmV-)1dIELP)Px(E=fc|R9Hv7m&;33Q5?s=GX~|%Xb`;=n_dt|Ab~{~7G+V9MJ<9B(k22|ZTc(P z;qQ%C{mASR5s#)P`rd`o?pz-H@6#A#&;R7=OMokM0gRRj<>M1E8v zzm#(T6v%QOu++Dm#`)uQO=>IxUylv>hE2`s18OZ1?@|4ki|6p}Z3%!-dBg=GW9dP| z8Bm-HZ3-j5yy#dmGdF>b-Y!g}C-LI#5I%pLMk3yg8@F1~)7j;30aGByTO-@nOpLi) zR)Nd;B0yT5jrVl?9YA#3UPtIas?P=R06;4M>ABcgd&;$kX9h<{F#9EoiFDclSkna} zIGZa5)a|Jx+IAgu1jyKLo*Um!B^`i3Fo-+%@2WT;!1Jfw+)~H_2}1UJHtvULt82%# z5deqn{rvsPwt{@a+%NOA0)hctyK)to@wA>1#WWP*qIGO4i55*`uIqE=I>&5sMz;(t zVJnH~ky-$jlM&AU05!j!>QjD|rfck_ojIqEz4kLBG82>NO%^jEz`tQ#d()@XjHcH8 zFMwWF@Unmz4}^Hw{a9s$0x0Eo9`^IC850s8X1vIG$%sNgwz&aHMLa*~Wf^Mw;>01G z>>qV{bFUc-5|9mb^~#W|EMU_6MYw2h_Htg%=~j_Ilskdxr~vABHsC@c4l+?IiD=#T zr7}RQU}lOI&*~?$Vb}iMnEEiO#!bx{0BZ8QG)n zl`L?jp&b|&0s0p@ap=-PRBo@pzJ{oRsg72gu3U3Gotaj9#s@Q)pIyM{lO)Fa$Blcz zPD24;|0fWUvI=YtY{3eCDj_**9=VkT2g*;lY*>|svLOG60>OFH8dX)*__iUBP+1r& z-wIU{#~Xi!Ff+4SYpzpsGw`(X%zYb5NydVdrB-Ugu%j7a*ytYB?}*^nFAKTsqB1iZ zO|3Ty_o|3|uVVp_ibk)Dbr`=OI8o88mZVN(Y8L?96_Ia268;`RemRe@T@&i1Sz}$H z+VQG-1&FC;mSkQP(V8_OtKR(5q9=%6HTzjYmG{eeuqI5r9!B=-9P|t5j{x-d0mIFF zndtQ*K`6}C?5de(|(XwC)QI&}dlV z=?|0A0QN(~sEF6$X*M5J%p%y-(9hrhI`4X%Quula7VzJ)c~tH9vdD%20000Px(VM#5CTn zR0YBRK@bbo2NCRJkVR2~hLR{riDBE2N`tVC2AWN_ZDO;bg=Diy+ay=c-2G-}CiiYE zE%{#N&d=|BzwVsz0ATO_lpkA&;jOk4_^$VBk7HXUN|^xQH$hOg3Wx=ut}cP)3p0UYE3hNMBmeaSkYs#kVI=T3OPb%+F7uXW$a1a?`kcV+40^&mfcT!-wb2qQCc2 zvpi!?wHvRThEHf zPfcSWTd4?-zhTpQH>R|Vt~TQ@t_|}-m=CtY`>ofrH#-hyr zbP9KFc3bsME6jLvO~VJ>`uu8hu19$jQkZ}`1aCLJhd=%r&6On9R}o zs%1zJLyNsQ^3h>*bRD+5x2N`b*PD_3e^>8VbHnMY7`k{B6GM~ww``SRIWc?zaPUMk zc05~;{p~30Q@>rN##Mm!c3xHLI0)TB8 z2#ynoNV29L&v-krhE+9G^cPTETh#MZOmUUVf(>OT6Y@tC5F9tX!G1$SBOcV25U)vK z?Lokj*x$Aq$L#Dz#hj<>-NZBKnSC1yB_kmv)f%rI)s_(qh2c@{b4jeP`zRKcl$$AZ zwJB5{#)u-XV*wzH#$?7O7)y)MIa1%LdcqS~+5rHY5k(S`#KR;?D? z@p)X{X*emodxNb$^v zNp%3_hls<7x8P~E5L1{%v1?&eyq|R3dYn@ERw)+X{{e0na_x?D(oO&X002ovPDHLk FV1nqKGwJ{U literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/6.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/6.png new file mode 100644 index 0000000000000000000000000000000000000000..994c2fb5bd5ad8734683db6369efdbe54e1206de GIT binary patch literal 1225 zcmV;)1UCDLP)Px(gGod|R9Hv7SKn(~MHK$--9Ve&O$(*fB-YioAcjaNMoA;F*ebRdiz0;}#g{4+ zAF3euKL}z$eGoyPB*f`*zXO$lM!kV^C5HZ{;}vTc(#8(K^@n^cor`R4AKojbXA zQ&DnW=FXWP=R0T4x$}(!0K505Ja^v3%WcQ-Rqy8xb(Z30V6 ziwb}MDcqYyPbU3;M#U>TjhhpBrvhLhhT5%h1;7MigOQ`LLA}#?T5U|gw)AAuj4}H-!sVhjHiTG&1QvoIHIB z{k`X+BVZe3e`j*nhJ`U#EGXczz5*bp)~5S={vCkSu07i5(AYo#z?a8?Q)dBbIoQ#3 zC@_a>E{V4K4{D0%y6?dApIykC;o9Gs{uH!i;uqeMCAbi z{Q6TLr}$MMNyzr`E&Cwen%;5j^2fk4ANuq8{oAk~1rd9`=otmL4!T}Hj{HQ;guA%mx4i%1{8X4Ff?vfrunyb=dCgz$#Xhs_4z5 zxVoUnX_#UgmkI02PzL0WC?KdeqrrYdLnH3jln{?4uzKHTN$hV~iDPDFy&t{O)vo~ zM(1E%r|Jn$WNG^Vf{ZAVkR<*}qO@E>qRa_RYSz)>vz;*3D;W5ru_;*?BifJ?lJ%CB z7DIuUtT`$Yirz1mKu(ysGJ?XrIhdLMKmg450n^QXnV5W$Kxix=W!B8K>0iEs6LTRK z(7Gd#r7U2H?xcX}C8s2vM00000NkvXXu0mjf)&N1a literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/7.png b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/7.png new file mode 100644 index 0000000000000000000000000000000000000000..deede1c64cd501f15c05a051e04c71d286d1b240 GIT binary patch literal 1252 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/meta.json new file mode 100644 index 0000000000..12f5ad4cdb --- /dev/null +++ b/Resources/Textures/Goobstation/Changeling/changeling_biomass.rsi/meta.json @@ -0,0 +1,63 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/09a5191fb11aab8ddffe3f9be94292b53e4d96f6/icons/mob/hud/alien_standard.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "16" + }, + { + "name": "15" + }, + { + "name": "14" + }, + { + "name": "13" + }, + { + "name": "12" + }, + { + "name": "11" + }, + { + "name": "10" + }, + { + "name": "9" + }, + { + "name": "8" + }, + { + "name": "7" + }, + { + "name": "6" + }, + { + "name": "5" + }, + { + "name": "4" + }, + { + "name": "3" + }, + { + "name": "2" + }, + { + "name": "1" + }, + { + "name": "0", + "delays": [ [ 0.1, 0.1 ] ] + } + ] +} diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/0.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/0.png index 2402a9218e0bb34f8e02f03e9bd3bbc4678cc142..bb1a8a818ad7c3b59c5fd0cbf92bf400af3cb67b 100644 GIT binary patch delta 818 zcmV-21I_&83Ec*eIDZ2VNkl=GD-OHHTlQ~JuK@%k5K18)OF&z1imMkCRxmy=2<^ib#0KJ^ z>n+fJ)}UB^1%I{b9=zY#V41HMN|4H=VRuKWfS+WLR5D{W63K2uKr;Z91eiK-V{!%x z^`fhPSAZc5W(Cw5Rp>=BC{|$jtU10`wa07KT6X{*xY4v16wZ4ulE=u(WV2vx04Ba3 z6oBT*14t*buvS@xpmg=pG<^L02_QnCzWI%P$LC)F;C~LlYx-1{WpH8SA_OUfu;Q?V zh?TG^Kt%;Gc#xTU>B;gEXtg~kZ`(T}%CkoWow=;9``cnc&KjSm_2;b3DiB8YvPQ=; zqAjMNpV@QC3lht(0APv2(Bi^^*n+q2&6@z{&J2fA0ZfuehhB&NhYE~LkFyU>L3yQQ z0whDI0DmTk2a{_X;*YN@MYuEd09{aDAw0Js_X2p+@Am+g=Qa$Z3clNrR{du%HNmXM!c7oQ8>`)6D6hJ-5v0KLdCwf{O&)j)Hh0DEm`|Q^Oz3 z+_K&RKVc$v+kBGgh}`n^gd7CbVn!20Rv27(KTGKUxb)vTyQYTHkiWeMg}2XuoQQ~( z@A8EFe?uCR@;2VtXunaTj$kO`_*Sg%wTYV)e3ZL9Bd>f#?h0UFM~2Q|UOKK1U-gFQ wyDOI^0i*!PfqM!1mm)Hd0)WB$7}!&P09J~f1gkQ*CIA2c07*qoM6N<$g6F`7tN;K2 delta 1212 zcmV;t1Vj7X2IL8lIDZ6|Nklj}%u+&MGn`@T7I?@1nn?dj`|a!+$`{C};JaIJdPgBNIdKKS9* zH*l`}X@vb31;heSD0E5?Xar>K&&M!xv+`epk^2hOVdX*7GXOjjg9CBFkh!4Je3&l< zl;h#i6VP0&%f1AFNH7n)4Vx$e37@T7VCcCU(DmUqJbE1BII`_%o4I`20uLCFD1wec z5!S<&Zqq=af`1*`I$`};8w%SxAPn2keA0lKx$mH{{1C3+y(O97tjt1pZx8%-mno2C z97uOpuj&-LGX0;l)+AtyfV53`_s|ejR%Q}wwgj*PgT(=F)aR|a(j>srdDEi3Gw_}P zC|Xot=XN!2EHCS|&E>|{04VTU%On&AYF!>=UI84~JAcSpkp9Jsf#avFx4$3a3m}d1 z!2-~F`6=`i`!!q64n#Xv3t-Au!?BW>6T#Gc;P?l6&$r+3;g?@)067m<7Vpbvbp9Fu z-T-WSUg?)CkkQC=01KxFMV~`7Y_X3_X&(J0iFlqXfgs{IG7(XUSb<0@fWgBH^KdU_ ze9yNTBY!*95A=L#GSCXCv1lK~YJlmJeSU2cnvI7r7nmE7C-xG(K#ZFb@`ck1#L^O2 zY{^QcKr>E`xmpSgCFC*j`)Xy{909V|=k6ePnQ;B5{Dxqjpb85@1sv0y zQPz+^z#jYPZAMH1%w`c!ZU(FDzQ;F#pSQnzT=XZ58jQU=r}hB7-(LTq{NWMIRc94| zE`Ol_6zcQc~>- zgc9TM<*o^y(<-8ahX&F=$7DC|%*)aX)!4iQaBBE5)bopS zZsUtOe0=4k_Qmo3qkXbpI2Xz_2>y@H@JS{1N>)Tvq*?%?Xp@x;1_;)IM-P|fvwyYe zWYs$WLkWLu(b6iS%$m^ia3_qhz<7&yHkRLL`$FAj5`_4DrmjEU5qnb{M+B(lzYwd?@RMML&Hzs{vU6c;J5$^k16C0a*Yr`2QF< a)t&?1MZH_V^?+Ic0000JN6G0Tm-^Ped8U(fJ z$)@>nu&p8%TExDOx3KI44LA_P zfM_HJ4ZX&;GAOKIw4)Om+jWR^L_ybo>rnYwhFswVluH}%c72&-zLd>FB9(;C>s$r= z#DgT_DQhPd-|G~pn1Dh8M7wZtU>LI1oM-%=09_c&4!BrcfMzt>e~E!&1-g%0=a)+* z2eMS&8-NDhtT_|vRL|+A0Qhrwr7{_?7XT5R4+22#@D?OvnWnRCMi>;s88n;i-$2-L$V9|S*cC8X1u%FkGneW9+ykhTH=wZQtcWnp z9@Xm1d42i5#wO%t^Yf_slhp8;f!sBfTO{yL^wTwNg63#)-bnCkn-`vx1FxXs3s;4A^JBcdjMRRA!IUCuH% zAm{~0T^E;vo8{)cBTD9od_AG2g@~Io$e$>@HT1&7E$emg6DH#9TUW9ikypN+kbT!?yE1eKlWD&(bk>_P-aOAUAOV06+)U5|O5=e901V#7 zz)}eSed#lSS~CxT>JP|3+%QzCKcM;pep3Abw^VJO;?z=8S$ zx!F7W2AjG2kc}t7TiG9QUt9{V`UAF9e?TVHAMn#3I^;R{te!p%N&o-=07*qoM6N<$ Ef^#Ug>Hq)$ delta 1228 zcmV;-1T*`-2j&To7YZN<1^@s6b9#F8ks%j<1ei%gK~#90y;ogmTtyT@Cm-{aSMedGkBS6^66&H+$q$xFn+=r0 zZQ2UWc9S5Of)I9dD}h|+o4IH1nY;IRJzt=WpkdoIQc$peeP;)4JZ@5cXFCN!lj@IZG&%h})s`O8?427r z^6L{*)Ya2Xckc)VvLK0cb@rG}p)=F|QDaRLb^_4YqPGqor-_xxmNnZF*bQfq!)uE( z&RlVvXyHQK0&g68J0_@FD6r=RGp;Qy*|qhh+V%w4@meEJC^%}2_6sYYUf$n-FIv*} zh0pwiok34uA4L~HgY&^5(0F#8x(j_aS{WUwcDU-(rIHyA7uB3{X6F5by>It?{aA^9 zy;UX3d9*VBn|{XUZwcTApzYq#r=vhbgI9c-JKL}Nl42Y!`iPY0VXuRT=A{xK5&6-H z2qhu~LaP9r2cOQ+y(r>)B{yP!L}&WGoiC32Rv|N1?Q*0B#eK#Xm&d7Idq~rMd?VU8 zdd*%j#-_}C?yMoPus}|EApYw-v0ezW} zkl!I7Bb4l|ya^kQesI-SE!mdWQ43~2z>Ugen)2IVg@nLGY zhA{eJagyFU@&T$KBh_ACDKSD{?HzOa9EpsQ=d$`GDqH1JuOZh0N#-(2sDcff+NRPH zWm+F=K-lC)zmveJ*D$GqJaefEp2?`P`pNeKka-PR1&~mc4*ha}d)mGQWk$0S)hMd% z6yP@@2$}akX{R88Z$bpm`5G2BpVyTR%5#)jvlj#P9S!u{u`_1l%`X?}&C#QF&AJ*N z{iIj-=ZC&k_U8HQ#a_Cl2M-@g{~R-}-Ja2<7l;5LbL;9nJqahCD_eWuK0SzL zS}P(hQXK+exFt$|hL{Ly!4pT^&9@`YwIh&L5oOkdo|n_rT&VwC;AnVbT`dPq$^rt3 z`(`Io5w#+-CS)Q&Q#r+$qk?l=q((sf#|Zv1dDFGKn}$xFr-`4wA@7--&HR>{kpDLf z^CE8b&PMqS97s3=)aTnNeN{mkQU(TIIW9BbL8}6AoQN(2!H-=$`W!2 qNXP%g>A!?02{{Dd{C{+uioXF3BeZRLa>LpH0000LP9ut;i@;!#D4+f!Gwz^T|AIQh@u8OAci2(7y*fi8*?Wy zF(GWlkWl-3?Q7p_Uq_L(;m|Kx`ntXL_xb($d#^u!VNi9pE1`t8LXf+38z!b7O5me^ z!tBZG#TPJo>#l-hzd$&ENTiiOpeV@ZpI=a1C^Qp>+*ge{RDVD zR5Bq;<;DQi@aDFeQ0F?%y8>XZVUqeI^X5qts=Z^p( z393uq$#?Ym8vwWh@R&T2CLXx;_8<_t9g>I`2|WUe5dk#bOwUDnH1QZJg-AxW8)Ec={*&jkQjarAXfS6IRNgK!$U*H z61<)t)Bw($YKJQLBZ+zG?J4_z?1WyZBby-~d<3)8Ga5kLhX^dIp5u$jwhZydS8GMM znFshZHSQ^da2e8C0DP2i8G0FoWj*m6mm!UU7Z7M%+y8+WMF@N+=#scq5ZgUTDxF+j zE48ZcpB>t`s3OYrbcZ!-y`R4VS1VQ1i<*kUJ=nEl><F@1hGoO=xJpyRh;-S@;Nc+v9S+7cYJ2hhfaR97gH%9+c5(~rupm7fk z3&jCsx-R%yn%Mw?{(uO?3PTn22ZH{9lY;($RY8A%RnQ-xI-31~^~oom13)(G+#m2u zN4G!VxzzAC><^5Nsa4Q}JDxHe{r-SwQOIgrv8(<-(;-yj2ks9*AOtB!$L}B7_@Vj( zqhs3UuQ;eMFn00000NkvXXu0mjf DCbGw* delta 1364 zcmV-a1*`h;2ki=w7YZN<1^@s6b9#F8ks%j<1s_R7K~#90y;t8)TtyWAX4$grvQR~! zg6;AvX`v(pg$9caRbyL|+L$(JiH}VTeKPS6@X`Ok_&`&ik@(OMB#IiagtR39n z5?HnrERfQ|?p`Rndz~})&b@Q*?qW6C?}p6GoipeA&bc!?2QN5|)`oP?wg>0iF5%XH zz&8x95MFig%UC}?zxaht{Z}Q#A;9l16e0*x$i~(>`tJ7rmturpo^`A&Mwu;u*&M16 zw-^bxsBAWhtt1rV_Rb54j?ap|5I{<3AFzFApd>`wO*q8J_ijSBjf(cp^Duct8;IL; z-fRmP6d}5ffIo6rDgm+GbSRx+dGI* zO*J0QC>h8FRY<6;#^?mga@udkHiTeD00L>efASQ1SNby694Mfa$V|e~*1d9>_rgfyqXCTVe*KLNSQD* zpj#Ora_!4u%$X6-wK^lF=!|}U*qU$Y4qF*A#=L!GW&?I#;@ex@h(?w%6t+)#huFJA2T7(`jgki%2D8^aL4g6|2L7K4XpDcC?=S2!GtK#Ie?T@kgIv zXkgF~P{uPbyJHx{pY6h1;Kg&cg&;f3-RB8+DiMKFuL_c+X@17(Gd$O~prsPRA7OrE zQCv(J$B-s}p6$cO4WDA9X8^uHKF)N$i>Ikg9}fxw&Ix3FY7B}g$(`6eTj@Qtd@ z6aGv-#%gpGi}#|!pBq_^p_PUH0W@D~6pIO~{HY98S_@)%+c?Zhc-%=bew>aVma<8R zm>|ig`okjqgq+8JP-a0DA+u3jUSv71sHnuI{sf7<7*cyi()PRw_>uvvtz{Auh}`)7 zhae>pbSIc#j#CiaFBX*!mFHAyP4$n9_aook7g?F&zj9Jg@sS|X{2awL;#Qg^sf65k z?uw}XWJC7rSYmW~Sd?BQgrJH!2pvTiEqfsVi{?ybMI?)VREGdvPb(#ZEre>pjwYw` zgM`c4k&vw-%2^XKW*jhGJWkN3k&Z>Rti_QF6o}n7JQ)>HCUVwAk?5r;bnn0000##6UQx(P84ekPwbuxa!R_@n67rFyZ1!7Y}9;qNo86h#`zL|USMAj@^A{;WV@?gdoJ>+p7MiFm%4pM`ib0iV~H z2snuaiMJ=UomhLLQ=qB>at;vf!KL1Q$kz(C@eKjm(3oy;p)?O}G}Lq10L2J&oYc;* zl*=Y$xzZSb8s6A46Y5OoSyuq;IjoYY6zCIxh~@(auyu4360wwPZ{29%dOHF2p0oP$ebq?FE9&=g?HSo85%9x%R$bjmW8b(17Ze4&)`0r3q6vxN=K%cD=*WmM z1+V6YHGoqmI-uqgfW$0%d&+-*h(Kp1O}=;w=4NI!fOa1uu%dd73zKac;-4=|1-PCA z_%J=`DTHtu(pms~lyDk)8HE)+@z`~Nn}#$BUO=F6?fDPHC_>g` z=1O0+74EFW2na|089sx9L@z82aq`!@!S+7cYGd*hnaR97gH%9+a5(~rupm7fk z3&jDXyU+Prn%Mw?{(uO?3PTn22ZH{9lY;($RY8A%RnQ-x626!~vFN#B#}^Yn<=7wa zONX~V;JGrH#{Gfu3AGBkcgs_T!`~n9ED9OzQS7Qe&~ymZ_`dr=0|-Hi(aF09Hh!S~ z!1%-?a_km{9gi~WpC<%>V!Z delta 1362 zcmV-Y1+Dt>2kQ!u7YZN<1^@s6b9#F8ks%j<1szF5K~#90y;t8)TtyWAX4!VxWdTLN zmfGc4Y@wK@0!=Mx@JE!I)W)<)!<$d0#s`gmfRFwM#s^G&Cd7w^AhDj+@-6=BUb$;O0*ZdEVi}h{ZN@7doRz;f zN>2pU;%4&m0dk^wKL2>T!w~J`d$XAP*v>|EU9IY8or0q03Q=5Cf*Q|%%P3=&@bb5{ zhhIlUnqMh1g-vq}ky8uUjqQm0XWHu=8l5s_IpVj@xr89hNpTg~dRU`{(SrQ6YZ@+181Ce{7ZtqtNwk6^HW zz!FfwGcdDj8N~kBgSXI&ZMKCVE9LGBggf^>pw#PvBx#zTae55f<`xnMap8|JKfEYD zOj*Z}ChyJk;@!sgG2Gn`U*I^dbiRhogAE*KKJoUf^_(< zeIW~Ae0U0@-M@%?Igg>N1*F5BwOx$#JVM>&5dQ=MC%mVyl(x^76tih=h_;NCJLU<$ zrJi6lx{AezQQ^;xoX1dQp|2kot~ZIrgjIf3g{ss7qhJT6tc=H<65}W72#f=pgop`} ze6lYr(oe{H3}qI7bP+NeC6&d>`N@-2*f4)byuiRg(n?y=8;36yz|*JM!~`O@-v1D! zB!a#P88F8w2=14NN{7mGDzzs2#>D;MZyt%PO!C*O6jbtJ5NUpnjP;mGvm}*}8_!)4 zHJoeA{W+ExnHmzM7YQNgVh%%l@ik>H1Yj{UlU)(XBGn;(z% z^`Ar{nGkKLyat``+(OTf-+~o*)XvYU3FUu7)33$N-`P}t6RKTg1YWi|N?%@(n#hB; zS1;;2-+}Bs3=pv^LN{41{GE;5^&I9;-wh~1&X18sd=%1u38o8j2q5zRkvJ#*0-)xw UwEqe^E&u=k07*qoM6N<$f(e+D8UO$Q diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/12.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/12.png index 093cbf10330495082cae8a184e43613f2d3d93d3..061ffac41abd0896c0a323ad0ff85270190f2377 100644 GIT binary patch delta 996 zcmVAp1ol_m~b&3bn##oA&Q#e0Wk!K#stuqxG{GU z6BEKHhJ^aQ*S_|>?sXPf3y1!frLXI2zu))ud+oQc0ffmAWQUT? z;pQ@fbiZ$S07~;KbW8z|1oJ@I79B+pb!$_AVZYlA+UM}<8%~2^hi)Sk>sW*A1O+${ zO@l}{3Y+SNxs^d-1S3ZqVRNej;iD0MP}K^Qeib2?e+k9HD!f~sqn^)Zry!Py!z{?*(BQvM6`~fyVXcKTx9xf$s!Eim(cz z+sA07lg{X+R{8zYJsX!*L>*n7^4Brb%;#xZdZjbB>AqW-Zf-sWFppG!S46Bx6#z7C z2l#AcAXp3b+b%8zCodenc0|c4BFCCgQ9`tu)96PO-WqmcS}m&;a1uIVjrA+(6;ZVu zpM(qqRiT9@m|mfAvq&|B^&gY^N2S|XQyhk`4ng+KGf)nN%*?N<3E6)`EzJ2%5(vw0 zRGZBhiWDzG=}U@H$7e}@Uw03m`GTq=fQD@z+KtKduQ@F1iL|#9QznoAz#DdB^gpHW zKmq_7_t3CZ0zgOmSYNGSN(yyLlEP; z?hhaYEk?)g?AiF9`U4}QkLbQz7=Av=s(A delta 1372 zcmV-i1*7_a2=xk(7YZN<1^@s6b9#F8ks%j<1t&>FK~#90y;t8)TtyWA?%i9KT^8CR zprx|FZfv0>O%tA5#6Q4C{{!O#rat&!2=SpINE9`oq-je) zLx>9n5?EHC6*k4f{$R_p*Ew@{=FYvlOVMb*o6XGJIdjf;&Yd%J)&Rqcp`fX6>bP)! z<}z*$ex~6W!cYf4j1A!WrH@SNKPw^*0zO{_CqWQI);HGBf2Z%iB*XQxsAG8{rr88E z+lETSO-8^?s+f!-D-7~+XZJlZ@Ir<&7K5m$XMRs?U@nB zTze1j>xXtrIN$oZO^~$|fx2pI99>+LYh#Piy$J}z>j`^9k)gz=ZHiO`#~a#zlonh* z@JYlaXQ4S9Mm_;J#C~uPNF3NiQy?t8RlNgiZ%jn+MTa%K(az?WF>5~Jl8;E8=T3Ft z?&JiZYgiti=kLV#mjoySsBQ9wxfkTotG6Qf<-Io6S1H=wQlC5(^CZum$j_^hAQE{y zd?QjM@)0m)1ju;$;}Oi}J)Z4Vo4L(32T<;!UTd@b%*yioKhtV(ZVQzFmZpb&- zuXp(t?;FDVwb~_|dH)r>)D^g*g zBBjdFdeX!7ZDr*9hK?5gqfanAIAjs1Wf7R!vILTUZNn%x@L1bGKs(6Pd$>CHKA_O6 zgoM!?%W?V)kIfCFcT-#+fi@vsOa*C3li$zuoVV3ljbonhFuQ`)*eVw8#<)H=il(8A!oVO-U2fxx39Ia>57x^C3C|`=v1;bG zm0pzZiCxO%b zOiT!)7!pdq*S_|>?zI$I3y1!frS10G@ArLsul?@}gQ~ObaV4}Kg3RUHFh2Q60v{EB zW)I%Yy@runcNN_C3xorRL>dVMih`{D{SEoqTs>jPb=9gv0pjd&H)7tr^ zV$p;wmYe}-;I$2NLY-?p?+SoDhgC9_0(}7x(fPmuY#d*QL@ebxTQ};xn1;`PUl#yE z5>)1Ykk9DvZvfy5z-{70ie%t6+xByBUbPBzLSp%S1)v%l z95j~T_3VHKaOPACRD1%En1|kfj&cYUXiayJH~s{L$tew>*@p_?D!>O*%`(I<->&50 zW)|Sf#F(cL!evOk0Qe~3GW4Er{UrU^KM2Q@cttXVE5OH$~dGW%ZhF+MsWw{D=!bHq{?Mj*>s+H}Xkb>>E2 zL+KA-b#4Ec_$k}|fL}Vg{Q=LVt^&vYz{sfTf*#!Ql;P<22Ry4!LRz~OyXp_r9YPJh z@BRQn5HmV<|G>cy)E^ibeM*kq!m#5}rvIr!f53WiA$ZUq&=vFtL<;%?cG?BEYa3Ef Sd@!8=0000 delta 1376 zcmV-m1)utW2>A+-7YZN<1^@s6b9#F8ks%j<1uIEJK~#90y;p5dTtyUq=I(`kVWBMo z3d-`b+CoVhv^2G(!I#+9q!MG3hWOQ(_LKj>kNyMWhno7C5I-~oiQ)@YLfR705aL3G z1eOh@3Y%hKUszanoHKXl?%cb(SdI1}o0++DW}fGqJ7?~}gun~!A#Q9NxOlc7H%C8z zWq67(^})AOBlz(0N1XakONc{&-(Mv~5R{P3tqly{8Tv29$bCiBv9=sz1p%zUp$l<~ zQE-bYWuw_DLOJdoxP;jBg6vBHq=e1^+jlxjLeje_hZyzTP3X4K(K~Pvc^=sYl7%_5 z)B*-g2*qXE3xjT(ss;0d+){C!Ack?U*}#1NixR zp(UK}e4`*xv=o7cT5B9#S+Uo~R-%UsP{f;w!hs^9#3x;vRRkxRyR;V4KKOBe#ARop zEgVKZ0VLFUa0p0LY@;<8w!PK8gJ`eKMDTf!HN19O%n34UKH{>E*n6Jq?!n#J8Ngsz zn_iO7)cdyuPzF%j>J3XT$fMV8Mex&mU81i`EO<+O@>I=}y>uc!uSbGJcD`hzo!O`+@PT`G+A{(5ZP+Q(kgk7ZvB+Z#LJBWWy7 zEaS7AHxP@iVmwmF2vd2lK;-8KUX}gl%;#m>W*oQYvp9gEo${RU#)JoW(HgJh7G?W; z_JI)Ggvp_MwyiplZ;IdG+AY~Pb??u<{|a8}YsW56p~(!Po;AY90)ixeCrFL_0*@mq zhSn#$W+N=u?|Cv<&&K3_nbe+)fI>@qo82$Uf~uoF_&os}VaHGjuk_Y%(3FQ|my_#g z{gx+=?9r^8J1&Ii2a>{HB*0(o1v3ozQRN^tQ6WOVZ+qFnPGOn8XP9o}j#KVPP!sc}JvlJ6ccs$o+OQ_VebR4*8>3Fg`kF38)tlSlF=)l7H^P z^qF|fwh&}T#P%M!o%@58`R+yZ-ElV#FLeRw=hQ8{5%eK7~0CoKqS465vbqL@* z2mQ;WgitNmcgnf>VZv4INGMeim8}UGvl5KUKPTwbsEuW{oKB(~$dN+d+R3Vjicz*E zR6+v83hQ9j}%wyOmzo;fu{~L0@=C`=Bsr)9i zxX1{iY;%-LzM>%ID1+XY&*&%Lf#L`dvCBg@StO%b zOiT!)7!vCHUi;ejy4P7`Etu?2mbTk#zu))uz4p%+1ftXJQ7y0$fYgN>Ffnys0Us5A zE`T^OwOf6yPbxGBs^sT5^0E!iZ2eHF2*`Z`( zxUnoCJ?$jln1O+${ zO@nYK0$b{)xs^d-1;Y)Eu(e%=P(v7hRJ9Dnp9RR~UP2+i2JcqpY36g8X^6&S@MVRo zfRlKTXmecOi8NO`1&RbvNPub=E_U@orj)H1UlpJUgBby5S7*SD20Aa9pjd&Hllu9E zeBOf07peoW!0VgVggVoD))fGM4zGA30mcHLV)H=&*gU!cu}H#owr`mZ3eEOn)20W<8Pic5>PT5&-zXZi4=!6dp(bz~CMR zmP!EVXg^m|OTK4-dVfF$VuxX>_Xq0z0Vmb_19tsi{ejuX_rNnHlZU)N;A!?Y9Y}uw z78jlWG4WH5{Q4pMi!8&c{ejUj;)3qp^pxT7_Xj+yLQ;QB6uasV)Et5Y-*bNe zA*dN0zq9Y)`|1yjjyqJA23w!56D#S4>;)$Ts#+%a%O~7 P00000NkvXXu0mjf60Xce delta 1368 zcmV-e1*iJ`2k{D!7YZN<1^@s6b9#F8ks%j<1tUpBK~#90y;t8)990zlW_DrOWuYws z3d-_hV++MJ)zZ|GhN#ijq!N>wN_;Xgrat)}_~?INe5k3pxwV1eTSNb)7`d;AI@XpGnj=7SI8-5C zF$}M$QZ|Y$6Uuq-z$GN67v!}RKuYKwX!~wQNl11#;}XO7y@XyH6}Q}$R&6h$ypRgKJlZW_U=5VF}c63IA*$G=8=WfecnPf5>j4UHkv z-iEvLN(9P56%uJ^w=TjBW$lxxO)1#rfP{gUPM*ck+HldDLj}|mS%GkJ;il`^nd`uh zA2=i7Lf6ZVKrvDT8tbfid}YO6n^=h-E`W(QQ_g`RqSVLTid76JTDp}H(mwouQOsj! zp*v_y$AG7y7f4T>^XJ-Ii z!`k$cyvOg~7C;$5V_P7KjfPdc{a!z68*9*UK7zLv9f8e}X7uhl^WNvgpLeb6^~Aay z*?#$U0^1uqm>r2p5d|K35GfLWg$S5-1epAEf45CegiraOwh3GGs8H-H3U3|<_^snt zd;WX=o-Km*BP>oV&IPWx^l`5;SGsmn-ycx zS{Jb*HaT?1w&er)iufH|`_g#@?ESfSUcj?`9oRK9X!b|Ypf#aN3kec`Ly+o)1?~vw z$~D!K{EK3avKFN7o7F;pTTz13YbKm6V0Z$xj#0FOJs8}?A7Cr_MH<$2Fp(!6d!}LTTyOV=y z7|0?mJ!P=>1opO7i(<)~cSTCIqxH0p+;1mm-*4&Zl)v-}#z)620SzJo3p-kL>{F!({`Ad zl)(qj+Yxo1>?-{^W=ziAl&u#DA*f;wLto8h+g=I)m*3)lj)!* zh}>>c3!wgwkO%b zOiT!)7!pdq*S_|>?sXPv3y1#6(sq08_j_O8*Z%p!pz3UUTnTN2Aam&!OiVqLz(<9D z*@L(9uVD1%9R>IO0^tB6kwyZ6q9E&kenEaN=Ozrfu9|hI{3uH%fMmvCL98%TRw&UJ zW-JXzj{8Rjp}eq4_6Y!z;5v|Y_=zHjyuHQ2(C@7Qtz%gAk7PiPL$=YX`CC|Yf&>hR zqChkfgDrW}*ovSqgVBaY*xIf_q#+7_vRsAo?-Jw-uc1_2gAXh7Wae|ZX^1Bi@NI?3 zfSp*7cym(Qi8a?d1|1FvtI3+i0!c}D>3IjoYY6zBy&MAricuz7p~60wxy**a0*#WZ|>`mzWR zlAyBioqR{{zX5<1fZN206v!(D=mB@tr_uv8Pa6Am%g{~XZNRla7bK?NR{*Nvp&_FL zuVn`{fHS9BpyCsN#Ju!&l|#sXKx?{#eDD?&rlvK3W*;(uWr#1Pnli*6->l~0Mi$`n z*?7)>x{ zg~1Jzstf%emHbyG+jvs~My?M-?%fNJj)jbsU*ies|AtD0nYX%t(0-%ZEWuE!ZR4!3 zYDR6JCH>vK?8@g{XPf08g2Y|sn3@j7} z(9wRuRZG5afS^Ah0LVPd;iQ*4x2x&{Gh7f@Y zkzF>FDr|~{{lUVr2Z0?CUv`?nCSg>mYi~?Rea~>n>qa|yO6wpXyMZ)Q&+pcG?+ys95 zz!?dbdM-Nx&5g4e45 z#^Xj}gGXAwv$@N25>e#ggGiBoC`Q1ry_6#-;!|3iO>$CgGiiM#;mw<}_O(5ppNh2M z_xwYiQ&h+wVR>p5pWV8NM0^dC3rerf1c%UO{;taiD`ROyr#%-xVEVG}DX zTkCWya?>OCY+G?4UpIfl>t8yrK33cQZi zZ~KzSAGgj-*MR##Qus>*1nT@yR22c#x=1xtnV~Ag);JFjc*u)y17R$yj<{krx8!PyyP^6Du!SF;NyPJbi zC?Jb8?^HnSo*;JHp+vD_EV?2k+tGUR!~S-2_WkyA-Tb3hFgZS931~7Suw} z5)+8rs{de85<%aDA{18^gbwPt)1mg9TCKUUS-wB@`8|%xocZY$g6dC(P%!f-wUxHR zEJy{!!Si-R-Di5re~tyF=WlcCMM4O&n4>UIcg?nE0TAVXx3nV?O{zDP z1qaT$ogXD!^p1pb9Z|)eP*JP|Gx*O9dNsl^cgxu{DuFiQ%q>q=M^uW6J)sanNSmZ| zEp|oZc9U8J^?w4{T$V?@u@ggY-oVIrUqJDx_Q@~l3B~`0MnKxPS+lAACUkhn2xgP& zYJJUy)IbGB^uKgoKKU+`Mu3PtJM@y3!{6D6Q*Xlf{;LUFko#jK5kC#-zl0(SatR>v d|B<*X{sO}&ptHizAAbM<002ovPDHLkV1hs_lH>pY diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/16.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/16.png index a58d33d1e73d72160b0f786cb3e816717a564f81..9c054691a6849ccadc637a0232f3de29abbe5929 100644 GIT binary patch delta 1005 zcmVAp1ol_m~b&3bn##oA&Q#e0Wk!K#sn}iabxZz zCMJYY3<>ppuYK)%-P^3LEsNQIY5Kap_WQkW-`9TofG9y$Fcg;7>6ii_3C4l4CoU90)ZHBchJ9~0XrIGta4ZRi9l8&ftaCxx1qyH= zng-!e1a{PIb1#EJ4~CC6!Om_8LPx`YpsFP({K!Kl`vUU04S2h@NLRj)o`q;U2A|hB z4^$El5^ag=gGfuYOQ1jig#}RU!-bxHNEb6@^Q!_hqcOwa{Q4X?(NOnA6BIqrdP2WG zpUYX0xqNj1W_WYk3aHa3)4XTQG*fEj@M*b&RPTk0c0JYb|arXmI+dcyF4W_rL2<2=L)fw({Q z01EjH$gWx$5oD|PLo6UKk?nEu868s(c)5C++B@RrxM~`@DHaPXAkqE40x*q^jF>5S zIW?>UoIKtNMV|l^?xnY{9e@Xa+7exK!=E5KGphr%_}~FdLwqrnr6Hbtxt@WmDS!{t zlb%9I(~#8y;G?8z=%p7{jKCW>myo9+^MV%;Xk35(12u{e_)ajS2w4!>J4P#=WJ)i! zitnD(Y+P0mb@g=1U&l=IpXO-kl}z2D$4+5-x%m{pJX2l~u_9Ff(6oJj;G>a&U@bUg zySWtn+p17IqGT0O#hOr20<@ab=tmU(H0;K-T2@O?Nw^Setlvqmh{{#*NytD@C0b~L zxhgbn7O94?{$o;qsdOK0j=|WKQAodj3d*5?8Tn;3A^UHrg}8N-0K)Pc)owF}B1NiD z`jTR_;T@gGKDbgRPPVC zZk&UFM55071D>s3q66s{Qi0tL6AbWb~Z8gVD!Hf!*;ExV|_ItN&-ZQLlxo`BjFWQ zX^b*f67uul@C_v9qTFWzq=fc?ICMKoLbCfAml*lpOXwY=VsQ96iagu~(xq=kr3nIx z5Yt4!A4Ddba@tCwD1w2ydSv#~@Ye-@kj`|$0t);EZF4$qkwnMU&iSASlOIo1+)-Zk#Mth4JV-u-6ermOA%;m zwx8p%m@_UBiytpQ604*Gw=Qto*w#=ses^<^+xhWD_lIby+Z&0Xm;em9A6x=|QZ>8i z2}T@ZA|=A`)et{7cQtVv(CeWI-JX-*<#zjz7Z|vG0jm!aEQrLM8w%s5L!h$>O{aIDOjXhae_Mnn$o9nU0Q_aALu!3=PTfl3l+pvj~ z4;qPO^HuwpK`BBhR_`DBOf z?d6>JU6=a!M@uj@F=-2EwIUF;Qx@qL`!MPZyc9bKic^+(oy~do0fk-_BuObNXc=Cb zJIEeo*dBp4Aze&mX-FTxkB;G^{@a)ynSd`)%W1fMltiuY@#%SgTpH4|%nTBI8sL?5+Ww3cY-_vs-~fgf+|8rL%sdl z+dGgnpChBEaFnxugO)Qg@Z|$|_N<(kK;-uM4<;oMbSD%cTv-r2Y~)IZ%5y5U=Evvw z{q$FlI4bj4bObe?38G--QEDq)^*WXt&s!1oo$s%F9ZSqC+~?AZgb-9Q$6=`9rZb)e zKpwy46_G4bT>?zA0QF{)LZ}uTy6853oN!q?5-L?hRck_jA?yS*`Oghn8floT@D}GyNHkIFmZVwq@CVJmh`d;gQ&qNgrzI9pM`7V@4fQUUFddVu`q%m^WTQGn6 wcG3~#zKj$l;=e=sFCkPxE&)XTKN45OUlhceMHrA#S^xk507*qoM6N<$g7v3_fB*mh diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/17.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/17.png new file mode 100644 index 0000000000000000000000000000000000000000..aafe104caa66037d6ecc0e939c9f052040cce980 GIT binary patch literal 1031 zcmV+i1o-=jP)nX*&JprH6+Kw6QfXE-XLQFfFzg) z${xQ^1W|W)I2ii5XeiEMH9V07(}%o=%l3Cc*$)(8Kr{`)p$P1#+t#}b3L_Y9X@#BL zGK5;fpsHmk{w_c!`w9yAO?bb)L{`3-o`+~W24B~i2vidb5^awggGhU=pFmLuAY`7@+LeEhru5Ky4B{Ed80ufGL=I{@EfC)_g^ zAxs~Vh!_bo0-6;8FU(npRUP8t%p)ikHX*xa?}#8>y&s|pc}0IcExjOP5&h% z+#1*1hHgqsNDRNP094~+W7ZbDni@3#&YbFil1~5%b8pz!jvxZ(5{jSO5*0eqUB_7p<84QVX^K1#X`y^O-DnRr7N67p?Gqu>Punj80j zAf^a`&jeEnw+kYBCrPD~Oc|wC>BF^xNm491B>PUr`g% z{|&Vevu-_su>3}K3T7z1Naadjy%?=}FB$G1WHX;rWdzVzSfOYp)Bonstk8tmAf5m;`vWo%5r(SSA87Ums>#*$XIH+>$ zK_X!d57ZwxjImTJD_kAus6XJExd37M)b0D#boe_(P-uY&H~@|2JPXMp_?DLKY$RV7@fXbw|QOtfyt@I z B#UTIy literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/18.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/18.png new file mode 100644 index 0000000000000000000000000000000000000000..6a7b5a75d821ed3674ccff8e3c563f3961e88fc4 GIT binary patch literal 1029 zcmV+g1p51lP)z0Jbf zvcvvl>Fd_^|9fA**M59K&~>&Wss%O!ki2vYre+>0;G=^4&fCRTFn051INY6qv9)oXdTm&kK2Z^@EjX|Wf+9^=b0fh+= z?ZM@qK1dfcWye z!CU}DG#>c>zi*z2!Sf-15{`Bh*pS~;u1QaMPeJ9`3`_BMy1>iPz!aZ|4gy};P z5hGznKw}Z`!kmX#(IFm9J%&Pl1G1~uiU`u#`yrZ;n5NH)FUgohz{}ao)ZP(i6X8asph}Vk-69D#0c|}Bv zQ~^NK_JGeu3WBxZpzY#P@Sm$f?TC_9L=|g7MG25;4FoJRmZ{Tuy(Mt`8uA8_6un1A{JoM`a&I0LP@Kj74qmvBxZ z!G;Iw4;;o=s^w*&4s_HXaLwEXVfs|>54dj5LE!DE@BV;ivzO>d`UCcatiS$%U#j2! zfalVeVb%V?*o0mM-M`~0L;d#$Jd1+)QgYNEa2-N7KX88lAxJSgIbO4QP5ptfi6>;= zDGYx+%Bp`{=nvR0E(LG&2TV2k12Q%G1C_K7W_KMnA)T-d00000NkvXXu0mjf398RS literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/2.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/2.png index 1c538e539bd0d6a8602af1ee7a35e661d313d541..f6b5880efec011c4a930de4e5228a93886290771 100644 GIT binary patch delta 940 zcmV;d15^C`3CIVK7YaZK1^@s6dyaLkks%j<1A0kBK~#90?U>JN6G0Tm-^Ped8U(fN z$)@>nu&p8%QpD1Oinf9kp-}n{^r|<{{tt==p%+gYJV?-@RY?VFrBp!*Z4|+zF()es zrRi1*S@+HE%+Bo2*3~8}!hB2E-R$hX&wD$Q{q{w{)X_}Rh-^e)G%m z2hSHD!_?(#2JVLiA^^lQag9696Q^d7$lxi6V%;y(PfV@7)32V|Wcsj({D9Y~yw3TvT>}1{{cC zKs=U!Eq#-1Wl&hbcw0McZPy{z76)B_uS4~F1q#KdP${p$tJOu4`9gjclIawDT;(d@ zCmtl(nYMNkox5EERTEH1fT#gy`v)OkD|p853ebVU?11y7IS8WRzHld=LOO_isQdkqw+}5S=-dgSQ`l-T_23 zs4aaa-_hsK0B{B1HgzCNG6kY73j`?^^y-hg5u1q1<)Bn1u#K;F?p6Do_tm+z@?K{(FL^> zLb?p87r=v7{|-<|mth!H@Lh(q3J7639eBN5Y+6vv-1_|o5)>iuonT87-iC>tgT(2e zJx8lmd-GsV!QY!pPUeVu`}^dtW2*V(Ibyv=#;%j&pfCg6cmlxAcpVXcHK_uCVKg|) z;DDeP9CqDY3T~F0_l_u;Bl7ixnie5$&LDrH@X^p46Su6_!B3covv1wWaztMFdO`|< zsuQCLEGrDoOsXyPe^mNUootgGDVV%C0r?jXLE9H$GvDJ0>HmgCjLX}cKxn^FJ+5FV z^YtxQU(<{l;G>~|L4M|c3-VR~1G_SG2b1Y{W9Y0mWxRYc%YXy`K5&qre<_Uz5&$rG z3j<3f0Q6=~glf$^0IEMA198Jpss4cK5BN#-2i#Kq0bZ&xO{=n4Dd*nDM3_l*__@5^92i!N8f~)?3E!7{8N%aR8{PYWIPdI}0Tu|1NvIhCqaA&KKNLCh>zk!N?&{s2|ko1E}AMyQ%Xyl z1VZ69ZG~jJ-5@jtA=%AV3c1dixifR-?!8e&`-NrB-1#}*`R2^MCoK?orMD|ct)*~( z?9G$7So+$-GeisTT)sJhvxQFrvY!yyRo%R94uWqK%epid2u&d>KSI&Xph%p&yxu zq(rDdU=$$djZdd>FO2wJ-i?@lqEohS#&cu7QAqVg`!G}k*4Fsu;utFB2bl8hjcC!) zOZI{>ZK|2ioKhs_=ix-Fk<0le;^sbAOOeqD`JVWFU%h0HfTB0&9$_AhjAM|^9iDR) zq8z#K(B=lt*>!t+dyH+4K(no#hoeH$Go2$J|6ah{8zt0t7sMVVIY#Gyuk%Oe0@*wv zNq&a_kC3xB@rAbA9phGC2bAZk>e`~~?r#@SL#%(L3PKNU_6}279-%{$3s`O!M zx`s6A=fxu4KllMvK}xD!zEENW-|ZT8Y#j-YQjaCsc@&ItvDZ-70#W8XN>YV3tjjs2 zrYPfVT0?|YZe*PVj=hFa75JHRRp=RyD#?z%7YK>hP^&;BM5Tj&tLr6P`3(5*t1Q|( zx0?-m*HifN{^!6tR7(i$r5oaEU9RHnw;y5c(Yjdg zGf&%Pr9^mjBh3|&E>ax=L9oe621|r$!GVMB=35chwId;}B1)_Y zJufAzxj_E8prg?n%WAn%M-nIyYpb19MbwPMnotuV)P+;(>!`@NDN;jF{io6Rvmx8A zo!uBZdIsaa`~dH{l*#<2no$357^GR;;+;+9H(_tY89{tazMaxn6r=$X(Es{jp7{=% z6(Glv2&2y8@ON``)(Zr`{y1p}IRv=lf8z9C!sCP-0_6OEbR3Jn0AM@7;F_@q761SM M07*qoM6N<$f|3_+yZ`_I diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/3.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/3.png index 2cd4e38d3ab3bd24f4ddf3f8cded99b46aed8719..7028b9024594eab2b2611effe9bf3da1d2a74999 100644 GIT binary patch delta 952 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 delta 1297 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 R-v9sr07*p#PDHLkV1koQdh`GQ diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/6.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/6.png index 23a34b59f3af034d9d408f6b46fbbf412edc588a..a7733d59eef299dc7bbd368fe992eceead6d644c 100644 GIT binary patch delta 964 zcmV;#13Uc03g8Ej7YaZK1^@s6dyaLkks%j<1CmKZK~#90?U=!9({L2WUuwm*D}uW1 z>}=~Zu@hyTLFgRqMyEgqz}p;OsB%*mLd2-_6FcC{xb z2xDC)gT=g;{E}aiUuNc-sF3d{Nt$2sKJVrCYrcC?P<6C3sYKQykUM<^#wTw}5TK%e z{K2#N$56O@UBUgJKr8^USTjMO7|806Z%~>mHWG&Jt5zLqU#gM`Aek|E5IYQ$9ZEEY z8_NRHasS95R2PhXx-@&sH%XL1W4O(wxIV z%O+&GvNHe+yt-~qsAKKNJpu67@JeSgpf3Otn-3O%_5Ev*N@P4|>qTcyW#RRI`!@g) z32FJ&7mW#dUj5v!J)(W8KoHsU>gY@x`^F>iGkYd}LSp$X09c|RG(0q9EWr!; zK@H%@p|(IOfJsW+OK)5G3l(U8&vwxVAHnqGlm^fmKm{;Cd@(teA)b7?T!Kp{ufn^D zG5-kE(hHX%^8yf{gv*eskOyC?@K&FAuFH^B!4C)wuKoT41w{yaC+LzaZ^OjaL2^2| zyk@m(uO93mA0$4s*yX2^Qw@;B(%D@-ppo&bz9PDjK{ss%uQQMQ4fjSK|6 z;DGJsLU47d@!k<7azw74P?93#<`nv~3Lg!-F}Y>A4sOCk%zf=nnj><`)e|xhRGo|_ z7+GO(!=&m$|HmZ%lxdr6Nx{g)VJJR-2-3cYG4masko|9{#JIfG3550=)nN;UQeWQ| z>#Lek*WZ%<-adZjTjZ>N00y>YXb&dR@5ZoMuS$6_F=YT*0QkUOg8rc-9>@ZK!F>!Y zlm(!x^F*N5%mEPg2Sgxt7^bj45cUV$6!r(~3i|`R!u|kL*dGY{1OIM+VEooK{|K_# z|DZom7zKZ-kkx*QJ@p3~4xt9$b$zNt=0000N6plGz2#mH6VnvC4eEO zx3o2Z?UtWfh(_FHA%(rpIrq-onY+7ajP@fiGk0dr`OY_I?mdJd@LXfe39W{3>gXAN zTuFRp;RzzF555~8!26vaIMjbqL<|CDWxJRJK@wU1{So@F_x+b-*sf3=^Rp?-Ct&$L zbRt1A5(yeH3f=6Clae_a68eeqU%@E%S_k^fRV%NhmN3czCUlzwgf84ndfk9X4sgE z_W)BD{FZRM`GikUw4^|Fg*#5p&8fYqx#ac)q~qnZpHSo|eW^uTW#jpMEm{kIULU<^ z2iCc0XljCY0T?nL3t^5;Hd$>FY)=69;A}@F=YEUA}ph4 z_JT1PmCQ%Zx+JEiU_>hskJ~EZ{IRZ{<%ox(sGk5Ff?```57@XjI?K30PKgA46FgYP*D(6HoGTqF&#Ba!9+=?gqhH=+Ri?%Dl!VHkjUp%R zQF=L}WR^}P$a$fP=-{E|!k=S_v7d*z^dcezoy>M<-`%O!GXczcGx-&fEK&^uj+4_$ z2A>Gkg6%Jw#Utg;?mZ=Ofr_g}#(38~D_s|n@*hEAClw|Hk$`Ayg# za7GZHZ=>`T1*wA~XnX01p7{py6(Glf2!qZF;qTVStQT;8_-0TMG6?97|B2Io2}>tr h5FqFOqvL`W_zU>F$w2ySD8&E(002ovPDHLkV1g=oi4Fh& diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/7.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/7.png index b94c8eb46095bff8e6479277690b7f5398812c5b..a0f02d6a3791218767e7c38d18c255357d5d5085 100644 GIT binary patch delta 972 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 DHzw5Z delta 1363 zcmV-Z1+4n<2kZ)v7YZN<1^@s6b9#F8ks%j<1s+L6K~#90y;ogNTtygu&axJES!hH) zEVWxcB9>yb2C70DsHPt#*qAnHxbVuv&?^&vfS3ITrWcTUO^6o_!HuE@T0%+*V36cM zTM}5dd{`hwU_YRRJIsj zIvdrswQvpqhsXy_K>U?$RE27#x2ktwZRdoE?k0QK*~sP?nLTfM)|=91^O+`$PE7zl z154v`{GNFJQUIL*YOC^VxfkS7+Z_}4t~Ri~D$(_p`sAsYr}f+kXI_m2iEwy$B2pxr z2v{-#M4tM32#*|(A2sP7lXrH1-<0!>U8anXJ!b6_CmL}30=I5=Ar@W4py{3nzwn-` zXNt+FP=4>KEn;#KnzsUtji&TCbF4X)v_-iflwR22`vh_?ui`ve6<}D??;e0-p*`9_#8cde~~{T7pSi$ zB!yoSpn9mPm*MrS5#F~go2-Rw#eO(RsYZox{n|Bo1TTDg*%t8HtL3O`t-<-uX1sgF z_VU&iP!_1rWB!Fcyzo4GOIS}mm3DsbH*vRn6px4J*&Ew^OQtQ)dmy#52P>UMQDHHv zd~c$}D2JbYFn#RuqRx1K8*|nSJ5XuuewtnT);=l<*APw5VJET8?FyV|I>mqV2nHhq zwtzAgf!Q5zZq_T}p*780^?sUP7M^1dTh=C02T5*^K;F}b zDdQN@a{`#4~Kl!i9;bCI@~#q?v| zr~L&+5EH}G80-0s-^+RoMJ*riez3K2YYtV_WViY?+3BEnn_F`a{K)c zPD&!^o8Z9EPC@8JF|Tx}Jf~7?s(+l{AO7wEkIED~&q7dfMF?qjj^gVH8D>E$AQI18 z5uG|+pZ#+zFfu*FOD_^aP{kaE)}nT~o(sUdGm}{ni6T{h6JS|srDSl0P%YT{j&A%g z;i7gVWUGjB)`W&(A25qwI_S{|!@OEfC6Ei`h}*X<*%eVHa@K@G2qD2pX)L@Va#^H0 zp!!cBwU^>;xU>o#7q6rDm+xWZ`DEs2)P&-{p%qa4X76k&zX>NjWCZs4)=FPikXp!r zmiNx8nXe%}GXg~H@z6__4S%;rWIcoR>kk7`kp3}Ji2n)czl5O*(gYCs|45t_e*wb= V)VU3L?D7Br002ovPDHLkV1fj3j>!N3 diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/9.png b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/9.png index a73e007eb29d71946e3f17a48043a5f98965303b..8ac5c2493414a3ac7136ef6e12dd1226511e0401 100644 GIT binary patch delta 979 zcmV;^11$Xf3hxJy7YaZK1^@s6dyaLkks%j<1EEPoK~#90?U=z&6G0Tl-_{ywX-rh$ zB&DSX0X;}8F%k}Hw3t{gB!r_Eu6pxK{1-4DOt^T`#)D~uC~Ck1Vh9k837|2trS>Ey zCWNvWk}mURcXnrXXUihphQofO+1Yk>-|xMBGy8a9AUfL_*Fu{i$Xvby6H|{AXrRJ> z{K?yeS1@|}o`&N_fd~MRNHc{%F_4YFzac-Lt0xS-FNr#of0h&rK(TD_Aa)ogJCtk= zHI;_3Mj; zf(2P9)&^jPH@2*VI@f;Q699h=uVg9(#sZ*X`5*vn9p8jREall-FS>Fu4WGV$ECGZR zC@=n?-?8U!0^kb3W8y@Ldf?UDgFqN|Xd+@Hj0k9E1kiXrJ(ub6#1kkL*I{PGS`lI9 zJ*wWG^ZNd4#Z1WSK+kktavKuMROpHWyg63=xRvMBfgfyVXyAE;4;z;}WnMOX!~-IKJ^ z$z=6XtNii#p^eKbqOP89`RkZz?&~Zqy)xPR^xP{@u^ zN5fJH09~CI8d{n;0D}I248#t@6!Zsz{(zf;{(xOUe}GreA7Bdl1JeEgEG_wOJ)J(n z{Q=)aysz0G7#$;3(8IgFG93N>fNxPq>szs>{y^O!i17pW2M~fj7Ng@24sHBU{ejW3 zr}W$_3_l-b)jxIU57=)m1rPcIhJyZpOhJFZO?xl3BR(YWB*g##002ovPDHLkV1gp- B%M<_r delta 1379 zcmV-p1)Tct2mA_<7YZN<1^@s6b9#F8ks%j<1ujWMK~#90y;t8$99IY0@B1&saJww(Se{QPjsV5s zkcD`~2zW&mS|hC$gnZoIc?pS$Io{_2ND1YEvg3A?glJn?ml*NfOXyuAqrLMYEFRv5 zGR~a8&;|vP5W_$y7)Dl0+if9H6v0q`Svj&>83fBh(6kH^sW|$Fen5O-5qD?Dx##!$ z1`&zY>cfq;x3YI&ZP%oVo>p_%)xzc&nK`d|)~mM7wv(+Go1O%G z3YI6H@Mq%v+XCnWP+K2pcr)F)5HJgw(WSo3luNQA}16Okfe zMZmBlK;+t&!+2zQ{HWFKF?nZy_EmenrCYTlWR6+8WJLo`U*MbD-AKe2F{C;t!Y{n% z>X~9PDwN;5YKoYeg3DWhmKN3aIDafh%M>FL@+0=wyL`ua1?0W`-aX2_p2;{Xt?in0 zM~HHyXUWM8+;sLGZHU@^`xOYa>059`q1ZE#BcHBzVDh~fGKV^tjToPQqxUcJN8|$a z<%Fd0y95|f4d=Sr@Xlq6@0D>%+b!FI{&JD_cD!_akRGQ)`k1UW&F9aS3f#F(|m-gUH} z>~MR`uE3Gj7rQBxDgplWjQZY^ zyxe7!ifioWB26)i`eWXw{bfoBlOr=2@BNiOD|ie=Eg%eTtZibn?*SUxBJ2tT-t-^B zf@PE-#Jn(4yscz;BF__k&ppFRVg>VK32x7eg2&K~!axis*-4 zpj0xybA0?vk0ZT*>yQvJL6A=ms670HqQ_7~K^7sRQBkwsHeOp>hm`Rg*^)GNx6Pn! z`?Cn-LRekRCngZN`TYkcB@uKdSWsN2AiPt-D;+A&snnVtnBdPxzJ0)>GR?knLQuuQ zFm!f~(#eb+WQA!HaSjfGc4ZWpN*sQweswl&^Hs_M~s;X3+${tils&(8e3no#_2Xaps{**BZY zZ^BUz8G&6+->%Y^6{H4=p#9xba^|~`9|0ovc<3c7guh!OvYx^C<@-Tfkoz){i2n)c lzl0(SatR>v|B<*L{sQ~_(Fy=7kaGY4002ovPDHLkV1gOrs$l>C diff --git a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/meta.json index 8ed018ec41..8d646c4f49 100644 --- a/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/meta.json +++ b/Resources/Textures/Goobstation/Changeling/changeling_chemicals.rsi/meta.json @@ -1,62 +1,86 @@ { - "version": 1, - "license": "CC-BY-SA-3.0", - "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/09a5191fb11aab8ddffe3f9be94292b53e4d96f6/icons/mob/hud/alien_standard.dmi", - "size": { - "x": 32, - "y": 32 - }, - "states": [ - { - "name": "16" - }, - { - "name": "15" - }, - { - "name": "14" - }, - { - "name": "13" - }, - { - "name": "12" - }, - { - "name": "11" - }, - { - "name": "10" - }, - { - "name": "9" - }, - { - "name": "8" - }, - { - "name": "7" - }, - { - "name": "6" - }, - { - "name": "5" - }, - { - "name": "4" - }, - { - "name": "3" - }, - { - "name": "2" - }, - { - "name": "1" - }, - { - "name": "0" - } - ] -} + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from cmss13 at https://github.com/cmss13-devs/cmss13/blob/09a5191fb11aab8ddffe3f9be94292b53e4d96f6/icons/mob/hud/alien_standard.dmi", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "0" + }, + { + "name": "1", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "2", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "3", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "4", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "5", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "6", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "7", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "8", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "9", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "10", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "11", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "12", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "13", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "14", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "15", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "16", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "17", + "delays": [ [ 0.3, 0.1 ] ] + }, + { + "name": "18", + "delays": [ [ 0.3, 0.1 ] ] + } + ] +} \ No newline at end of file From a3a3db57eff7bef46303ed90faeac4b8e176efc3 Mon Sep 17 00:00:00 2001 From: username <113782077+whateverusername0@users.noreply.github.com> Date: Wed, 31 Jul 2024 06:01:54 +1000 Subject: [PATCH 10/20] ling check & guidebook entry (#442) * gaming * real! * real 3! * fuck * great * ungreat * gnomes are here. * g * fuck * raaagh cleanup * fart * ghfghdfghfbvd * boobooboo * RAAAAAAAAAAAAGH * zased --------- Co-authored-by: whateverusername0 --- .../Changeling/ChangelingSystem.cs | 19 ++++-- .../Components/ChangelingRuleComponent.cs | 1 + .../Changeling/guidebook/guides.ftl | 1 + .../changeling/abilities/changeling.ftl | 2 +- .../Entities/Guidebook/changeling.yml | 9 +++ .../Changeling/Guidebook/antagonist.yml | 4 ++ .../Changeling/Objectives/objectiveGroups.yml | 16 ----- .../Changeling/Reagents/biological.yml | 27 ++++++++ .../Changeling/Roles/Antags/changeling.yml | 3 +- Resources/Prototypes/Guidebook/antagonist.yml | 1 + .../Guidebook/Antagonist/Changelings.xml | 61 ++++++++++++++++++ .../guidebook_changeling.rsi/icon.png | Bin 0 -> 4746 bytes .../guidebook_changeling.rsi/meta.json | 15 +++++ 13 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 Resources/Locale/en-US/Goobstation/Changeling/guidebook/guides.ftl create mode 100644 Resources/Prototypes/Goobstation/Changeling/Entities/Guidebook/changeling.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Guidebook/antagonist.yml delete mode 100644 Resources/Prototypes/Goobstation/Changeling/Objectives/objectiveGroups.yml create mode 100644 Resources/Prototypes/Goobstation/Changeling/Reagents/biological.yml create mode 100644 Resources/ServerInfo/Goobstation/Changeling/Guidebook/Antagonist/Changelings.xml create mode 100644 Resources/Textures/Goobstation/Changeling/Guidebook/guidebook_changeling.rsi/icon.png create mode 100644 Resources/Textures/Goobstation/Changeling/Guidebook/guidebook_changeling.rsi/meta.json diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index c648bbce82..a1d37f6ef3 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -320,13 +320,18 @@ public bool TrySting(EntityUid uid, ChangelingComponent comp, EntityTargetAction return false; var target = action.Target; - if (HasComp(target)) + + // can't get his dna if he doesn't have it! + if (!HasComp(target) || HasComp(target)) { - var selfMessage = Loc.GetString("changeling-sting-fail-self", ("target", Identity.Entity(target, EntityManager))); - var targetMessage = Loc.GetString("changeling-sting-fail-ling"); + _popup.PopupEntity(Loc.GetString("changeling-sting-extract-fail"), uid, uid); + return false; + } - _popup.PopupEntity(selfMessage, uid, uid); - _popup.PopupEntity(targetMessage, target, target); + if (HasComp(target)) + { + _popup.PopupEntity(Loc.GetString("changeling-sting-fail-self", ("target", Identity.Entity(target, EntityManager))), uid, uid); + _popup.PopupEntity(Loc.GetString("changeling-sting-fail-ling"), target, target); return false; } if (!overrideMessage) @@ -584,6 +589,8 @@ private void OnStartup(EntityUid uid, ChangelingComponent comp, ref ComponentSta // show alerts UpdateChemicals(uid, comp, 0); UpdateBiomass(uid, comp, 0); + // make their blood unreal + _blood.ChangeBloodReagent(uid, "BloodChangeling"); } private void OnMobStateChange(EntityUid uid, ChangelingComponent comp, ref MobStateChangedEvent args) @@ -604,7 +611,7 @@ private void OnDamageChange(Entity ent, ref DamageChangedEv if (!args.DamageIncreased) return; - + target.Damage.ClampMax(200); // we never die. UNLESS?? } diff --git a/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs b/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs index 47fb15299b..d6435d15d2 100644 --- a/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs +++ b/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs @@ -21,6 +21,7 @@ public sealed partial class ChangelingRuleComponent : Component public readonly List> Objectives = new() { "ChangelingSurviveObjective", + "ChangelingStealDNAObjective", "EscapeIdentityObjective" }; } diff --git a/Resources/Locale/en-US/Goobstation/Changeling/guidebook/guides.ftl b/Resources/Locale/en-US/Goobstation/Changeling/guidebook/guides.ftl new file mode 100644 index 0000000000..75a120878b --- /dev/null +++ b/Resources/Locale/en-US/Goobstation/Changeling/guidebook/guides.ftl @@ -0,0 +1 @@ +guide-entry-changelings = Changelings \ No newline at end of file diff --git a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl index 33af66e772..01343e20b5 100644 --- a/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl +++ b/Resources/Locale/en-US/Goobstation/changeling/abilities/changeling.ftl @@ -20,7 +20,7 @@ changeling-transform-fail-choose = You did not choose a form to transform into! changeling-transform-fail-absorbed = You can't transform a husk! changeling-transform-finish = You are now {$target}. -changeling-sting-fail-self = You tried to sting {CAPITALIZE(THE($target))}, but failed! +changeling-sting-fail-self = You tried to sting {CAPITALIZE(THE($target))}, but something stopped you from doing it! changeling-sting-fail-ling = Someone just tried to silently sting you! changeling-sting = You silently sting {CAPITALIZE(THE($target))} diff --git a/Resources/Prototypes/Goobstation/Changeling/Entities/Guidebook/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Entities/Guidebook/changeling.yml new file mode 100644 index 0000000000..b1c64f0bcb --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Entities/Guidebook/changeling.yml @@ -0,0 +1,9 @@ +- type: entity + id: GuidebookChangelingFluff + name: guidebook changeling + description: you shouldn't be seeing this normally. + noSpawn: true + components: + - type: Sprite + sprite: Goobstation/Changeling/Guidebook/guidebook_changeling.rsi + state: icon \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Guidebook/antagonist.yml b/Resources/Prototypes/Goobstation/Changeling/Guidebook/antagonist.yml new file mode 100644 index 0000000000..e437e355fb --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Guidebook/antagonist.yml @@ -0,0 +1,4 @@ +- type: guideEntry + id: Changelings + name: guide-entry-changelings + text: "/ServerInfo/Goobstation/Changeling/Guidebook/Antagonist/Changelings.xml" \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Objectives/objectiveGroups.yml b/Resources/Prototypes/Goobstation/Changeling/Objectives/objectiveGroups.yml deleted file mode 100644 index e233d8524e..0000000000 --- a/Resources/Prototypes/Goobstation/Changeling/Objectives/objectiveGroups.yml +++ /dev/null @@ -1,16 +0,0 @@ -# changeling - -- type: weightedRandom - id: ChangelingAbsorbObjectiveGroup - weights: - ChangelingAbsorbObjective: 1 - -- type: weightedRandom - id: ChangelingStealDNAObjectiveGroup - weights: - ChangelingStealDNAObjective: 1 - -- type: weightedRandom - id: EscapeIdentityObjectiveGroup - weights: - EscapeIdentityObjective: 1 \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/Reagents/biological.yml b/Resources/Prototypes/Goobstation/Changeling/Reagents/biological.yml new file mode 100644 index 0000000000..fa8bbb9644 --- /dev/null +++ b/Resources/Prototypes/Goobstation/Changeling/Reagents/biological.yml @@ -0,0 +1,27 @@ +- type: reagent + parent: Blood + id: BloodChangeling + +- type: reaction + id: ChangelingBloodBreakdown + source: true + requiredMixerCategories: + - Centrifuge + reactants: + BloodChangeling: + amount: 5 + products: + Water: 11 + Iron: 0.5 + Sugar: 2 + CarbonDioxide: 3 + Protein: 4 + effects: + - !type:CreateEntityReactionEffect + entity: FleshKudzu + - !type:ExplosionReactionEffect + explosionType: Default + maxIntensity: 2 + intensityPerUnit: 0.5 + intensitySlope: 4 + maxTotalIntensity: 1 diff --git a/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml index dc88cecb5d..8f2393c718 100644 --- a/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml +++ b/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml @@ -3,4 +3,5 @@ name: roles-antag-changeling-name antagonist: true setPreference: true - objective: roles-antag-changeling-description \ No newline at end of file + objective: roles-antag-changeling-description + guides: [ Changelings ] \ No newline at end of file diff --git a/Resources/Prototypes/Guidebook/antagonist.yml b/Resources/Prototypes/Guidebook/antagonist.yml index 081ff7ef0a..985e590c4d 100644 --- a/Resources/Prototypes/Guidebook/antagonist.yml +++ b/Resources/Prototypes/Guidebook/antagonist.yml @@ -4,6 +4,7 @@ text: "/ServerInfo/Guidebook/Antagonist/Antagonists.xml" children: - Traitors + - Changelings # goobstation - changelings - NuclearOperatives - Zombies - Revolutionaries diff --git a/Resources/ServerInfo/Goobstation/Changeling/Guidebook/Antagonist/Changelings.xml b/Resources/ServerInfo/Goobstation/Changeling/Guidebook/Antagonist/Changelings.xml new file mode 100644 index 0000000000..789982ad9e --- /dev/null +++ b/Resources/ServerInfo/Goobstation/Changeling/Guidebook/Antagonist/Changelings.xml @@ -0,0 +1,61 @@ + + # Changelings + + + + A Changeling is an alien creature that is intelligent and able to morph into humans. Changelings are aliens in a form of a headslug, but before a shift starts, they either transform as someone while they're off-station or rather they somehow killed someone and made into a Changeling through unknown ways. + The main weapons of the Changeling are its ability to internally synthesize dangerous chemicals, morph into other creatures that it has absorbed, and blend in with humans. + + The changeling can be anyone it's absorbed, it can switch identities instantaneously, only absorbing takes time and peace. Unlike the Traitor, the Changeling's only objective is to survive until the shuttle arrives and escape on it while transformed into somebody else. + + Remember that Changelings are not obliged to work eachother as a team, and some may go solo/rogue depending on how they like it. + + ## I've turned into myself, what do? + + ### Chemicals + Chemicals are your source of abilities. Without them you won't be able to use your powers. + They regenerate slowly over time, and absorption will increase their max capacity. + + ### Biomass + Your biomass is your health. In the beginning you have 30 biomass to begin. You spend 1 biomass each minute, and absorption fully recovers it. + Once your biomass levels get low enough, the effects of your decay will be seen by crew, such as: + - Vomiting blood + - Violently shaking + - Death. + You cannot die normally, as in being gibbed by blunt trauma, but your Biomass is slowly draining away, and if you don't get to absorb someone before it runs out, your game will be over. + + ### DNA Absorption + Your main weapon is deception. Transform into other humanoid creatures to confuse the crew. + To do this, it must take ANY human, living or dead (even thrown away bodies from cloning), and absorb them using either the Absorb abliity, or the DNA Extraction Sting. + You can only have a maximum of 5 DNA strands at a time, and must transform to obtain more. + + Acquiring DNA via absorption requires the victim's incapacitation, critical condition or death. Simply, handcuff, put him into crit or kill him if you need to absorb him. + + - Absorbing someone takes a lot of time, so prepare a safe spot or do it somewhere with the least amount of ears possible. + - Absorbing a victim will recover all of your biomass, increase your maximum chemical capacity and give you bonus evolution points to buy new abilities. + - Absorbing another changeling will, on top of that, increase your maximum biomass capacity, allowing you to stay alive for more time and give you even more chemicals and evolution points. + - [color=red]Absorbed victims cannot be cloned.[/color] On the other hand, they can still be turned into cyborgs. + + ### You exclaim, "I am the only one here!" + Changelings are limited, however, to how much DNA they can absorb at once! If a changeling has 5 DNAs stored and attempts to gain another, they must purge the older DNA by transforming. Eventually, any changeling will have to be a twin of someone else on the station, living or dead. + The changeling can shift its appearance, making them look and sound exactly like a victim of which they have absorbed. This can be massive compromise in security, especially if command staff are absorbed and the changeling is able to imitate them. + Changelings can also, via their lesser form ability, transform into monkeys and do monkey things. + + + ### Regeneration + Also known as Regenerative Stasis, changelings have the ability to 'kill' themselves, and appear dead. After an uncertain amount of time, the changeling can revive at will, fully healed of all injuries and illness. + Entering stasis drains all of the changeling's chemicals, and leaving costs 60. Chemicals will still regenerate while a changeling is dead, meaning it can always enter stasis unless it's biomass levels are critical. + + Changelings can also choose to revive by defibrillator, making it even harder to tell if they are one or not. + + This makes them nigh-unkillable, as they can fully regenerate themselves even from death if they have enough chemicals. Spaced changelings may also be able to make it back on station given enough time. The best way to permanently deal with a changeling is to starve it to death, putting them on mining colonies or solitary confinement. + + ### Going Solo or Teaming Up + Like traitors, changelings operate individually and are in no way obligated to assist each other. It is not required for changelings to even reveal their identity to each other, as it's not uncommon for changelings to backstab each other to remove competition. + Even so, [colopr=red]a coordinated group of changelings is truly a terror to behold[/color]. + + ## Identifying a changeling + Changelings have a different blood type. Even if a changeling is pretending to be a diona, vox or a moth person, they have one blood type. + Also, when put in a centrifuge, the changeling's blood reacts violently. + You cannot identify changelings in any other possible way, unless they're dead obvious and start going loud. + diff --git a/Resources/Textures/Goobstation/Changeling/Guidebook/guidebook_changeling.rsi/icon.png b/Resources/Textures/Goobstation/Changeling/Guidebook/guidebook_changeling.rsi/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..b0a671bf4a99e987809a9645cdef208561101ead GIT binary patch literal 4746 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 literal 0 HcmV?d00001 diff --git a/Resources/Textures/Goobstation/Changeling/Guidebook/guidebook_changeling.rsi/meta.json b/Resources/Textures/Goobstation/Changeling/Guidebook/guidebook_changeling.rsi/meta.json new file mode 100644 index 0000000000..23850a83e4 --- /dev/null +++ b/Resources/Textures/Goobstation/Changeling/Guidebook/guidebook_changeling.rsi/meta.json @@ -0,0 +1,15 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "https://github.com/tgstation/tgstation/blob/8024397cc81c5f47f74cf4279e35728487d0a1a7/icons/mob/human_parts_greyscale.dmi , modified by DrSmugleaf and whateverusername0", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "icon", + "delays": [ [ 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1 ] ] + } + ] +} From 13423f129e2c37d1151a194dd15df2638328286d Mon Sep 17 00:00:00 2001 From: username <113782077+whateverusername0@users.noreply.github.com> Date: Sat, 10 Aug 2024 13:40:25 +1000 Subject: [PATCH 11/20] finally fix ling speed issues (#472) * gaming * real! * real 3! * fuck * great * ungreat * gnomes are here. * g * fuck * raaagh cleanup * fart * ghfghdfghfbvd * boobooboo * RAAAAAAAAAAAAGH * zased * the unholy shitfix * small chaeng --------- Co-authored-by: whateverusername0 --- .../Changeling/ChangelingSystem.Abilities.cs | 5 ++--- .../Goobstation/Changeling/ChangelingSystem.cs | 11 +++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs index 1feee55e81..32114dc8d8 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs @@ -349,18 +349,17 @@ private void ToggleStrainedMuscles(EntityUid uid, ChangelingComponent comp) { if (!comp.StrainedMusclesActive) { - _speed.ChangeBaseSpeed(uid, 125f, 150f, 1f); _popup.PopupEntity(Loc.GetString("changeling-muscles-start"), uid, uid); comp.StrainedMusclesActive = true; } else { - _speed.ChangeBaseSpeed(uid, 100f, 100f, 1f); _popup.PopupEntity(Loc.GetString("changeling-muscles-end"), uid, uid); comp.StrainedMusclesActive = false; } PlayMeatySound(uid, comp); + _speed.RefreshMovementSpeedModifiers(uid); } #endregion @@ -527,7 +526,7 @@ public void OnEphedrineOverdose(EntityUid uid, ChangelingComponent comp, ref Act var reagents = new List<(string, FixedPoint2)>() { - ("Synaptizine", 5f) + ("Desoxyephedrine", 5f) }; if (TryInjectReagents(uid, reagents)) _popup.PopupEntity(Loc.GetString("changeling-inject"), uid, uid); diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index a1d37f6ef3..683d589c8e 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -121,9 +121,19 @@ public override void Initialize() SubscribeLocalEvent(OnDamageChange); SubscribeLocalEvent(OnComponentRemove); + SubscribeLocalEvent(OnRefreshSpeed); + SubscribeAbilities(); } + private void OnRefreshSpeed(Entity ent, ref RefreshMovementSpeedModifiersEvent args) + { + if (ent.Comp.StrainedMusclesActive) + args.ModifySpeed(1.25f, 1.5f); + else + args.ModifySpeed(1f, 1f); + } + public override void Update(float frameTime) { base.Update(frameTime); @@ -221,6 +231,7 @@ private void UpdateBiomass(EntityUid uid, ChangelingComponent comp, float? amoun } private void UpdateAbilities(EntityUid uid, ChangelingComponent comp) { + _speed.RefreshMovementSpeedModifiers(uid); if (comp.StrainedMusclesActive) { var stamina = EnsureComp(uid); From d27ce707c3e51041011ef1ea2ff247d75793b8d1 Mon Sep 17 00:00:00 2001 From: RealFakeSoof <131112412+RealFakeSoof@users.noreply.github.com> Date: Sun, 11 Aug 2024 03:06:39 +0000 Subject: [PATCH 12/20] fixed organic shield (#477) --- .../Changeling/shields.rsi/ling-icon.png | Bin 241 -> 4706 bytes .../shields.rsi/ling-inhand-left.png | Bin 1020 -> 5060 bytes .../shields.rsi/ling-inhand-right.png | Bin 951 -> 5044 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-icon.png b/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-icon.png index 7c8bd9243376a01a126d0898f268d56b5b1724c3..81da793fff738d75134f749841fe195548e1c65f 100644 GIT binary patch literal 4706 zcmeHLeNYo;8eh}{M5NX8#8VuXq<00%CcB%>*Ou^^AkhXQM2bhfve}QYnlF-tgq#Jf z+T-x!L~pcbEr)k%dmcJ)+9|YnN`V%8MO&+_XVof{vGof_ZFSUX@7;vYxz5}$Q~x6~ zo85ih=lQ+Q@Ao|KJ9+a5bMC_FQFEgp2%2up(dUCZEMAe};M*Gr+y%F;fTc*tr+l#6 zw zsapSmK6mCG;=qy5a&mUmM@-)IO0&22pLaH2pLwTPw=0vI+D84ou)6y6r+mrhB^Fus z1A%Itw*35y)f*p4KibU2{$pObI#U(j+TPS`y>afKG`YAf>&7}`spjwX+pfHb_hP@?ux!CMsha@PuyuS^mf4t< zHAD!|*!5DKE~mphYkgk9Uc)mn6;GCRo9B1Rl6s5w7fqSA_rS;d*B)Q|LxVAEYj1Bt z%j~#E%NofR&9$qSk|JMDgHOcd*2?mGUo!4CY{_EkDdtS}?wDNpMTJQaW zueKgv8YcZ<(h76Dw(3^eeO>V(jjsRK>wD7&HaMy`hOJrNaHi?T0R4sYb?AHBrA627 zuQZk1+;%s6;fA=dwO<_TDqpzm-s*qPFS&2r5q-SwktMS^vh6RQsQcIKE^cu3yk7h; z?u{qXw{vf;nj9B5sqa@G{`Q{V*YrE*4;T590}*AbJp1gMR~&?(n58^uup-lQB<*s@ zD8^-FWqyYnbRGnyr1{+xy^DNhuxFmQM&2kAGbP-nSBt<4OoaOScFd-vkC}QyQ z6}Tid3QqAb9GS1r9)tjII!UP@xJkL(=kv*Y3Yp7elVe(~R*vFw97li$;;nQFlpk?= zr6NQKL(h6?5APOumlGB-DXXho&`Bg<93INg;Wn9u;ho+=6@VUcKjoHVGF0wx$VW$b z1w#b@8BFL$BfJ){PUZQm*H!MJSwjWu6r`ge7IJi6W2Rha zG?|AZL z7^_4GoW&4IO%Na(T7}_Ol~SwF2B8?5%yxMk6euU}plqz%?X(34M8U}nvr#9(W$1{- zY^MYc1b{WbI~kYHJ2GV99c+O>iF#scT%k~?luAN{VM?WD#Ho<=ctJ0Us2D0!gd`CQ zLjpQLT1xCx01%7-Y{)DRO9?KI#pSZ=Bw|vq7&$y_0tbW=Yxw0hJ(w3r05U@ zFIB;Y$_21OLv$(Sw6S3S7;M-fJO3liz*w~yOJFL5!3od|1i>L%meU|gOrus1Y7Ar4 zAy&iaUKc0$C=Z)q13Ch&K!XOgf)fTyl^9fQ*xy&migf^zAt>m%pkydPD%BGCS?G3-wSF^eptOyV;xyEK}V%%e)XY?dReZ2JPH6 z2N)v+qsb8Qb>uWis%qFh-v|CGK#ckfOHs#-!s6NvG1Whg4|`+D|r?LFSy;oZrvu z$4>0Pw@7=!CzrheHD%0xWll+7w($1hm_Egv+%-$?UJV~et?ufMbPmYesRQ3Mo@&z; z?ELO1*N`vkNtC5NuU>KU7|))KKawAXOV^*mrN`pKXPsI+lm7ZxcU{G$OZbdMN$qD3 z?FmSi$jXl;1meTquZoZV9>*`%IHeNH)qux*=e8j+2K%@>U3d0S?B3SHGuG_PTYMp} z;BUXVnw~;_+i>Q?g05X~=2}Ofp~{)6+|Sniop#UZnsdLL+dX61{+hh#>brB3 p3=66aXO0%VJ@tY7g_c`S--7xNP>G5Q)*hfGWHjXJ4`!CU{9gq`r!W8j delta 225 zcmV<703QG1B=G@|7=Hu<0001iRABVt@rhub0rRS2BTdnSnQyjo)|)R?6fLvVLuQhjs? zURs_B7RX`zhZ?6taE*>$H30;=nGj$J>VZuN#Jv1wX)+o}5c)EfS=u>uP{kvIVY>zl b>Yw=ncQzG73qCZC00000NkvXXu0mjf4+T_b diff --git a/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-inhand-left.png b/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-inhand-left.png index 5f9c0fb57667501428b218db9dfa7ef7f122eb2c..373bbc8d43198dadc5eae920ba2f664ef0ff62f5 100644 GIT binary patch literal 5060 zcmeHLc~leU7N6ik1jQm&t!PhmdY_(Oj=4$a_BTO$sui& z7J_V-4eEH8N>1;#JN-vH9AO%JN_ zyx-+ArB0Kyp(TIA!-~*pnmNsjqCeSl@SDj~{%8viT-Q@XYhP%H@dNY9&%NwXI@Dtx z8;7xZ$deb_;?~wR56naAvBz1##pSKkqxi(8XBSR;Dx|{{LiKHEP&xo!heD#Msw`%Fx`#Mz6gC++R*ELHEjCHO z&ky9Baf!_4Mm5e+{t&jLFSq4pMXF2dHAi;!$mr^VL4^rdjwAMK5OzdXEgvu)Z+UE1|FYag#3D_8+JvZHh*T+b=ERz{y`JAzHh>)&W?R4y1i zZ0@a}A3PdzC%4k!Z0{kLp7}pHk2h6t&aFL?P_IUhqb;m8*K6$axtPQp&DaU*p@9`9pl6sd`;}oBQUzL3!HU>D}Dj9c~UkIla}E zy7Oz}>w4;Ji(^u{Wt0B9gzXUIGLZrW7N;7A;wA$ZBTO2SYcrTZH8LY`2F01d>NW~4D2VzhcN5bYQW(u!LsGfkO{90n89 zn38FK7zX>CH~tySD%D$fqqTztzz5HUnRx;(pJy=eI$K!juoM8&;m}`NSYtq+@}fwq zDcORPVJW1M_UH^j;BW2C$rimm90KQ&deQ(?t>9Eamm|ZKD)n0nh5{XBFx#yF*B%XOHh6% zeG@@x)Bc+_(>@%(_M}HpR&airUDV!F(PUElQ~RTyvNsclV{Z!-!`nl!VksnH_Y+{X zZ{f2rqmBggqoZKo$f>_5224v5LNOsiWTYHqP#^+jfnfw9!+o_213Ut*K!Mu1;&^qW%Dbz6>MWAU1E37y%MgY#87lEb#Zs8};lmT+GL5eUClNx* z=ObcFC_v;wtp*WmB_fSPM*5O6tgE~Kdw3lc@g_V!9y1W%B-4-gAL(va=rlnAE$uQe znZcmvy`A(Od@(ci1AaQn>;s+vpg$h+Ui^Nf>myz7#lU+hf2^*LbiEe?@1^{)y8dr; zIlX&VB#qz)UMhGKY+3rnBJdX3Q!_SP0qulFL!-^5>Hr|=WsaC^g&=1)=3+r>^9KQ; zBdt`0Io@SELmuMD9qI89 z=T^g-1RdJPlRb9IDcGmMEOU1`w!TV(Wd%91F6QJ}Dw?dlAl605=SBLC>htC~NI*%x zV+I>#)11-OuU9=D{I_K%o39^*)=>A%!7t7@HB20ksP7r-nV!JC?WWTE>{MFs?3Qkx z>X5!cF#6T;^*aXD;MT0iBYXb-^yTy5ni0?P4|yGp*K-%-#|kobRIc>?<@Q;@=yeuI zEDc9@On11VJi4H8E^$*L4%j6pA^2`wRng+gZ)DXjf)QR^lA g)*Mh)*AE7sUEoq>d6WtMHGv>ySfpZm$jteF0)snPSpWb4 delta 998 zcmV-~{Y!t4 zyeavCKhjfk>wnZ)(>p>qB0+%CkRwwwnOHIf6N?2viF*l8eq5g1|1bfV40v(`W(qtx z0rn|^*Kl2~A%xHHb!QaL-yX<*-Ooe7`DX~>{V_%ZzGSo3hJW@O_`W#>IML{MbP~r$ z2m#LXumGUh90q_i(gYzzo^1Ga2PLv3chuM%27r%nUEl~J&1fU??+-TMJ2($!!1*|~ z3_!`Ic5e;k(=kmcfXSxL!&x2x0)XJK0I+5AQdv8!Ah-dLk)o5*aeih2gi`>d6vdp) zqnyVa-2lWw7k?Q5n!~wxEmEEi0S6hPdQ z+8h=DL<%hr(N5bR9APa0KxByKOlot_YM*0@I|WEp8YT-6eE@?$d}c?8oj~sZl1*G# zG2M;;qzM2x35s*^!&M&{K!)J@+70p`kO8zQ0eHJJu73=mryjTgU{qM}mYgo|Jmx&6(J|YE>Tm(0cy_XZ~(}NG3Vt|AstzPsH^H^R%){bKr0KH z7$93A>5n``&z4SGdpPo4b`RPd5&$dWqm6H7z-SIx=Q9DYB6ha%lL2RosMVjz04ri= z8$TIvwuoB&nGCQZcDC`80cVS-)t|`#D`F#z-+xk%c;6N^Ghh!nj0M!3dZY^ge2d#6 zhp{>@wMV#j-Gi)q-Io4d69c%PFO6E{Fc# zG%)~s`E1t|PMZJ#CB1SO4*(@aN|z$YEij6t zSPyFCFjfG#4#+7|x~v1h0O$g|au_QJB}GaX09cs3au_cFTZ)wKDS$~?t<8 diff --git a/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-inhand-right.png b/Resources/Textures/Goobstation/Changeling/shields.rsi/ling-inhand-right.png index 0e91383effae930318ccd6ac069d0f9ddcad8949..9f879f0840bd52e39c1ddd7f7264ec0e0a296f35 100644 GIT binary patch literal 5044 zcmeHKc~leU79VgSMFcnc6!E+HAST&PwFD%O3$r-Dn=+M`%`R-YabeP6;Np7xw~Jn#9RIVYLiS9Qv<-gcN((^@>I(0x?cG5vx1S2NZf`u6N*on&DEO39A=yEX4fdO=yw z`bqJl1KrPWoETev?rhMO;3Yx!z&p!x#XTbzX71dSQj`DvmfD|od=!-KRHVOAyor(K6IUF*+8{57ef;Zkc~qIh5xnMD(TeP!R6Xzr3v=Bsd@o z6=);Xo7bNFRC8_e9ya5-e7RSB=}T2)HLGUbxsbba*Qb^nPOV98_21$Xu}rgjb`O7l_uGLd zra!Y;t7hq6UuVud^2nTR+MMx~b_WD`PhvoWB`d~DNP~_;PzE*4vFVJU^B^cR+-4-m zblL)|=`=IkMfhvqq^h_o$*F-1fCaB2VbP}i7;bESkHVFXG(G~)> z=`!?YiA~CO;Yz@}Q_N+5B$s8OSVWcygJP?es693?xHIT(3eN z&I{pFVni%Jd5BO<2oXGl5+Na|Ml8aG1VIRe-Js-pvxU%;v=a(|a~OaVq7f4UjKmO< zM~e|5j;j%Zr^XNzqeT>oX)s))=>{>u#DK0OGP+0QgrWeHkP^@WF-0H(991Ji3P9l^ zHHCzLwFpBsIA4UjpeRxjV=(CmP)zbVp(-9O5nvKb z1lW_H-SiCWU-+PBD=gg%W1=SN4wcFLJQYX*{1Yj`3QA=P{o5vtJ;M2v~W2%i@4G&qLR1lmqg z7rNP?u~-QcJvI&S2)F_b>f#Cx?kLqKZ%1RDNjvKRC__*&;-pM0!9pZLjLm)j>4`Li z5GP3?~#|_YdW6SLk+v z0$SQ-;A92|J-6$m@8AoR;(dNP+U$M0fZ-2L-ihB2bbX-fofvp0<@cIRj72G0wtK(y3P!%)=8e^4jJ+b_>f4PuSoW zGS^sOkG&kQ{;~GHSKcPC8R%fI-nIE99P8o3I`0CHtbf@&FS81Ipl-h&$x`f9r{@cE zpyZn(kADpJvWMT!LI%G6va)H&EYspIY-d;ngKURKjGV50u=v2r!EOT!R`xyZaj|f4 z#puTDx(15XQqLu-u;zQo4Tb$LPOIJ>J2IxBs?P0~s9audUt1a3;AhmU>_X?}Mxrrg}$%!G2vz}I0el{HSWIJGcWbID!6s)vr(qB*O z{VWh&8&E2jGv5CkuzT$|b3{$f0P1A(%iyw~E4KD8TVLXy>anutUmBF+m?Ob)=Tbc9 zuunHF?bp&AZ+7^vsjBqFQy07}RIh8X^?DvZFg(8D$EhC1Jx6Rid>BP{N%QG-Qa?xQI*G235>nlssx5Xq@EIsi3FE7R%g`TPc@JlDA zc^zuopek)KznWhke=FvGpq}=5UL&~qdg#IRex*x-LZ5AEM%=X(F{X;8n&EKMnFT>l zpZq=GyOjF2yeyxsoBZ&kN?}0xX6>E0-RBn-q*Rm+xHN9(h~#+8v@wP8bKk8Bhy|9D z3JyigNx~90q%aOQY?4#9nLfL4-jh8R&XyD=GUCMD8_F-IPOMZtgSd^a$UEja(A@oj zrETl92VVp={_weW1W>!`(ehBSZ_$(B0hh`r`flB3KmOfhkm4=xS8hIKtW~(LN({Oa Nl1C@Vc0|r7{0|<1MUnsj delta 929 zcmV;S177^JC$|TXB!2;OQb$4nuFf3k000ATNklkx=nQyq1bPU(I04d0a0tUV zgb+T%_njrWew)$yzF&u+>(3Cv`}`YEFyF#3me{%(0&ITT_f_Lcp*?`Kk^obH3xGXaS0SL1CvV3kMT?LN z1P}~qk^UACRfn9dD*&wL`7G}X82#xpS$3)f{w2_ESPZ@yn*;z3-LiF)0iq4f4_zM& zfFb&RsRr$UU^_zDtm~Yun@Ny_2Xe%|Er-+O7vNZ_+n-t~A1u%m0NQrD207_xX)&~IWkL7ulIhqIn(SO@vP2tf_2-qAz zTWp)U*wz&TxQT2JeTaFIW2@fBiG(ZRAP{z|z|=KO-4mrQ?g*I>Ab19Et^2cH&)K^9 z7;B%XsDB7}9B>vO+Mz#X?-^S+0nkH{&GdTXLpjmp@_JlvTVa3&;U~7bzsT?gt^?rh z0q^xY1H3)py?$qaw+Fn}?+ozvfcN^H0p1?)UcWQI+XLR~cLsQSzPN`PhlA!I(*3Hnd=Y<^A zA%E8)^o~%e6^8)b84M5zXDLRIyKyjURV!|dkX2uutr)R-zv;kpQLVUz01e97ijh6l zidz!2AwUjDsW58$fShW@Ed*>_LQBr$Yd0L40iYaU<#ZTb8 Date: Thu, 15 Aug 2024 20:59:53 -0400 Subject: [PATCH 13/20] Update meta.json --- Resources/Textures/Interface/Misc/job_icons.rsi/meta.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json index 95b57d99c5..e845b98728 100644 --- a/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json +++ b/Resources/Textures/Interface/Misc/job_icons.rsi/meta.json @@ -186,9 +186,6 @@ { "name": "InitialInfected" }, - { - "name": "Admin" - }, { "name": "Changeling" } From f1cfa04f6288f4417a11ffe39f32e98b470872e7 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 21:15:31 -0400 Subject: [PATCH 14/20] FIXES --- .../Goobstation/Changeling/ChangelingSystem.cs | 2 +- .../Changeling/ChangelingSystem.Abilities.cs | 2 +- .../Goobstation/Changeling/ChangelingSystem.cs | 6 +++--- .../GameTicking/Rules/ChangelingRuleSystem.cs | 15 ++++++++++----- .../Rules/Components/ChangelingRuleComponent.cs | 3 --- 5 files changed, 15 insertions(+), 13 deletions(-) diff --git a/Content.Client/Goobstation/Changeling/ChangelingSystem.cs b/Content.Client/Goobstation/Changeling/ChangelingSystem.cs index 6d8ee47793..58d949a634 100644 --- a/Content.Client/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Client/Goobstation/Changeling/ChangelingSystem.cs @@ -23,7 +23,7 @@ private void OnUpdateAlert(EntityUid uid, ChangelingComponent comp, ref UpdateAl var stateNormalized = 0f; // hardcoded because uhh umm i don't know. send help. - switch (args.Alert.AlertKey.AlertType) + switch (args.Alert.AlertViewEntity) { case "ChangelingChemicals": stateNormalized = (int) (comp.Chemicals / comp.MaxChemicals * 18); diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs index 32114dc8d8..c60edb2fe4 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs @@ -5,7 +5,7 @@ using Content.Shared.FixedPoint; using Content.Shared.IdentityManagement; using Content.Shared.Mobs; -using Content.Shared.Store.Components; +using Content.Server.Store.Components; using Content.Shared.Popups; using Content.Shared.Damage; using Robust.Shared.Prototypes; diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index 683d589c8e..ca6fc5bfd6 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -14,7 +14,7 @@ using Content.Shared.Mobs; using Content.Shared.Mobs.Systems; using Content.Shared.Nutrition.Components; -using Content.Shared.Store.Components; +using Content.Server.Store.Components; using Robust.Server.Audio; using Robust.Shared.Audio; using Robust.Shared.Random; @@ -50,7 +50,7 @@ using System.Numerics; using Content.Shared.Camera; using Robust.Shared.Timing; -using Content.Shared.Damage.Components; +using Content.Server.Damage.Components; using Content.Server.Gravity; using Content.Shared.Mobs.Components; using Content.Server.Stunnable; @@ -622,7 +622,7 @@ private void OnDamageChange(Entity ent, ref DamageChangedEv if (!args.DamageIncreased) return; - + target.Damage.ClampMax(200); // we never die. UNLESS?? } diff --git a/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs b/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs index 32d44dd530..f70aebbe4d 100644 --- a/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs +++ b/Content.Server/Goobstation/GameTicking/Rules/ChangelingRuleSystem.cs @@ -4,11 +4,11 @@ using Content.Server.Objectives; using Content.Server.Roles; using Content.Shared.Changeling; -using Content.Shared.NPC.Prototypes; -using Content.Shared.NPC.Systems; +using Content.Server.NPC.Components; +using Content.Server.NPC.Systems; using Content.Shared.Roles; using Content.Shared.Store; -using Content.Shared.Store.Components; +using Content.Server.Store.Components; using Robust.Shared.Audio; using Robust.Shared.Prototypes; using System.Text; @@ -99,12 +99,17 @@ private void OnTextPrepend(EntityUid uid, ChangelingRuleComponent comp, ref Obje if (ling.TotalAbsorbedEntities > mostAbsorbed) { mostAbsorbed = ling.TotalAbsorbedEntities; - mostAbsorbedName = _objective.GetTitle((mindId, mind), metaData.EntityName); + mostAbsorbedName = _objective.GetTitle(mindId); + if (mostAbsorbedName is null) + mostAbsorbedName = String.Empty; } if (ling.TotalStolenDNA > mostStolen) { mostStolen = ling.TotalStolenDNA; - mostStolenName = _objective.GetTitle((mindId, mind), metaData.EntityName); + mostStolenName = _objective.GetTitle(mindId); + if (mostStolenName is null) + mostStolenName = String.Empty; + } } diff --git a/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs b/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs index d6435d15d2..2a23375ad5 100644 --- a/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs +++ b/Content.Server/Goobstation/GameTicking/Rules/Components/ChangelingRuleComponent.cs @@ -1,7 +1,4 @@ -using Content.Shared.NPC.Prototypes; -using Content.Shared.Roles; using Content.Shared.Store; -using Robust.Shared.Audio; using Robust.Shared.Prototypes; namespace Content.Server.GameTicking.Rules.Components; From eafaf66f8785e4f2fdd70f62883ae96be86428e5 Mon Sep 17 00:00:00 2001 From: DrSmugleaf <10968691+DrSmugleaf@users.noreply.github.com> Date: Fri, 10 May 2024 17:04:01 -0700 Subject: [PATCH 15/20] Add ActionPerformedEvent, ActionsSystem.SetIfBiggerCooldown, action id to action events and BackgroundOn field (#27682) * Add ActionPerformedEvent and ActionsSystem.SetIfBiggerCooldown * Add action id to action events and backgroundon field to action component --- Content.Client/Actions/ActionsSystem.cs | 3 ++ .../Systems/Actions/ActionUIController.cs | 2 ++ .../Systems/Actions/Controls/ActionButton.cs | 13 ++++++-- .../Actions/ActionOnInteractSystem.cs | 5 +++ Content.Shared/Actions/ActionEvents.cs | 5 +++ Content.Shared/Actions/BaseActionComponent.cs | 8 +++-- .../Actions/Events/ActionPerformedEvent.cs | 8 +++++ Content.Shared/Actions/SharedActionsSystem.cs | 33 +++++++++++++++---- 8 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 Content.Shared/Actions/Events/ActionPerformedEvent.cs diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index b992e77256..b9d554607c 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -248,7 +248,10 @@ public void TriggerAction(EntityUid actionId, BaseActionComponent action) if (action.ClientExclusive) { if (instantAction.Event != null) + { instantAction.Event.Performer = user; + instantAction.Event.Action = actionId; + } PerformAction(user, actions, actionId, instantAction, instantAction.Event, GameTiming.CurTime); } diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index 09663ba82c..7b67d23cec 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -215,6 +215,7 @@ private bool TryTargetWorld(in PointerInputCmdArgs args, EntityUid actionId, Wor { action.Event.Target = coords; action.Event.Performer = user; + action.Event.Action = actionId; } _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime); @@ -249,6 +250,7 @@ private bool TryTargetEntity(in PointerInputCmdArgs args, EntityUid actionId, En { action.Event.Target = entity; action.Event.Performer = user; + action.Event.Action = actionId; } _actionsSystem.PerformAction(user, actionComp, actionId, action, action.Event, _timing.CurTime); diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs index 2372d98f8d..19699696c6 100644 --- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs +++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButton.cs @@ -281,10 +281,19 @@ public void UpdateIcons() _controller ??= UserInterfaceManager.GetUIController(); _spriteSys ??= _entities.System(); - if ((_controller.SelectingTargetFor == ActionId || _action.Toggled) && _action.IconOn != null) - SetActionIcon(_spriteSys.Frame0(_action.IconOn)); + if ((_controller.SelectingTargetFor == ActionId || _action.Toggled)) + { + if (_action.IconOn != null) + SetActionIcon(_spriteSys.Frame0(_action.IconOn)); + + if (_action.BackgroundOn != null) + _buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn); + } else + { SetActionIcon(_action.Icon != null ? _spriteSys.Frame0(_action.Icon) : null); + _buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground"); + } } public void UpdateBackground() diff --git a/Content.Server/Actions/ActionOnInteractSystem.cs b/Content.Server/Actions/ActionOnInteractSystem.cs index c9a5f4b5d0..e226b7803b 100644 --- a/Content.Server/Actions/ActionOnInteractSystem.cs +++ b/Content.Server/Actions/ActionOnInteractSystem.cs @@ -47,7 +47,10 @@ private void OnActivate(EntityUid uid, ActionOnInteractComponent component, Acti var (actId, act) = _random.Pick(options); if (act.Event != null) + { act.Event.Performer = args.User; + act.Event.Action = actId; + } _actions.PerformAction(args.User, null, actId, act, act.Event, _timing.CurTime, false); args.Handled = true; @@ -75,6 +78,7 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, if (entAct.Event != null) { entAct.Event.Performer = args.User; + entAct.Event.Action = entActId; entAct.Event.Target = args.Target.Value; } @@ -100,6 +104,7 @@ private void OnAfterInteract(EntityUid uid, ActionOnInteractComponent component, if (act.Event != null) { act.Event.Performer = args.User; + act.Event.Action = actId; act.Event.Target = args.ClickLocation; } diff --git a/Content.Shared/Actions/ActionEvents.cs b/Content.Shared/Actions/ActionEvents.cs index 72a566b8c8..1f1e07f3aa 100644 --- a/Content.Shared/Actions/ActionEvents.cs +++ b/Content.Shared/Actions/ActionEvents.cs @@ -154,4 +154,9 @@ public abstract partial class BaseActionEvent : HandledEntityEventArgs /// The user performing the action. ///

public EntityUid Performer; + + /// + /// The action that was performed. + /// + public EntityUid Action; } diff --git a/Content.Shared/Actions/BaseActionComponent.cs b/Content.Shared/Actions/BaseActionComponent.cs index 6d9242acc1..57c145a0ec 100644 --- a/Content.Shared/Actions/BaseActionComponent.cs +++ b/Content.Shared/Actions/BaseActionComponent.cs @@ -1,5 +1,4 @@ -using Content.Shared.Mobs; -using Robust.Shared.Audio; +using Robust.Shared.Audio; using Robust.Shared.Serialization; using Robust.Shared.Utility; @@ -25,6 +24,11 @@ public abstract partial class BaseActionComponent : Component ///
[DataField("iconOn")] public SpriteSpecifier? IconOn; + /// + /// For toggle actions only, background to show when toggled on. + /// + [DataField] public SpriteSpecifier? BackgroundOn; + /// /// If not null, this color will modulate the action icon color. /// diff --git a/Content.Shared/Actions/Events/ActionPerformedEvent.cs b/Content.Shared/Actions/Events/ActionPerformedEvent.cs new file mode 100644 index 0000000000..530d7c9335 --- /dev/null +++ b/Content.Shared/Actions/Events/ActionPerformedEvent.cs @@ -0,0 +1,8 @@ +namespace Content.Shared.Actions.Events; + +/// +/// Raised on the action entity when it is used and . +/// +/// The entity that performed this action. +[ByRefEvent] +public readonly record struct ActionPerformedEvent(EntityUid Performer); diff --git a/Content.Shared/Actions/SharedActionsSystem.cs b/Content.Shared/Actions/SharedActionsSystem.cs index 9f3fb96410..538726c89d 100644 --- a/Content.Shared/Actions/SharedActionsSystem.cs +++ b/Content.Shared/Actions/SharedActionsSystem.cs @@ -145,9 +145,6 @@ public bool ResolveActionData( public void SetCooldown(EntityUid? actionId, TimeSpan start, TimeSpan end) { - if (actionId == null) - return; - if (!TryGetActionData(actionId, out var action)) return; @@ -163,9 +160,6 @@ public void SetCooldown(EntityUid? actionId, TimeSpan cooldown) public void ClearCooldown(EntityUid? actionId) { - if (actionId == null) - return; - if (!TryGetActionData(actionId, out var action)) return; @@ -176,6 +170,27 @@ public void ClearCooldown(EntityUid? actionId) Dirty(actionId.Value, action); } + /// + /// Sets the cooldown for this action only if it is bigger than the one it already has. + /// + public void SetIfBiggerCooldown(EntityUid? actionId, TimeSpan? cooldown) + { + if (cooldown == null || + cooldown.Value <= TimeSpan.Zero || + !TryGetActionData(actionId, out var action)) + { + return; + } + + var start = GameTiming.CurTime; + var end = start + cooldown; + if (action.Cooldown?.End > end) + return; + + action.Cooldown = (start, end.Value); + Dirty(actionId.Value, action); + } + public void StartUseDelay(EntityUid? actionId) { if (actionId == null) @@ -439,7 +454,10 @@ private void OnActionRequest(RequestPerformActionEvent ev, EntitySessionEventArg } if (performEvent != null) + { performEvent.Performer = user; + performEvent.Action = actionEnt; + } // All checks passed. Perform the action! PerformAction(user, component, actionEnt, action, performEvent, curTime); @@ -561,6 +579,9 @@ public void PerformAction(EntityUid performer, ActionsComponent? component, Enti if (dirty && component != null) Dirty(performer, component); + + var ev = new ActionPerformedEvent(performer); + RaiseLocalEvent(actionId, ref ev); } #endregion From 11f20d2ddcb1f42dbfc34aa6cc5a0232df5939ac Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 21:24:27 -0400 Subject: [PATCH 16/20] Update AdminVerbSystem.Antags.cs --- Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs index 87840be089..108b4db0fb 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -141,7 +141,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Goobstation/Changeling/changeling_abilities.rsi"), "transform"), Act = () => { - _antag.ForceMakeAntag(targetPlayer, "Changeling"); + _antag.ForceMakeAntag(player, "Changeling"); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-changeling"), From ec44cd00cdea1f8f945a8261a5cff5b601d8ba4f Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 21:49:53 -0400 Subject: [PATCH 17/20] gah --- .../Changeling/ChangelingSystem.cs | 33 +++++++++---------- Content.Shared/Alert/AlertType.cs | 2 ++ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs index ca6fc5bfd6..8b912004ff 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.cs @@ -37,7 +37,6 @@ using Content.Shared.Movement.Systems; using Content.Shared.Damage.Systems; using Content.Shared.Mind; -using Content.Shared.Damage.Components; using Content.Server.Objectives.Components; using Content.Server.Light.EntitySystems; using Content.Shared.Eye.Blinding.Systems; @@ -50,7 +49,7 @@ using System.Numerics; using Content.Shared.Camera; using Robust.Shared.Timing; -using Content.Server.Damage.Components; +using Content.Shared.Damage.Components; using Content.Server.Gravity; using Content.Shared.Mobs.Components; using Content.Server.Stunnable; @@ -174,14 +173,14 @@ private void UpdateChemicals(EntityUid uid, ChangelingComponent comp, float? amo chemicals += amount ?? 1 + comp.BonusChemicalRegen; comp.Chemicals = Math.Clamp(chemicals, 0, comp.MaxChemicals); Dirty(uid, comp); - _alerts.ShowAlert(uid, "ChangelingChemicals"); + _alerts.ShowAlert(uid, AlertType.ChangelingChemicals); } private void UpdateBiomass(EntityUid uid, ChangelingComponent comp, float? amount = null) { comp.Biomass += amount ?? -1; comp.Biomass = Math.Clamp(comp.Biomass, 0, comp.MaxBiomass); Dirty(uid, comp); - _alerts.ShowAlert(uid, "ChangelingBiomass"); + _alerts.ShowAlert(uid, AlertType.ChangelingBiomass); var random = (int) _rand.Next(1, 3); @@ -236,8 +235,7 @@ private void UpdateAbilities(EntityUid uid, ChangelingComponent comp) { var stamina = EnsureComp(uid); _stamina.TakeStaminaDamage(uid, 7.5f, visual: false); - if (_stamina.GetStaminaDamage(uid) >= stamina.CritThreshold - || !HasComp(uid)) + if (stamina.StaminaDamage >= stamina.CritThreshold || _gravity.IsWeightless(uid)) ToggleStrainedMuscles(uid, comp); } } @@ -374,28 +372,29 @@ public bool TryReagentSting(EntityUid uid, ChangelingComponent comp, EntityTarge return true; } - public bool TryToggleItem(EntityUid uid, EntProtoId proto, ref EntityUid? outItem, string? clothingSlot = null) + public bool TryToggleItem(EntityUid uid, EntProtoId proto, ChangelingComponent comp, string? clothingSlot = null) { - if (outItem == null) + if (!comp.Equipment.TryGetValue(proto.Id, out var item) && item == null) { - var item = EntityManager.SpawnEntity(proto, Transform(uid).Coordinates); - if (clothingSlot != null && !_inventory.TryEquip(uid, item, clothingSlot, force: true)) + item = Spawn(proto, Transform(uid).Coordinates); + if (clothingSlot != null && !_inventory.TryEquip(uid, (EntityUid) item, clothingSlot, force: true)) { - EntityManager.DeleteEntity(item); + QueueDel(item); return false; } - else if (!_hands.TryForcePickupAnyHand(uid, item)) + else if (!_hands.TryForcePickupAnyHand(uid, (EntityUid) item)) { _popup.PopupEntity(Loc.GetString("changeling-fail-hands"), uid, uid); - EntityManager.DeleteEntity(item); + QueueDel(item); return false; } - outItem = item; + comp.Equipment.Add(proto.Id, item); return true; } - EntityManager.DeleteEntity(outItem); - outItem = null; + QueueDel(item); + // assuming that it exists + comp.Equipment.Remove(proto.Id); return true; } @@ -632,4 +631,4 @@ private void OnComponentRemove(Entity ent, ref ComponentRem } #endregion -} +} \ No newline at end of file diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index 113c96db04..30887d2070 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -56,6 +56,8 @@ public enum AlertType : byte BorgDead, Charge, Offer, + ChangelingChemicals, + ChangelingBiomass, } } From b2a424b58600268187cccb4241c8bcf91193b264 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 22:06:44 -0400 Subject: [PATCH 18/20] It now actually works --- .../Goobstation/Changeling/ChangelingSystem.Abilities.cs | 3 ++- Resources/Prototypes/Entities/Mobs/Species/base.yml | 3 +-- Resources/Prototypes/Goobstation/GameRules/roundstart.yml | 6 +++--- Resources/Prototypes/Goobstation/game_presets.yml | 6 ------ 4 files changed, 6 insertions(+), 12 deletions(-) diff --git a/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs b/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs index c60edb2fe4..cd9ffaee6b 100644 --- a/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs +++ b/Content.Server/Goobstation/Changeling/ChangelingSystem.Abilities.cs @@ -103,7 +103,8 @@ private void OnAbsorb(EntityUid uid, ChangelingComponent comp, ref AbsorbDNAEven DistanceThreshold = 1.5f, BreakOnDamage = true, BreakOnHandChange = false, - BreakOnMove = true, + BreakOnUserMove = true, + BreakOnTargetMove = true, BreakOnWeightlessMove = true, AttemptFrequency = AttemptFrequency.StartAndEnd }; diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 99258e3bf0..9de5541990 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -200,8 +200,7 @@ type: HumanoidMarkingModifierBoundUserInterface - key: enum.StrippingUiKey.Key type: StrippableBoundUserInterface - # Goobstation - changelings - enum.StoreUiKey.Key: + - key: enum.StoreUiKey.Key type: StoreBoundUserInterface - type: Puller - type: Speech diff --git a/Resources/Prototypes/Goobstation/GameRules/roundstart.yml b/Resources/Prototypes/Goobstation/GameRules/roundstart.yml index eb9cdda8f4..791b2dd41b 100644 --- a/Resources/Prototypes/Goobstation/GameRules/roundstart.yml +++ b/Resources/Prototypes/Goobstation/GameRules/roundstart.yml @@ -18,9 +18,9 @@ mindComponents: - type: ChangelingRole prototype: Changeling - + - type: entity - parent: BaseTraitorRule + parent: Traitor id: CalmTraitor # For Dual Antag Gamemodes components: - type: GameRule @@ -63,7 +63,7 @@ prototype: Changeling - type: entity - parent: BaseNukeopsRule + parent: Nukeops id: Calmops # For Dual Antag Gamemodes components: - type: GameRule diff --git a/Resources/Prototypes/Goobstation/game_presets.yml b/Resources/Prototypes/Goobstation/game_presets.yml index 771b0d6cee..36edaa285b 100644 --- a/Resources/Prototypes/Goobstation/game_presets.yml +++ b/Resources/Prototypes/Goobstation/game_presets.yml @@ -11,7 +11,6 @@ - Changeling - SubGamemodesRule - BasicStationEventScheduler - - GameRuleMeteorScheduler - BasicRoundstartVariation - type: gamePreset @@ -26,7 +25,6 @@ - CalmLing - CalmTraitor - BasicStationEventScheduler - - GameRuleMeteorScheduler - BasicRoundstartVariation - type: gamePreset @@ -42,7 +40,6 @@ - Calmops - CalmTraitor - BasicStationEventScheduler - - GameRuleMeteorScheduler - BasicRoundstartVariation - type: gamePreset @@ -57,7 +54,6 @@ - Calmops - CalmLing - BasicStationEventScheduler - - GameRuleMeteorScheduler - BasicRoundstartVariation - type: gamePreset @@ -73,7 +69,6 @@ - CalmRevs - CalmTraitor - BasicStationEventScheduler - - GameRuleMeteorScheduler - type: gamePreset id: RevLing @@ -87,6 +82,5 @@ - CalmRevs - CalmLing - BasicStationEventScheduler - - GameRuleMeteorScheduler - BasicRoundstartVariation - BasicRoundstartVariation From 3c6d331d22fd21a77402d283f4f52bc183033168 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 22:15:30 -0400 Subject: [PATCH 19/20] More fixes --- Content.Server/Antag/AntagSelectionSystem.cs | 12 +++++++-- .../Components/AntagSelectionComponent.cs | 1 + .../Entities/Markers/Spawners/ghost_roles.yml | 27 +++++++++++++++++++ Resources/Prototypes/GameRules/roundstart.yml | 8 +++--- 4 files changed, 41 insertions(+), 7 deletions(-) diff --git a/Content.Server/Antag/AntagSelectionSystem.cs b/Content.Server/Antag/AntagSelectionSystem.cs index 6bfb7394f5..7f61585add 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -202,16 +202,24 @@ public void ChooseAntags(Entity ent, List /// Whether or not players should be picked to inhabit this antag or not. + /// If no players are left and is set, it will make a ghost role. ///
[DataField] public bool PickPlayer = true; diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index 0b09e0e4c9..f39c6a0f69 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -98,6 +98,33 @@ - sprite: Structures/Wallmounts/signs.rsi state: radiation +- type: entity + noSpawn: true + parent: SpawnPointLoneNukeOperative + id: SpawnPointNukeopsCommander + components: + - type: GhostRole + name: roles-antag-nuclear-operative-commander-name + description: roles-antag-nuclear-operative-commander-objective + +- type: entity + noSpawn: true + parent: SpawnPointLoneNukeOperative + id: SpawnPointNukeopsMedic + components: + - type: GhostRole + name: roles-antag-nuclear-operative-agent-name + description: roles-antag-nuclear-operative-agent-objective + +- type: entity + noSpawn: true + parent: SpawnPointLoneNukeOperative + id: SpawnPointNukeopsOperative + components: + - type: GhostRole + name: roles-antag-nuclear-operative-name + description: roles-antag-nuclear-operative-objective + - type: entity parent: MarkerBase id: SpawnPointGhostDragon diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml index e4bf931f64..7a01f61bf8 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -82,8 +82,7 @@ definitions: - prefRoles: [ NukeopsCommander ] fallbackRoles: [ Nukeops, NukeopsMedic ] - max: 1 - playerRatio: 10 + spawnerPrototype: SpawnPointNukeopsCommander startingGear: SyndicateCommanderGearFull components: - type: NukeOperative @@ -99,8 +98,7 @@ prototype: NukeopsCommander - prefRoles: [ NukeopsMedic ] fallbackRoles: [ Nukeops, NukeopsCommander ] - max: 1 - playerRatio: 10 + spawnerPrototype: SpawnPointNukeopsMedic startingGear: SyndicateOperativeMedicFull components: - type: NukeOperative @@ -116,7 +114,7 @@ prototype: NukeopsMedic - prefRoles: [ Nukeops ] fallbackRoles: [ NukeopsCommander, NukeopsMedic ] - min: 0 + spawnerPrototype: SpawnPointNukeopsOperative max: 3 playerRatio: 10 startingGear: SyndicateOperativeGearFull From ab6bfd55ab73da52107ffe0a50f61cd08d844443 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 15 Aug 2024 22:19:09 -0400 Subject: [PATCH 20/20] more shit to fix --- .../Goobstation/Changeling/Roles/Antags/changeling.yml | 2 +- .../Prototypes/Goobstation/Changeling/StatusIcon/antag.yml | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml b/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml index 8f2393c718..0bfe8dc560 100644 --- a/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml +++ b/Resources/Prototypes/Goobstation/Changeling/Roles/Antags/changeling.yml @@ -4,4 +4,4 @@ antagonist: true setPreference: true objective: roles-antag-changeling-description - guides: [ Changelings ] \ No newline at end of file + # guides: [ Changelings ] #Temporarily commented \ No newline at end of file diff --git a/Resources/Prototypes/Goobstation/Changeling/StatusIcon/antag.yml b/Resources/Prototypes/Goobstation/Changeling/StatusIcon/antag.yml index 3fd6952603..db243ce62d 100644 --- a/Resources/Prototypes/Goobstation/Changeling/StatusIcon/antag.yml +++ b/Resources/Prototypes/Goobstation/Changeling/StatusIcon/antag.yml @@ -1,10 +1,6 @@ - type: statusIcon id: HivemindFaction priority: 11 - showTo: - components: - - ShowAntagIcons - - Hivemind icon: sprite: /Textures/Interface/Misc/job_icons.rsi state: Changeling