From f10a4506fe71b7b275080755f27d62936c486252 Mon Sep 17 00:00:00 2001 From: ThereDrD <88589686+ThereDrD0@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:52:10 +0300 Subject: [PATCH] Adds the Justice Department (#660) * legal departmant * god i wish i knew what i was doing * minor rsi fixes * i forgor the legal radio frequency * Minor fixes to radio, stamp, PDA, etc... * add department hehe * Edits from Velcroboy * Add trial timer * add department signs * Changes all references of legal -> justice and adds prosecutor access * Missed one * Adjust lawyer * update lawyer icon * temp to fix * revert to fix * add airlock texture adds justice department airlock texture. Still need to do the yaml part of it but the texture's there. * fix bureaucracy meta * More meta fixes * Connect airlocks to yaml * Fixes, tweaks, and last reference of "legal" maybe * Add justice to airlock groups * add prosc. drip and fix prosecutor typos adds prosecutor badge and suit, as well as fixes the spelling of "prosecuter" to "prosecutor" * Changes prosecutor's supervisor to CJ Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Attorney <- Lawyer locale edit * Removed modified shoukou file * Added antag objective and touched up yaml Fixed Clerk PDA and CJ jumpsuit error. Additionally moved notary stamp from paper.yml to rubber-stamp.yml, and fixed some indentation in the meta.json of the pda.rsi file * Attorney <- Lawyer, now including the PDA Forgot this last commit * Fixed typo in Clerk Vest ID Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Added Chief Justice locker, all of its contents, and a prosecutor PDA (sprite pending) * lockers 2, electric boogaloo. Committing files I forgot to commit * Added CJ stamp and put it into CJ locker. Fixed notary stamp and put the sprites in the correct RSI * Attempt to resolve merge conflicts * Remove conflict markers * Added stamp sprite, implemented CJ cloak * Implemented Gavel Prototype and added to CJ locker * Removed justice access from attorney as it will be used to secure Clerk's locker. Disabled antag for all justice besides attorney. * Added Clerk locker and placed Notary Stamp into it. Uncommented gavel in CJ locker * Added lawyer's stamp to lawdrobe * Added Prosecutor PDA, icon, and ID sprite * Added gavel block and made gavel tap when clicking on it * Add component, EmitSoundOnInteractUsing onto the gavel block * Added sprites for CJ and Clerk locker * Spell-checked and edited job descriptions slightly * Fixing missing type entity access.yml Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Adjusted rsi paths for gavel and gavel block to use forward slash * Fixed a variety of invalid rsi references, and door remote yaml issue * Added Lawyer access to Justice access group, fixed door_remote.yml access * Remove antagadvantage from chief justice Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Apply suggestions from code review line at the end for readability & matching current standards... line at the end for readability & matching current standards... Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com> Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Hopefully remove shoukou.yml from the changed files Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Once again attempt to remove shoukou.yml from changed files * Attempt once more to remove shoukou.yml from changed files * Fixed justice pda box description Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Please go home, shoukou. We don't want you here * Fixing prototype references pt1 Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Renamed headsets/legal.rsi to headsets/justice.rsi * Added hyphen to chief justice localizations * Ordered justice accesses alphabetically * Added prison key to justice headset * Various yaml fixes from review * Apply suggestions from code review Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com> Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Localized changes to pdas and jobs * Removed indents in locale * Fixed reference to chief justice stamp * Apply suggestions from code review Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com> Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Renamed Lawyer to Attorney in locale, added deltav marker * Lawyer to Attorney, Warrant Document in Clerk and CJ locker * Apply suggestions from code review Co-authored-by: Danger Revolution! <142105406+DangerRevolution@users.noreply.github.com> Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Replaced legal with justice in job-description.ftl Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Moved job-name-lawyer to DeltaV locale * Remove added space in accesses.ftl Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Removed unneeded deletion of unneeded spaces in headsets.yml Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Changed Lawyer ID name to Attorney ID * Updated copyrights in all of the meta.json files affected * Added formal and white variants of CJ jumpsuit to locker * Updated to use upstream's EmitSoundOnInteractUsing * Removing old component stuff * Gave Prosecutor Filled Lawyer Backpack * Deleted old component Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Removed old comments * Updated doors to electronics system, fixed courier spawner that I botched * Fixed door_access indentation Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Adding a little bit of loadouts * Add loadouts for justice * Removed accessreader from windoors * Added job spawner icons * Fixed spawner sprite layering * Added missing parent in ID cards * Removed redundant laceup shoes loadout Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Added lawyer windoor, fixed spawner names, fixed locker names * Added station beacons * Softened justice dept palette Signed-off-by: Timemaster99 <57200767+Timemaster99@users.noreply.github.com> * Added justice key to telecomms * Added deltaV comments to telecomms --------- Signed-off-by: Timemaster99 <57200767+Timemaster99@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/MidRoundAntagRule.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/FreeProberRule.cs | 1 + .../StationEvents/Events/GasLeakRule.cs | 1 + .../Events/GlimmerEventSystem.cs | 1 + .../Events/GlimmerRandomSentienceRule.cs | 1 + .../Events/GlimmerRevenantSpawnRule.cs | 1 + .../Events/GlimmerWispSpawnRule.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/MassMindSwapRule.cs | 1 + .../StationEvents/Events/MeteorSwarmRule.cs | 1 + .../StationEvents/Events/NinjaSpawnRule.cs | 1 + .../StationEvents/Events/NoosphericFryRule.cs | 1 + .../Events/NoosphericStormRule.cs | 1 + .../StationEvents/Events/NoosphericZapRule.cs | 1 + .../Events/PowerGridCheckRule.cs | 1 + .../Events/PsionicCatGotYourTongueRule.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 + .../Zombies/ZombieSystem.Transform.cs | 1 - Content.Server/Zombies/ZombieSystem.cs | 4 + Content.Shared/Antag/AntagAcceptability.cs | 5 + Content.Shared/CCVar/CCVars.cs | 89 +- .../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 | 20 + .../Roles/Jobs/Fun/misc_startinggear.yml | 14 +- Resources/Prototypes/game_presets.yml | 12 - 121 files changed, 4544 insertions(+), 4777 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 de51b2fb192..8512107b69d 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 8087d1833e6..867dcbc2692 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 00000000000..f539daee367 --- /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 1e3f9c9854f..ffaff3b8ded 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 0f665a63de0..5d7ae8efbf4 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 6f10ef9b479..04fd38598fb 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 9849d2df79c..df77a3a1a78 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 00000000000..87873e96d1a --- /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 00000000000..470f98fca1d --- /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 b11c562df5a..6bfb7394f5b 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 00000000000..096be14049a --- /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 00000000000..fcaa4d42672 --- /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 ba09c84bce4..18837b5a7c8 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 62d994dac34..abdc9500202 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 ec9ec770313..6849c508a1f 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 ba042d89662..c5d199164b4 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 b438e7c0e8d..c44864183ab 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 a04f274491c..48a65973491 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 956768bdd99..b9e6fa5d4b8 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 00000000000..de4be83627d --- /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 4484abd4d0b..3234bfff3a0 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 6309b974020..1e6c3f0ab1d 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 4ebe946af4a..f52a3cb296d 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 efda3df0ca1..fa23312268f 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 00000000000..463aecbff54 --- /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 e6966c1e377..fa352eb320b 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 e02d90c18bf..bb1b7c87460 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 358b157cdf3..3d097cd7c79 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 8efd61b4694..f64947e286e 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 1d03b41d773..00000000000 --- 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 2ce3f1f9a66..3b19bbffb6a 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 9dfd6e6627c..01a078625ae 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 62619db76a2..dd359969b6f 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 4fe91e3a5f5..59d1940eafe 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 82ac755592e..78b8a8a85c8 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 a60a2bfe22f..27a9edbad71 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 363c2ad7f75..c167ae7b6c7 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 b775b7af564..01fa387595c 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 01fd97d9a79..3da55e30c9e 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 00000000000..aba9ed9e583 --- /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 e792a004df5..ee3a025533a 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 46040e29450..d06b9fb899c 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 128f1123043..e69de29bb2d 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 b11c28fb2b0..5215da96aa8 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 d20775c7343..7e6901e6c48 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 7755f684be2..f09ed3ebc3c 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 a26a2d783c7..c60670a3ad7 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 fa5f17b4f37..d5adb8fdb78 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 42e7e82335c..4486ee40fbb 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 32f9040f89f..083085fa0d8 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 fc9f0a9a9ff..1cc5e577041 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 5714337d4db..f62d0b79ffb 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!; [Dependency] private readonly GameTicker _gameTicker = default!; @@ -49,67 +38,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(true); - // 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))); } } @@ -139,38 +117,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) @@ -235,81 +195,4 @@ private List GetHealthyHumans(bool includeOffStation = false) } 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 fa263e059de..bc399977358 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/MidRoundAntagRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs index 7abbdcdab3b..94a488bd84b 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/Objectives/ObjectivesSystem.cs b/Content.Server/Objectives/ObjectivesSystem.cs index 20205b8b72f..47fe4eb5f88 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 107d09c8980..0e20f007d71 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 e262fde64d2..c3efe14be96 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 c088d57fd96..0c254c52ac0 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 506fd61d559..75f86187989 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 7ec9a9d7981..fc7a10749a2 100644 --- a/Content.Server/Station/Systems/StationSpawningSystem.cs +++ b/Content.Server/Station/Systems/StationSpawningSystem.cs @@ -173,7 +173,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); } diff --git a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs index 0243a00c9a7..b9eb3b7b09d 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 92911e08584..00000000000 --- 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 4cd94d3e719..98d5aa76a6a 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 b25c1d6561c..29c18976576 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 709b750334e..eef9850e739 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 e7574f27ad5..3b2368556be 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 feb88d9b848..282e28e4991 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 80af23c6fa4..62f01f58fe6 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 dd4473952cb..854ee685b33 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 cd434a721b1..2d129b35584 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/FreeProberRule.cs b/Content.Server/StationEvents/Events/FreeProberRule.cs index 0aa8ecc47cc..a5dfdd6b6ea 100644 --- a/Content.Server/StationEvents/Events/FreeProberRule.cs +++ b/Content.Server/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/StationEvents/Events/GasLeakRule.cs b/Content.Server/StationEvents/Events/GasLeakRule.cs index 68544e416c3..1221612171d 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/GlimmerEventSystem.cs b/Content.Server/StationEvents/Events/GlimmerEventSystem.cs index a3d36ae7157..3e0762c8346 100644 --- a/Content.Server/StationEvents/Events/GlimmerEventSystem.cs +++ b/Content.Server/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/StationEvents/Events/GlimmerRandomSentienceRule.cs b/Content.Server/StationEvents/Events/GlimmerRandomSentienceRule.cs index c086462b409..578f8bf48bd 100644 --- a/Content.Server/StationEvents/Events/GlimmerRandomSentienceRule.cs +++ b/Content.Server/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/StationEvents/Events/GlimmerRevenantSpawnRule.cs b/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs index 8bab321db75..152d6d9fe59 100644 --- a/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs +++ b/Content.Server/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/StationEvents/Events/GlimmerWispSpawnRule.cs b/Content.Server/StationEvents/Events/GlimmerWispSpawnRule.cs index 66eea988aeb..c2cb4eca6d4 100644 --- a/Content.Server/StationEvents/Events/GlimmerWispSpawnRule.cs +++ b/Content.Server/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/StationEvents/Events/ImmovableRodRule.cs b/Content.Server/StationEvents/Events/ImmovableRodRule.cs index a61c6b69e1a..781d0368f47 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 cd3cd63ae86..8361cc6048a 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 3fa12cd4e9f..5b56e03846f 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 4b15e590997..00000000000 --- 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 4fc158f8646..2239db7f701 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/MassMindSwapRule.cs b/Content.Server/StationEvents/Events/MassMindSwapRule.cs index 63944563269..3b90e6204da 100644 --- a/Content.Server/StationEvents/Events/MassMindSwapRule.cs +++ b/Content.Server/StationEvents/Events/MassMindSwapRule.cs @@ -1,6 +1,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Random; using Content.Server.Abilities.Psionics; +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/StationEvents/Events/MeteorSwarmRule.cs b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs index ad56479b379..455011259dc 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 8ad5c8602e3..d9d68a386cf 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/NoosphericFryRule.cs b/Content.Server/StationEvents/Events/NoosphericFryRule.cs index c04543d2195..85f98d6f4be 100644 --- a/Content.Server/StationEvents/Events/NoosphericFryRule.cs +++ b/Content.Server/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/StationEvents/Events/NoosphericStormRule.cs b/Content.Server/StationEvents/Events/NoosphericStormRule.cs index 175318e15bd..19720e68c34 100644 --- a/Content.Server/StationEvents/Events/NoosphericStormRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericStormRule.cs @@ -1,5 +1,6 @@ using Robust.Shared.Random; using Content.Server.Abilities.Psionics; +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/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/StationEvents/Events/NoosphericZapRule.cs index 82c3d72b139..2d0f89fd17c 100644 --- a/Content.Server/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/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/StationEvents/Events/PowerGridCheckRule.cs b/Content.Server/StationEvents/Events/PowerGridCheckRule.cs index 97e89484612..b0a0bbc9fe0 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/PsionicCatGotYourTongueRule.cs b/Content.Server/StationEvents/Events/PsionicCatGotYourTongueRule.cs index 63e0a435cb0..45e8b94fe4b 100644 --- a/Content.Server/StationEvents/Events/PsionicCatGotYourTongueRule.cs +++ b/Content.Server/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/StationEvents/Events/RandomEntityStorageSpawnRule.cs b/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs index c3cd719cc4c..87d50fc8b2a 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 f667ad79750..7b9173241f7 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 c514acc6236..77744d44e46 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 a4ec74b43ba..0370b4ee61d 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 6de8024bd0a..257babd0d2c 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 e263a5f4f69..867f41dcccf 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 cdcf2bf6ff2..c2605039bce 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 aa0c9b214b4..a6c38ef765f 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 ab4bee2f267..473441ccec2 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 15deae25529..e9307effbc6 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 cdaed3f928e..79192f6b496 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 a49b424c53f..1bb0ef28720 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.Transform.cs b/Content.Server/Zombies/ZombieSystem.Transform.cs index c6c71b80345..2b39404ab50 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -60,7 +60,6 @@ public sealed partial class ZombieSystem [Dependency] private readonly MindSystem _mind = default!; [Dependency] private readonly SharedRoleSystem _roles = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; - [Dependency] private readonly ActionsSystem _actions = default!; // DeltaV - No psionic zombies /// /// Handles an entity turning into a zombie when they die or go into crit diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 080bef44e7a..09c8fa26db6 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 98abe713ebe..02d0b5f58fe 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 ff2a915b1ba..e419a67f16e 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -537,91 +537,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 */ @@ -2291,7 +2206,7 @@ public static readonly CVarDef /// public static readonly CVarDef StationGoalsChance = CVarDef.Create("game.station_goals_chance", 0.1f, CVar.SERVERONLY); - + #region CPR System /// @@ -2338,7 +2253,7 @@ public static readonly CVarDef /// public static readonly CVarDef CPRAirlossReductionMultiplier = CVarDef.Create("cpr.airloss_reduction_multiplier", 1f, CVar.REPLICATED | CVar.SERVER); - + #endregion #region Contests System diff --git a/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs b/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs index e7a0eef80ee..08ca204372f 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 ece4b59e91a..ce49f80af3b 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 811387d3750..7e325abe216 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 cdbefece9d6..d19f0ae3e9d 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 e8053e4c678..94ad32164b3 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 55b746f2ce4..ea0898824b6 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 941643dd9a9..00000000000 --- 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 35b6178bd45..88b113d7fdb 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 712dfcf3a06..0b09e0e4c9f 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 ca885117449..284ed006525 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 37fc4b44cde..bb870f6007e 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 0af55a7f9d0..46513376187 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 @@ -154,7 +262,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 26024b44b28..b02ebe67539 100644 --- a/Resources/Prototypes/Roles/Antags/nukeops.yml +++ b/Resources/Prototypes/Roles/Antags/nukeops.yml @@ -40,3 +40,23 @@ department: Command min: 36000 # DeltaV - 10 hours - !type:CharacterWhitelistRequirement + +#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 434a7c1083e..b2bcd8bcb49 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 7d7169bf10a..7d9c9786bb5 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -164,15 +164,3 @@ - Zombie - BasicStationEventScheduler - BasicRoundstartVariation - -- type: gamePreset - id: Pirates - alias: - - pirates - name: pirates-title - description: pirates-description - showInVote: false - rules: - - Pirates - - BasicStationEventScheduler - - BasicRoundstartVariation