diff --git a/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs b/Content.Benchmarks/SpawnEquipDeleteBenchmark.cs index 8512107b69d..de51b2fb192 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); + _spawnSys.EquipStartingGear(_entity, _gear, null); server.EntMan.DeleteEntity(_entity); } }); diff --git a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs index 867dcbc2692..8087d1833e6 100644 --- a/Content.Client/Humanoid/HumanoidAppearanceSystem.cs +++ b/Content.Client/Humanoid/HumanoidAppearanceSystem.cs @@ -118,11 +118,8 @@ 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.Client/Nyanotrasen/Overlays/DogVisionOverlay.cs b/Content.Client/Overlays/DogVisionOverlay.cs similarity index 68% rename from Content.Client/Nyanotrasen/Overlays/DogVisionOverlay.cs rename to Content.Client/Overlays/DogVisionOverlay.cs index 95cfc683e09..91f5521f7ff 100644 --- a/Content.Client/Nyanotrasen/Overlays/DogVisionOverlay.cs +++ b/Content.Client/Overlays/DogVisionOverlay.cs @@ -2,9 +2,9 @@ using Robust.Client.Player; using Robust.Shared.Enums; using Robust.Shared.Prototypes; -using Content.Shared.Abilities; +using Content.Shared.Traits.Assorted.Components; -namespace Content.Client.Nyanotrasen.Overlays; +namespace Content.Client.Overlays; public sealed partial class DogVisionOverlay : Overlay { @@ -23,22 +23,27 @@ public DogVisionOverlay() _dogVisionShader = _prototypeManager.Index("DogVision").Instance().Duplicate(); } + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_playerManager.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + protected override void Draw(in OverlayDrawArgs args) { - if (ScreenTexture == null) + if (ScreenTexture is null) return; - if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} player) - return; - if (!_entityManager.HasComponent(player)) - return; - - _dogVisionShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _dogVisionShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); var worldHandle = args.WorldHandle; var viewport = args.WorldBounds; worldHandle.SetTransform(Matrix3.Identity); worldHandle.UseShader(_dogVisionShader); worldHandle.DrawRect(viewport, Color.White); + worldHandle.UseShader(null); } } diff --git a/Content.Client/Nyanotrasen/Overlays/DogVisionSystem.cs b/Content.Client/Overlays/DogVisionSystem.cs similarity index 59% rename from Content.Client/Nyanotrasen/Overlays/DogVisionSystem.cs rename to Content.Client/Overlays/DogVisionSystem.cs index 2da90e877ed..9eab2e09aff 100644 --- a/Content.Client/Nyanotrasen/Overlays/DogVisionSystem.cs +++ b/Content.Client/Overlays/DogVisionSystem.cs @@ -1,14 +1,16 @@ -using Content.Shared.Abilities; +using Content.Shared.Traits.Assorted.Components; using Content.Shared.DeltaV.CCVars; using Robust.Client.Graphics; using Robust.Shared.Configuration; +using Robust.Shared.Player; -namespace Content.Client.Nyanotrasen.Overlays; +namespace Content.Client.Overlays; public sealed partial class DogVisionSystem : EntitySystem { [Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly ISharedPlayerManager _playerMan = default!; private DogVisionOverlay _overlay = default!; @@ -18,6 +20,8 @@ public override void Initialize() SubscribeLocalEvent(OnDogVisionInit); SubscribeLocalEvent(OnDogVisionShutdown); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); Subs.CVar(_cfg, DCCVars.NoVisionFilters, OnNoVisionFiltersChanged); @@ -26,11 +30,28 @@ public override void Initialize() private void OnDogVisionInit(EntityUid uid, DogVisionComponent component, ComponentInit args) { + if (uid != _playerMan.LocalEntity) + return; + if (!_cfg.GetCVar(DCCVars.NoVisionFilters)) _overlayMan.AddOverlay(_overlay); } private void OnDogVisionShutdown(EntityUid uid, DogVisionComponent component, ComponentShutdown args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerAttached(EntityUid uid, DogVisionComponent component, LocalPlayerAttachedEvent args) + { + if (!_cfg.GetCVar(DCCVars.NoVisionFilters)) + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, DogVisionComponent component, LocalPlayerDetachedEvent args) { _overlayMan.RemoveOverlay(_overlay); } diff --git a/Content.Client/Overlays/SaturationScaleOverlay.cs b/Content.Client/Overlays/SaturationScaleOverlay.cs new file mode 100644 index 00000000000..50656d3bc1e --- /dev/null +++ b/Content.Client/Overlays/SaturationScaleOverlay.cs @@ -0,0 +1,53 @@ +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Content.Shared.Mood; +using Content.Shared.Overlays; + +namespace Content.Client.Overlays; + +public sealed class SaturationScaleOverlay : Overlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] IEntityManager _entityManager = default!; + + public override bool RequestScreenTexture => true; + public override OverlaySpace Space => OverlaySpace.WorldSpace; + private readonly ShaderInstance _shader; + private const float Saturation = 0.5f; + + + public SaturationScaleOverlay() + { + IoCManager.InjectDependencies(this); + + _shader = _prototypeManager.Index("SaturationScale").Instance().Duplicate(); + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_playerManager.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + + + protected override void Draw(in OverlayDrawArgs args) + { + if (ScreenTexture is null) + return; + + _shader.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _shader.SetParameter("saturation", Saturation); + + var handle = args.WorldHandle; + handle.SetTransform(Matrix3.Identity); + handle.UseShader(_shader); + handle.DrawRect(args.WorldBounds, Color.White); + handle.UseShader(null); + } +} diff --git a/Content.Client/Overlays/SaturationScaleSystem.cs b/Content.Client/Overlays/SaturationScaleSystem.cs new file mode 100644 index 00000000000..b5932e3a490 --- /dev/null +++ b/Content.Client/Overlays/SaturationScaleSystem.cs @@ -0,0 +1,63 @@ +using Content.Shared.GameTicking; +using Content.Shared.Mood; +using Content.Shared.Overlays; +using Robust.Client.Graphics; +using Robust.Shared.Player; + +namespace Content.Client.Overlays; + +public sealed class SaturationScaleSystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayMan = default!; + [Dependency] private readonly ISharedPlayerManager _playerMan = default!; + + private SaturationScaleOverlay _overlay = default!; + + + public override void Initialize() + { + base.Initialize(); + + _overlay = new(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnShutdown); + + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); + + SubscribeNetworkEvent(RoundRestartCleanup); + } + + + private void RoundRestartCleanup(RoundRestartCleanupEvent ev) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, SaturationScaleOverlayComponent component, PlayerDetachedEvent args) + { + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerAttached(EntityUid uid, SaturationScaleOverlayComponent component, PlayerAttachedEvent args) + { + _overlayMan.AddOverlay(_overlay); + } + + private void OnShutdown(EntityUid uid, SaturationScaleOverlayComponent component, ComponentShutdown args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnInit(EntityUid uid, SaturationScaleOverlayComponent component, ComponentInit args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.AddOverlay(_overlay); + } +} diff --git a/Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs b/Content.Client/Overlays/UltraVisionOverlay.cs similarity index 68% rename from Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs rename to Content.Client/Overlays/UltraVisionOverlay.cs index 73c05e052b6..fe9317e3650 100644 --- a/Content.Client/DeltaV/Overlays/UltraVisionOverlay.cs +++ b/Content.Client/Overlays/UltraVisionOverlay.cs @@ -2,9 +2,9 @@ using Robust.Client.Player; using Robust.Shared.Enums; using Robust.Shared.Prototypes; -using Content.Shared.Abilities; +using Content.Shared.Traits.Assorted.Components; -namespace Content.Client.DeltaV.Overlays; +namespace Content.Client.Overlays; public sealed partial class UltraVisionOverlay : Overlay { @@ -23,22 +23,27 @@ public UltraVisionOverlay() _ultraVisionShader = _prototypeManager.Index("UltraVision").Instance().Duplicate(); } + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + if (_playerManager.LocalEntity is not { Valid: true } player + || !_entityManager.HasComponent(player)) + return false; + + return base.BeforeDraw(in args); + } + protected override void Draw(in OverlayDrawArgs args) { - if (ScreenTexture == null) + if (ScreenTexture is null) return; - if (_playerManager.LocalPlayer?.ControlledEntity is not {Valid: true} player) - return; - if (!_entityManager.HasComponent(player)) - return; - - _ultraVisionShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture); + _ultraVisionShader.SetParameter("SCREEN_TEXTURE", ScreenTexture); var worldHandle = args.WorldHandle; var viewport = args.WorldBounds; worldHandle.SetTransform(Matrix3.Identity); worldHandle.UseShader(_ultraVisionShader); worldHandle.DrawRect(viewport, Color.White); + worldHandle.UseShader(null); } } diff --git a/Content.Client/DeltaV/Overlays/UltraVisionSystem.cs b/Content.Client/Overlays/UltraVisionSystem.cs similarity index 59% rename from Content.Client/DeltaV/Overlays/UltraVisionSystem.cs rename to Content.Client/Overlays/UltraVisionSystem.cs index b89ffd15263..7728a647848 100644 --- a/Content.Client/DeltaV/Overlays/UltraVisionSystem.cs +++ b/Content.Client/Overlays/UltraVisionSystem.cs @@ -1,14 +1,16 @@ -using Content.Shared.Abilities; +using Content.Shared.Traits.Assorted.Components; using Content.Shared.DeltaV.CCVars; using Robust.Client.Graphics; using Robust.Shared.Configuration; +using Robust.Shared.Player; -namespace Content.Client.DeltaV.Overlays; +namespace Content.Client.Overlays; public sealed partial class UltraVisionSystem : EntitySystem { [Dependency] private readonly IOverlayManager _overlayMan = default!; [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly ISharedPlayerManager _playerMan = default!; private UltraVisionOverlay _overlay = default!; @@ -18,6 +20,8 @@ public override void Initialize() SubscribeLocalEvent(OnUltraVisionInit); SubscribeLocalEvent(OnUltraVisionShutdown); + SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(OnPlayerDetached); Subs.CVar(_cfg, DCCVars.NoVisionFilters, OnNoVisionFiltersChanged); @@ -26,11 +30,28 @@ public override void Initialize() private void OnUltraVisionInit(EntityUid uid, UltraVisionComponent component, ComponentInit args) { + if (uid != _playerMan.LocalEntity) + return; + if (!_cfg.GetCVar(DCCVars.NoVisionFilters)) _overlayMan.AddOverlay(_overlay); } private void OnUltraVisionShutdown(EntityUid uid, UltraVisionComponent component, ComponentShutdown args) + { + if (uid != _playerMan.LocalEntity) + return; + + _overlayMan.RemoveOverlay(_overlay); + } + + private void OnPlayerAttached(EntityUid uid, UltraVisionComponent component, LocalPlayerAttachedEvent args) + { + if (!_cfg.GetCVar(DCCVars.NoVisionFilters)) + _overlayMan.AddOverlay(_overlay); + } + + private void OnPlayerDetached(EntityUid uid, UltraVisionComponent component, LocalPlayerDetachedEvent args) { _overlayMan.RemoveOverlay(_overlay); } diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs deleted file mode 100644 index f539daee367..00000000000 --- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs +++ /dev/null @@ -1,208 +0,0 @@ -/* 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 ffaff3b8ded..1e3f9c9854f 100644 --- a/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/RuleMaxTimeRestartTest.cs @@ -1,6 +1,5 @@ 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 5d7ae8efbf4..0f665a63de0 100644 --- a/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs +++ b/Content.IntegrationTests/Tests/GameRules/SecretStartsTest.cs @@ -17,7 +17,6 @@ public async Task TestSecretStarts() var server = pair.Server; await server.WaitIdleAsync(); - var entMan = server.ResolveDependency(); var gameTicker = server.ResolveDependency().GetEntitySystem(); await server.WaitAssertion(() => @@ -33,7 +32,10 @@ await server.WaitAssertion(() => await server.WaitAssertion(() => { - Assert.That(gameTicker.GetAddedGameRules().Count(), Is.GreaterThan(1), $"No additional rules started by secret rule."); + foreach (var rule in gameTicker.GetAddedGameRules()) + { + Assert.That(gameTicker.GetActiveGameRules(), Does.Contain(rule)); + } // End all rules gameTicker.ClearGameRules(); diff --git a/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs b/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs index 511a720ed07..61dcc3331da 100644 --- a/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs +++ b/Content.IntegrationTests/Tests/Slipping/SlippingTest.cs @@ -37,8 +37,8 @@ public async Task BananaSlipTest() var sprintWalks = sys.Config.GetCVar(CCVars.GamePressToSprint); await SpawnTarget("TrashBananaPeel"); - var modifier = Comp(Player).SprintSpeedModifier; - Assert.That(modifier, Is.EqualTo(1), "Player is not moving at full speed."); + // var modifier = Comp(Player).SprintSpeedModifier; + // Assert.That(modifier, Is.EqualTo(1), "Player is not moving at full speed."); // Yeeting this pointless Assert because it's not actually important. // Player is to the left of the banana peel and has not slipped. #pragma warning disable NUnit2045 diff --git a/Content.Server/Abilities/Mime/MimePowersSystem.cs b/Content.Server/Abilities/Mime/MimePowersSystem.cs index c1d2643d6fa..57163a96a50 100644 --- a/Content.Server/Abilities/Mime/MimePowersSystem.cs +++ b/Content.Server/Abilities/Mime/MimePowersSystem.cs @@ -58,9 +58,6 @@ private void OnComponentInit(EntityUid uid, MimePowersComponent component, Compo EnsureComp(uid); _alertsSystem.ShowAlert(uid, AlertType.VowOfSilence); _actionsSystem.AddAction(uid, ref component.InvisibleWallActionEntity, component.InvisibleWallAction, uid); - //Nyano - Summary: Add Psionic Ability to Mime. - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.InvisibleWallActionEntity; } /// diff --git a/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs index d338a5a5bcb..f2dd82900b6 100644 --- a/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/DispelPowerSystem.cs @@ -1,4 +1,3 @@ -using Content.Shared.Actions; using Content.Shared.StatusEffect; using Content.Shared.Abilities.Psionics; using Content.Shared.Damage; @@ -6,11 +5,8 @@ using Content.Server.Guardian; using Content.Server.Bible.Components; using Content.Server.Popups; -using Robust.Shared.Prototypes; using Robust.Shared.Player; using Robust.Shared.Random; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; using Robust.Shared.Audio.Systems; @@ -18,24 +14,18 @@ namespace Content.Server.Abilities.Psionics { public sealed class DispelPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly DamageableSystem _damageableSystem = default!; [Dependency] private readonly GuardianSystem _guardianSystem = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly SharedAudioSystem _audioSystem = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnDispelled); @@ -46,29 +36,6 @@ public override void Initialize() SubscribeLocalEvent(OnRevenantDispelled); } - private void OnInit(EntityUid uid, DispelPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.DispelActionEntity, component.DispelActionId ); - _actions.TryGetActionData( component.DispelActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.DispelActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.DispelActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, DispelPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.DispelActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(DispelPowerActionEvent args) { if (HasComp(args.Target)) diff --git a/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs index b775117b716..58d7d804da6 100644 --- a/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/MetapsionicPowerSystem.cs @@ -1,58 +1,22 @@ -using Content.Shared.Actions; using Content.Shared.Abilities.Psionics; -using Content.Shared.StatusEffect; using Content.Shared.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class MetapsionicPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPopupSystem _popups = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); } - private void OnInit(EntityUid uid, MetapsionicPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MetapsionicActionEntity, component.MetapsionicActionId ); - _actions.TryGetActionData( component.MetapsionicActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MetapsionicActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.MetapsionicActionEntity; - psionic.ActivePowers.Add(component); - } - - } - - private void OnShutdown(EntityUid uid, MetapsionicPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MetapsionicActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(EntityUid uid, MetapsionicPowerComponent component, MetapsionicPowerActionEvent args) { foreach (var entity in _lookup.GetEntitiesInRange(uid, component.Range)) diff --git a/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs index b23224cab48..780c1488ec6 100644 --- a/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/MindSwapPowerSystem.cs @@ -10,8 +10,6 @@ using Content.Server.Popups; using Content.Server.Psionics; using Content.Server.GameTicking; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; using Content.Shared.Mind; using Content.Shared.Actions.Events; @@ -19,10 +17,8 @@ namespace Content.Server.Abilities.Psionics { public sealed class MindSwapPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly MobStateSystem _mobStateSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; [Dependency] private readonly MindSystem _mindSystem = default!; @@ -31,8 +27,6 @@ public sealed class MindSwapPowerSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnPowerReturned); SubscribeLocalEvent(OnDispelled); @@ -42,28 +36,6 @@ public override void Initialize() SubscribeLocalEvent(OnSwapInit); } - private void OnInit(EntityUid uid, MindSwapPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MindSwapActionEntity, component.MindSwapActionId ); - _actions.TryGetActionData( component.MindSwapActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MindSwapActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.MindSwapActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, MindSwapPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MindSwapActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(MindSwapPowerActionEvent args) { if (!(TryComp(args.Target, out var damageable) && damageable.DamageContainerID == "Biological")) @@ -151,8 +123,6 @@ private void OnSwapInit(EntityUid uid, MindSwappedComponent component, Component _actions.TryGetActionData( component.MindSwapReturnActionEntity, out var actionData ); if (actionData is { UseDelay: not null }) _actions.StartUseDelay(component.MindSwapReturnActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.MindSwapReturnActionEntity; } public void Swap(EntityUid performer, EntityUid target, bool end = false) diff --git a/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs index 0fd261ef12f..fa44639ea8c 100644 --- a/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/NoosphericZapPowerSystem.cs @@ -1,63 +1,27 @@ -using Content.Shared.Actions; using Content.Shared.Abilities.Psionics; -using Content.Server.Psionics; using Content.Shared.StatusEffect; using Content.Server.Stunnable; using Content.Server.Beam; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class NoosphericZapPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly StunSystem _stunSystem = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly BeamSystem _beam = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); } - private void OnInit(EntityUid uid, NoosphericZapPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.NoosphericZapActionEntity, component.NoosphericZapActionId ); - _actions.TryGetActionData( component.NoosphericZapActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.NoosphericZapActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.NoosphericZapActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, NoosphericZapPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.NoosphericZapActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(NoosphericZapPowerActionEvent args) { - if (!HasComp(args.Target)) - return; - if (HasComp(args.Target)) return; diff --git a/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs index 5ca1dc7a6dc..19658629545 100644 --- a/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/PsionicInvisibilityPowerSystem.cs @@ -6,11 +6,6 @@ using Content.Shared.Stealth; using Content.Shared.Stealth.Components; using Content.Server.Psionics; -using Robust.Shared.Prototypes; -using Robust.Shared.Player; -using Robust.Shared.Audio; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; using Robust.Shared.Audio.Systems; @@ -18,20 +13,15 @@ namespace Content.Server.Abilities.Psionics { public sealed class PsionicInvisibilityPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly SharedStunSystem _stunSystem = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly SharedStealthSystem _stealth = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnPowerOff); SubscribeLocalEvent(OnStart); @@ -39,28 +29,6 @@ public override void Initialize() SubscribeLocalEvent(OnDamageChanged); } - private void OnInit(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PsionicInvisibilityActionEntity, component.PsionicInvisibilityActionId ); - _actions.TryGetActionData( component.PsionicInvisibilityActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PsionicInvisibilityActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PsionicInvisibilityActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, PsionicInvisibilityPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PsionicInvisibilityActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(EntityUid uid, PsionicInvisibilityPowerComponent component, PsionicInvisibilityPowerActionEvent args) { if (HasComp(uid)) diff --git a/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs index 097a0cb750b..17e9249e655 100644 --- a/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/PsionicRegenerationPowerSystem.cs @@ -1,69 +1,42 @@ using Robust.Shared.Audio; -using Robust.Server.GameObjects; using Robust.Shared.Player; -using Robust.Shared.Prototypes; using Content.Server.Body.Components; using Content.Server.Body.Systems; -using Content.Server.Chemistry.Containers.EntitySystems; -using Content.Server.Chemistry.EntitySystems; using Content.Server.DoAfter; using Content.Shared.Abilities.Psionics; -using Content.Shared.Actions; using Content.Shared.Chemistry.Components; using Content.Shared.DoAfter; using Content.Shared.FixedPoint; using Content.Shared.Popups; using Content.Shared.Psionics.Events; -using Content.Shared.Tag; using Content.Shared.Examine; using static Content.Shared.Examine.ExamineSystemShared; using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; -using Content.Shared.Chemistry.EntitySystems; using Robust.Server.Audio; namespace Content.Server.Abilities.Psionics { public sealed class PsionicRegenerationPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; - [Dependency] private readonly SolutionContainerSystem _solutionSystem = default!; [Dependency] private readonly BloodstreamSystem _bloodstreamSystem = default!; [Dependency] private readonly AudioSystem _audioSystem = default!; - [Dependency] private readonly TagSystem _tagSystem = default!; [Dependency] private readonly DoAfterSystem _doAfterSystem = default!; [Dependency] private readonly SharedPopupSystem _popupSystem = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; [Dependency] private readonly ExamineSystemShared _examine = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnDispelled); SubscribeLocalEvent(OnDoAfter); } - private void OnInit(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PsionicRegenerationActionEntity, component.PsionicRegenerationActionId ); - _actions.TryGetActionData( component.PsionicRegenerationActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PsionicRegenerationActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PsionicRegenerationActionEntity; - psionic.ActivePowers.Add(component); - } - } private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent component, PsionicRegenerationPowerActionEvent args) { @@ -86,16 +59,6 @@ private void OnPowerUsed(EntityUid uid, PsionicRegenerationPowerComponent compon args.Handled = true; } - private void OnShutdown(EntityUid uid, PsionicRegenerationPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PsionicRegenerationActionEntity); - - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnDispelled(EntityUid uid, PsionicRegenerationPowerComponent component, DispelledEvent args) { if (component.DoAfter == null) diff --git a/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs index 407b72c6b58..3740822667b 100644 --- a/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/PyrokinesisPowerSystem.cs @@ -1,55 +1,22 @@ -using Content.Shared.Actions; using Content.Shared.Abilities.Psionics; using Content.Server.Atmos.Components; using Content.Server.Atmos.EntitySystems; using Content.Server.Popups; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class PyrokinesisPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly FlammableSystem _flammableSystem = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; [Dependency] private readonly PopupSystem _popupSystem = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); } - - private void OnInit(EntityUid uid, PyrokinesisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.PyrokinesisActionEntity, component.PyrokinesisActionId ); - _actions.TryGetActionData( component.PyrokinesisActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.PyrokinesisActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, PyrokinesisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.PyrokinesisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(PyrokinesisPowerActionEvent args) { if (!TryComp(args.Target, out var flammableComponent)) diff --git a/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs b/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs index f7ae04b61ea..7a3f663a43f 100644 --- a/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs +++ b/Content.Server/Abilities/Psionics/Abilities/TelegnosisPowerSystem.cs @@ -1,55 +1,21 @@ -using Content.Shared.Actions; -using Content.Shared.StatusEffect; using Content.Shared.Abilities.Psionics; using Content.Shared.Mind.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Server.Mind; using Content.Shared.Actions.Events; namespace Content.Server.Abilities.Psionics { public sealed class TelegnosisPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly MindSwapPowerSystem _mindSwap = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); SubscribeLocalEvent(OnMindRemoved); } - private void OnInit(EntityUid uid, TelegnosisPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.TelegnosisActionEntity, component.TelegnosisActionId ); - _actions.TryGetActionData( component.TelegnosisActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - { - psionic.PsionicAbility = component.TelegnosisActionEntity; - psionic.ActivePowers.Add(component); - } - } - - private void OnShutdown(EntityUid uid, TelegnosisPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.TelegnosisActionEntity); - if (TryComp(uid, out var psionic)) - { - psionic.ActivePowers.Remove(component); - } - } - private void OnPowerUsed(EntityUid uid, TelegnosisPowerComponent component, TelegnosisPowerActionEvent args) { var projection = Spawn(component.Prototype, Transform(uid).Coordinates); diff --git a/Content.Server/Abilities/Psionics/InnatePsionicPowersComponent.cs b/Content.Server/Abilities/Psionics/InnatePsionicPowersComponent.cs new file mode 100644 index 00000000000..fe9c7511cbc --- /dev/null +++ b/Content.Server/Abilities/Psionics/InnatePsionicPowersComponent.cs @@ -0,0 +1,15 @@ +using Content.Shared.Psionics; +using Robust.Shared.Prototypes; + +namespace Content.Server.Abilities.Psionics +{ + [RegisterComponent] + public sealed partial class InnatePsionicPowersComponent : Component + { + /// + /// The list of all powers to be added on Startup + /// + [DataField] + public List> PowersToAdd = new(); + } +} diff --git a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs index e59696aa904..b6309a64f18 100644 --- a/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs +++ b/Content.Server/Abilities/Psionics/PsionicAbilitiesSystem.cs @@ -1,15 +1,15 @@ using Content.Shared.Abilities.Psionics; using Content.Shared.Actions; +using Content.Shared.Popups; using Content.Shared.Psionics.Glimmer; using Content.Shared.Random; using Content.Shared.Random.Helpers; -using Content.Server.EUI; -using Content.Server.Psionics; -using Content.Server.Mind; using Content.Shared.StatusEffect; using Robust.Shared.Random; using Robust.Shared.Prototypes; -using Robust.Shared.Player; +using Robust.Shared.Utility; +using Content.Shared.Psionics; +using System.Linq; namespace Content.Server.Abilities.Psionics { @@ -18,110 +18,325 @@ public sealed class PsionicAbilitiesSystem : EntitySystem [Dependency] private readonly IComponentFactory _componentFactory = default!; [Dependency] private readonly IRobustRandom _random = default!; [Dependency] private readonly SharedActionsSystem _actionsSystem = default!; - [Dependency] private readonly EuiManager _euiManager = default!; [Dependency] private readonly StatusEffectsSystem _statusEffectsSystem = default!; [Dependency] private readonly GlimmerSystem _glimmerSystem = default!; [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly SharedActionsSystem _actions = default!; + [Dependency] private readonly SharedPopupSystem _popups = default!; - private ISawmill _sawmill = default!; + private ProtoId _pool = "RandomPsionicPowerPool"; + private const string GenericInitializationMessage = "generic-power-initialization-feedback"; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnPlayerAttached); + SubscribeLocalEvent(InnatePowerStartup); + SubscribeLocalEvent(OnPsionicShutdown); } - private void OnPlayerAttached(EntityUid uid, PsionicAwaitingPlayerComponent component, PlayerAttachedEvent args) + /// + /// Special use-case for a InnatePsionicPowers, which allows an entity to start with any number of Psionic Powers. + /// + /// + /// + /// + private void InnatePowerStartup(EntityUid uid, InnatePsionicPowersComponent comp, ComponentStartup args) { - if (TryComp(uid, out var bonus) && bonus.Warn == true) - _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), args.Player); - else - AddRandomPsionicPower(uid); - RemCompDeferred(uid); + // Any entity with InnatePowers should also be psionic, but in case they aren't already... + EnsureComp(uid, out var psionic); + + foreach (var proto in comp.PowersToAdd) + if (!psionic.ActivePowers.Contains(_prototypeManager.Index(proto))) + InitializePsionicPower(uid, _prototypeManager.Index(proto), psionic, false); } - public void AddPsionics(EntityUid uid, bool warn = true) + private void OnPsionicShutdown(EntityUid uid, PsionicComponent component, ComponentShutdown args) { - if (Deleted(uid) - || HasComp(uid)) + if (!EntityManager.EntityExists(uid) + || HasComp(uid)) return; - if (!_mindSystem.TryGetMind(uid, out _, out var mind)) - { - EnsureComp(uid); - return; - } + RemoveAllPsionicPowers(uid); + } - if (!_mindSystem.TryGetSession(mind, out var client)) + /// + /// The most shorthand route to creating a Psion. If an entity is not already psionic, it becomes one. This also adds a random new PsionicPower. + /// To create a "Latent Psychic"(Psion with no powers) just add or ensure the PsionicComponent normally. + /// + /// + public void AddPsionics(EntityUid uid) + { + if (Deleted(uid)) return; - if (warn && HasComp(uid)) - _euiManager.OpenEui(new AcceptPsionicsEui(uid, this), client); - else - AddRandomPsionicPower(uid); + AddRandomPsionicPower(uid); } - public void AddPsionics(EntityUid uid, string powerComp) + /// + /// Pretty straightforward, adds a random psionic power to a given Entity. If that Entity is not already Psychic, it will be made one. + /// If an entity already has all possible powers, this will not add any new ones. + /// + /// + public void AddRandomPsionicPower(EntityUid uid) { - if (Deleted(uid) - || HasComp(uid)) + // We need to EnsureComp here to make sure that we aren't iterating over a component that: + // A: Isn't fully initialized + // B: Is in the process of being shutdown/deleted + // Imagine my surprise when I found out Resolve doesn't check for that. + // TODO: This EnsureComp will be 1984'd in a separate PR, when I rework how you get psionics in the first place. + EnsureComp(uid, out var psionic); + + if (!_prototypeManager.TryIndex(_pool.Id, out var pool)) + return; + + var newPool = pool.Weights.Keys.ToList(); + newPool.RemoveAll(s => + _prototypeManager.TryIndex(s, out var p) && + psionic.ActivePowers.Contains(p)); + + if (newPool is null) return; - AddComp(uid); + var newProto = _random.Pick(newPool); + if (!_prototypeManager.TryIndex(newProto, out var newPower)) + return; - var newComponent = (Component) _componentFactory.GetComponent(powerComp); - newComponent.Owner = uid; + InitializePsionicPower(uid, newPower); - EntityManager.AddComponent(uid, newComponent); + _glimmerSystem.Glimmer += _random.Next(1, (int) Math.Round(1 + psionic.CurrentAmplification + psionic.CurrentDampening)); } - public void AddRandomPsionicPower(EntityUid uid) + /// + /// Initializes a new Psionic Power on a given entity, assuming the entity does not already have said power initialized. + /// + /// + /// + /// + /// + public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic, bool playPopup = true) { - AddComp(uid); + if (!_prototypeManager.HasIndex(proto.ID) + || psionic.ActivePowers.Contains(proto)) + return; - if (!_prototypeManager.TryIndex("RandomPsionicPowerPool", out var pool)) - { - _sawmill.Error("Can't index the random psionic power pool!"); + psionic.ActivePowers.Add(proto); + + AddPsionicActions(uid, proto, psionic); + AddPsionicPowerComponents(uid, proto); + AddPsionicStatSources(proto, psionic); + RefreshPsionicModifiers(uid, psionic); + + if (playPopup) + _popups.PopupEntity(Loc.GetString(GenericInitializationMessage), uid, uid, PopupType.MediumCaution); + // TODO: Replace this with chat message: _popups.PopupEntity(proto.InitializationFeedback, uid, uid, PopupType.MediumCaution); + } + + /// + /// Initializes a new Psionic Power on a given entity, assuming the entity does not already have said power initialized. + /// + /// + /// + /// + /// + public void InitializePsionicPower(EntityUid uid, PsionicPowerPrototype proto, bool playPopup = true) + { + if (!TryComp(uid, out var psionic)) return; - } - // uh oh, stinky! - var newComponent = (Component) _componentFactory.GetComponent(pool.Pick()); - newComponent.Owner = uid; + InitializePsionicPower(uid, proto, psionic, playPopup); + } - EntityManager.AddComponent(uid, newComponent); + /// + /// Updates a Psion's casting stats, call this anytime a system adds a new source of Amp or Damp. + /// + /// + /// + public void RefreshPsionicModifiers(EntityUid uid, PsionicComponent comp) + { + var ampModifier = 0f; + var dampModifier = 0f; + foreach (var (_, source) in comp.AmplificationSources) + ampModifier += source; + foreach (var (_, source) in comp.DampeningSources) + dampModifier += source; - _glimmerSystem.Glimmer += _random.Next(1, 5); + var ev = new OnSetPsionicStatsEvent(ampModifier, dampModifier); + RaiseLocalEvent(uid, ref ev); + ampModifier = ev.AmplificationChangedAmount; + dampModifier = ev.DampeningChangedAmount; + + comp.CurrentAmplification = ampModifier; + comp.CurrentDampening = dampModifier; } - public void RemovePsionics(EntityUid uid) + /// + /// Updates a Psion's casting stats, call this anytime a system adds a new source of Amp or Damp. + /// Variant function for systems that didn't already have the PsionicComponent. + /// + /// + public void RefreshPsionicModifiers(EntityUid uid) + { + if (!TryComp(uid, out var comp)) + return; + + RefreshPsionicModifiers(uid, comp); + } + + /// + /// A more advanced form of removing powers. Mindbreaking not only removes all psionic powers, + /// it also disables the possibility of obtaining new ones. + /// + /// + public void MindBreak(EntityUid uid) + { + RemoveAllPsionicPowers(uid, true); + } + + /// + /// Remove all Psionic powers, with accompanying actions, components, and casting stat sources, from a given Psion. + /// Optionally, the Psion can also be rendered permanently non-Psionic. + /// + /// + /// + public void RemoveAllPsionicPowers(EntityUid uid, bool mindbreak = false) { if (!TryComp(uid, out var psionic) || !psionic.Removable) return; - if (!_prototypeManager.TryIndex("RandomPsionicPowerPool", out var pool)) + RemovePsionicActions(uid, psionic); + + var newPsionic = psionic.ActivePowers.ToList(); + foreach (var proto in newPsionic) + { + if (!_prototypeManager.TryIndex(proto.ID, out var power)) + continue; + + RemovePsionicPowerComponents(uid, proto); + + // If we're mindbreaking, we can skip the casting stats since the PsionicComponent is getting 1984'd. + if (!mindbreak) + RemovePsionicStatSources(uid, power, psionic); + } + + if (mindbreak) + { + EnsureComp(uid); + _statusEffectsSystem.TryAddStatusEffect(uid, psionic.MindbreakingStutterCondition, + TimeSpan.FromMinutes(psionic.MindbreakingStutterTime * psionic.CurrentAmplification * psionic.CurrentDampening), + false, + psionic.MindbreakingStutterAccent); + + _popups.PopupEntity(Loc.GetString(psionic.MindbreakingFeedback, ("entity", MetaData(uid).EntityName)), uid, uid, PopupType.MediumCaution); + + RemComp(uid); + RemComp(uid); + return; + } + RefreshPsionicModifiers(uid, psionic); + } + + /// + /// Add all actions associated with a specific Psionic Power + /// + /// + /// + /// + private void AddPsionicActions(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic) + { + foreach (var id in proto.Actions) { - _sawmill.Error("Can't index the random psionic power pool!"); + EntityUid? actionId = null; + if (_actions.AddAction(uid, ref actionId, id)) + { + _actions.StartUseDelay(actionId); + psionic.Actions.Add(id, actionId); + } + } + } + + /// + /// Add all components associated with a specific Psionic power. + /// + /// + /// + private void AddPsionicPowerComponents(EntityUid uid, PsionicPowerPrototype proto) + { + if (proto.Components is null) return; + + foreach (var comp in proto.Components) + { + var powerComp = (Component) _componentFactory.GetComponent(comp.Key); + if (EntityManager.HasComponent(uid, powerComp.GetType())) + continue; + + AddComp(uid, powerComp); } + } + + /// + /// Update the Amplification and Dampening sources of a Psion to include a new Power. + /// + /// + /// + private void AddPsionicStatSources(PsionicPowerPrototype proto, PsionicComponent psionic) + { + if (proto.AmplificationModifier != 0) + psionic.AmplificationSources.Add(proto.Name, proto.AmplificationModifier); + + if (proto.DampeningModifier != 0) + psionic.DampeningSources.Add(proto.Name, proto.DampeningModifier); + } - foreach (var compName in pool.Weights.Keys) + /// + /// Remove all Psychic Actions listed in an entity's Psionic Component. Unfortunately, removing actions associated with a specific Power Prototype is not supported. + /// + /// + /// + private void RemovePsionicActions(EntityUid uid, PsionicComponent psionic) + { + if (psionic.Actions is null) + return; + + foreach (var action in psionic.Actions) + _actionsSystem.RemoveAction(uid, action.Value); + } + + /// + /// Remove all Components associated with a specific Psionic Power. + /// + /// + /// + private void RemovePsionicPowerComponents(EntityUid uid, PsionicPowerPrototype proto) + { + if (proto.Components is null) + return; + + foreach (var comp in proto.Components) { - // component moment - var comp = _componentFactory.GetComponent(compName); - if (EntityManager.TryGetComponent(uid, comp.GetType(), out var psionicPower)) - RemComp(uid, psionicPower); + var powerComp = (Component) _componentFactory.GetComponent(comp.Key); + if (!EntityManager.HasComponent(uid, powerComp.GetType())) + continue; + + EntityManager.RemoveComponent(uid, powerComp.GetType()); } - if (psionic.PsionicAbility != null - && _actionsSystem.TryGetActionData(psionic.PsionicAbility, out var psiAbility) - && psiAbility is not null) - _actionsSystem.RemoveAction(uid, psionic.PsionicAbility); + } + + /// + /// Remove all stat sources associated with a specific Psionic Power. + /// + /// + /// + private void RemovePsionicStatSources(EntityUid uid, PsionicPowerPrototype proto, PsionicComponent psionic) + { + if (proto.AmplificationModifier != 0) + psionic.AmplificationSources.Remove(proto.Name); - _statusEffectsSystem.TryAddStatusEffect(uid, "Stutter", TimeSpan.FromMinutes(5), false, "StutteringAccent"); + if (proto.DampeningModifier != 0) + psionic.DampeningSources.Remove(proto.Name); - RemComp(uid); + RefreshPsionicModifiers(uid, psionic); } } } diff --git a/Content.Server/Administration/ServerApi.cs b/Content.Server/Administration/ServerApi.cs index 04fd38598fb..6f10ef9b479 100644 --- a/Content.Server/Administration/ServerApi.cs +++ b/Content.Server/Administration/ServerApi.cs @@ -8,7 +8,6 @@ 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 df77a3a1a78..9849d2df79c 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Antags.cs @@ -1,37 +1,23 @@ -using Content.Server.Administration.Commands; -using Content.Server.Antag; -using Content.Server.GameTicking.Rules.Components; +using Content.Server.GameTicking.Rules; 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!; - - [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"; + [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!; // All antag verbs have names so invokeverb works. private void AddAntagVerbs(GetVerbsEvent args) @@ -54,7 +40,9 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Structures/Wallmounts/posters.rsi"), "poster5_contraband"), Act = () => { - _antag.ForceMakeAntag(player, DefaultTraitorRule); + // 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); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-traitor"), @@ -83,7 +71,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Wallmounts/signs.rsi"), "radiation"), Act = () => { - _antag.ForceMakeAntag(player, DefaultNukeOpRule); + _nukeopsRule.MakeLoneNukie(args.Target); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-nuclear-operative"), @@ -97,14 +85,14 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Clothing/Head/Hats/pirate.rsi"), "icon"), Act = () => { - // pirates just get an outfit because they don't really have logic associated with them - SetOutfitCommand.SetOutfit(args.Target, PirateGearId, EntityManager); + _piratesRule.MakePirate(args.Target); }, 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"), @@ -112,7 +100,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new("/Textures/Interface/Misc/job_icons.rsi"), "HeadRevolutionary"), Act = () => { - _antag.ForceMakeAntag(player, DefaultRevsRule); + _revolutionaryRule.OnHeadRevAdmin(args.Target); }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-head-rev"), @@ -126,7 +114,7 @@ private void AddAntagVerbs(GetVerbsEvent args) Icon = new SpriteSpecifier.Rsi(new ResPath("/Textures/Clothing/Hands/Gloves/ihscombat.rsi"), "icon"), Act = () => { - _antag.ForceMakeAntag(player, DefaultThiefRule); + _thief.AdminMakeThief(args.Target, false); //Midround add pacified is bad }, Impact = LogImpact.High, Message = Loc.GetString("admin-verb-make-thief"), diff --git a/Content.Server/Antag/AntagSelectionPlayerPool.cs b/Content.Server/Antag/AntagSelectionPlayerPool.cs deleted file mode 100644 index 87873e96d1a..00000000000 --- a/Content.Server/Antag/AntagSelectionPlayerPool.cs +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index 470f98fca1d..00000000000 --- a/Content.Server/Antag/AntagSelectionSystem.API.cs +++ /dev/null @@ -1,303 +0,0 @@ -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 6bfb7394f5b..b11c562df5a 100644 --- a/Content.Server/Antag/AntagSelectionSystem.cs +++ b/Content.Server/Antag/AntagSelectionSystem.cs @@ -1,461 +1,347 @@ -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.Ghost.Roles; -using Content.Server.Ghost.Roles.Components; +using Content.Server.GameTicking.Rules.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.Server.GameObjects; -using Robust.Server.Player; -using Robust.Shared.Enums; -using Robust.Shared.Map; +using Robust.Shared.Audio; 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 partial class AntagSelectionSystem : GameRuleSystem +public sealed class AntagSelectionSystem : GameRuleSystem { - [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 IServerPreferencesManager _prefs = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; [Dependency] private readonly JobSystem _jobs = 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!; - - // arbitrary random number to give late joining some mild interest. - public const float LateJoinRandomChance = 0.5f; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly SharedRoleSystem _roleSystem = default!; - /// - public override void Initialize() + #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) { - base.Initialize(); - - SubscribeLocalEvent(OnTakeGhostRole); - - SubscribeLocalEvent(OnPlayerSpawning); - SubscribeLocalEvent(OnJobsAssigned); - SubscribeLocalEvent(OnSpawnComplete); - } + var eligiblePlayers = new List(); - private void OnTakeGhostRole(Entity ent, ref TakeGhostRoleEvent args) - { - if (args.TookRole) - return; - - if (ent.Comp.Rule is not { } rule || ent.Comp.Definition is not { } def) - return; - - if (!Exists(rule) || !TryComp(rule, out var select)) - return; + foreach (var player in playerSessions) + { + if (IsPlayerEligible(player, antagPrototype, includeAllJobs, acceptableAntags, ignorePreferences, allowNonHumanoids, customExcludeCondition)) + eligiblePlayers.Add(player.AttachedEntity!.Value); + } - MakeAntag((rule, select), args.Player, def, ignoreSpawner: true); - args.TookRole = true; - _ghostRole.UnregisterGhostRole((ent, Comp(ent))); + return eligiblePlayers; } - private void OnPlayerSpawning(RulePlayerSpawningEvent args) + /// + /// 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) { - var pool = args.PlayerPool; + var eligibleSessions = new List(); - var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out _, out var comp, out _)) + foreach (var session in playerSessions) { - 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); - } + if (IsSessionEligible(session, antagPrototype, ignorePreferences)) + eligibleSessions.Add(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; - ChooseAntags((uid, comp)); - comp.SelectionsComplete = true; - } + return eligibleSessions; } - private void OnSpawnComplete(PlayerSpawnCompleteEvent args) + /// + /// 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) { - if (!args.LateJoin) - return; - - // 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. - - var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out _, out var antag, out _)) - { - if (!RobustRandom.Prob(LateJoinRandomChance)) - continue; + if (!IsSessionEligible(session, antagPrototype, ignorePreferences)) + return false; - if (!antag.Definitions.Any(p => p.LateJoinAdditional)) - continue; + //Ensure the player has a mind + if (session.GetMind() is not { } playerMind) + return false; - if (!TryGetNextAvailableDefinition((uid, antag), out var def)) - continue; + //Ensure the player has an attached entity + if (session.AttachedEntity is not { } playerEntity) + return false; - if (TryMakeAntag((uid, antag), args.Player, def.Value)) - break; - } - } + //Ignore latejoined players, ie those on the arrivals station + if (HasComp(playerEntity)) + return false; - protected override void Added(EntityUid uid, AntagSelectionComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) - { - base.Added(uid, component, gameRule, args); + //Exclude jobs that cannot be antag, unless explicitly allowed + if (!includeAllJobs && !_jobs.CanBeAntag(session)) + return false; - for (var i = 0; i < component.Definitions.Count; i++) + //Check if the entity is already an antag + switch (acceptableAntags) { - 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); - } + //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; + } } - } - protected override void Started(EntityUid uid, AntagSelectionComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) - { - base.Started(uid, component, gameRule, args); + //Unless explictly allowed, ignore non humanoids (eg pets) + if (!allowNonHumanoids && !HasComp(playerEntity)) + return false; - if (component.SelectionsComplete) - return; + //If a custom condition was provided, test it and exclude the player if it returns true + if (customExcludeCondition != null && customExcludeCondition(playerEntity)) + return false; - if (GameTicker.RunLevel != GameRunLevel.InRound) - return; - if (GameTicker.RunLevel == GameRunLevel.InRound && component.SelectionTime == AntagSelectionTime.PrePlayerSpawn) - return; - - ChooseAntags((uid, component)); - component.SelectionsComplete = true; + return true; } /// - /// Chooses antagonists from the current selection of players + /// Check if the session is eligible for a role, can be run prior to the session being attached to an entity /// - public void ChooseAntags(Entity ent) + /// 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) { - var sessions = _playerManager.Sessions.ToList(); - ChooseAntags(ent, sessions); + //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; } + #endregion /// - /// Chooses antagonists from the given selection of players + /// Helper method to calculate the number of antags to select based upon the number of players /// - public void ChooseAntags(Entity ent, List pool) + /// 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) { - foreach (var def in ent.Comp.Definitions) - { - ChooseAntags(ent, pool, def); - } + return Math.Clamp(playerCount / playersPerAntag, 1, maxAntags); } + #region Antag Selection /// - /// Chooses antagonists from the given selection of players for the given antag definition. + /// Selects a set number of entities from several lists, prioritising the first list till its empty, then second list etc /// - public void ChooseAntags(Entity ent, List pool, AntagSelectionDefinition def) + /// 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) { - var playerPool = GetPlayerPool(ent, pool, def); - var count = GetTargetAntagCount(ent, playerPool, def); - - for (var i = 0; i < count; i++) + var chosenPlayers = new List(); + foreach (var playerList in eligiblePlayerLists) { - var session = (ICommonSession?) null; - if (def.PickPlayer) + //Remove all chosen players from this list, to prevent duplicates + foreach (var chosenPlayer in chosenPlayers) { - if (!playerPool.TryPickAndTake(RobustRandom, out session)) - break; - - if (ent.Comp.SelectedSessions.Contains(session)) - continue; + playerList.Remove(chosenPlayer); } - MakeAntag(ent, session, def); + //If we have reached the desired number of players, skip + if (chosenPlayers.Count >= count) + continue; + + //Pick and choose a random number of players from this list + chosenPlayers.AddRange(ChooseAntags(count - chosenPlayers.Count, playerList)); } + return chosenPlayers; } - /// - /// Tries to makes a given player into the specified antagonist. + /// Helper method to choose antags from a list /// - public bool TryMakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false) + /// 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) { - if (!IsSessionValid(ent, session, def) || - !IsEntityValid(session?.AttachedEntity, def)) + var chosenPlayers = new List(); + + for (var i = 0; i < count; i++) { - return false; + if (eligiblePlayers.Count == 0) + break; + + chosenPlayers.Add(RobustRandom.PickAndTake(eligiblePlayers)); } - MakeAntag(ent, session, def, ignoreSpawner); - return true; + return chosenPlayers; } /// - /// Makes a given player into the specified antagonist. + /// Selects a set number of sessions from several lists, prioritising the first list till its empty, then second list etc /// - public void MakeAntag(Entity ent, ICommonSession? session, AntagSelectionDefinition def, bool ignoreSpawner = false) + /// 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) { - 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) - { - var getEntEv = new AntagSelectEntityEvent(session, ent); - RaiseLocalEvent(ent, ref getEntEv, true); - - if (!getEntEv.Handled) - { - throw new InvalidOperationException($"Attempted to make {session} antagonist in gamerule {ToPrettyString(ent)} but there was no valid entity for player."); - } - - antagEnt = getEntEv.Entity; - } - - if (antagEnt is not { } player) - return; - - 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); - } - - if (isSpawner) - { - if (!TryComp(player, out var spawnerComp)) - { - Log.Error("Antag spawner with GhostRoleAntagSpawnerComponent."); - return; - } - - spawnerComp.Rule = ent; - spawnerComp.Definition = def; - return; - } - - EntityManager.AddComponents(player, def.Components); - _stationSpawning.EquipStartingGear(player, def.StartingGear); - - if (session != null) + var chosenPlayers = new List(); + foreach (var playerList in eligiblePlayerLists) { - var curMind = session.GetMind(); - if (curMind == null) + //Remove all chosen players from this list, to prevent duplicates + foreach (var chosenPlayer in chosenPlayers) { - curMind = _mind.CreateMind(session.UserId, Name(antagEnt.Value)); - _mind.SetUserId(curMind.Value, session.UserId); + playerList.Remove(chosenPlayer); } - _mind.TransferTo(curMind.Value, antagEnt, ghostCheckOverride: true); - _role.MindAddRoles(curMind.Value, def.MindComponents); - ent.Comp.SelectedMinds.Add((curMind.Value, Name(player))); - } + //If we have reached the desired number of players, skip + if (chosenPlayers.Count >= count) + continue; - if (def.Briefing is { } briefing) - { - SendBriefing(session, briefing); + //Pick and choose a random number of players from this list + chosenPlayers.AddRange(ChooseAntags(count - chosenPlayers.Count, playerList)); } - - var afterEv = new AfterAntagEntitySelectedEvent(session, player, ent, def); - RaiseLocalEvent(ent, ref afterEv, true); + return chosenPlayers; } - /// - /// Gets an ordered player pool based on player preferences and the antagonist definition. + /// Helper method to choose sessions from a list /// - public AntagSelectionPlayerPool GetPlayerPool(Entity ent, List sessions, AntagSelectionDefinition def) + /// 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 preferredList = new List(); - var secondBestList = new List(); - var unwantedList = new List(); - var invalidList = new List(); - foreach (var session in sessions) + var chosenPlayers = new List(); + + for (int i = 0; i < count; i++) { - if (!IsSessionValid(ent, session, def) || - !IsEntityValid(session.AttachedEntity, def)) - { - invalidList.Add(session); - continue; - } + if (eligiblePlayers.Count == 0) + break; - 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); - } + chosenPlayers.Add(RobustRandom.PickAndTake(eligiblePlayers)); } - return new AntagSelectionPlayerPool(new() { preferredList, secondBestList, unwantedList, invalidList }); + return chosenPlayers; } + #endregion + #region Briefings /// - /// Checks if a given session is valid for an antagonist. + /// Helper method to send the briefing text and sound to a list of entities /// - public bool IsSessionValid(Entity ent, ICommonSession? session, AntagSelectionDefinition def, EntityUid? mind = null) + /// 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) { - if (session == null) - return true; - - mind ??= session.GetMind(); - - if (session.Status is SessionStatus.Disconnected or SessionStatus.Zombie) - return false; - - if (ent.Comp.SelectedSessions.Contains(session)) - return false; - - //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) + foreach (var entity in entities) { - case AntagAcceptability.None: - { - if (_role.MindIsAntagonist(mind)) - return false; - break; - } - case AntagAcceptability.NotExclusive: - { - if (_role.MindIsExclusiveAntagonist(mind)) - return false; - break; - } + SendBriefing(entity, briefing, briefingColor, briefingSound); } - - // todo: expand this to allow for more fine antag-selection logic for game rules. - if (!_jobs.CanBeAntag(session)) - return false; - - return true; } /// - /// Checks if a given entity (mind/session not included) is valid for a given antagonist. + /// Helper method to send the briefing text and sound to a player entity /// - private bool IsEntityValid(EntityUid? entity, AntagSelectionDefinition def) + /// 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 (entity == null) - return false; + if (!_mindSystem.TryGetMind(entity, out _, out var mindComponent)) + return; - if (HasComp(entity)) - return false; + if (mindComponent.Session == null) + return; - if (!def.AllowNonHumans && !HasComp(entity)) - return false; + SendBriefing(mindComponent.Session, briefing, briefingColor, briefingSound); + } - if (def.Whitelist != null) - { - if (!def.Whitelist.IsValid(entity.Value, EntityManager)) - return false; - } + /// + /// Helper method to send the briefing text and sound to a list of sessions + /// + /// + /// + /// + /// - if (def.Blacklist != null) + public void SendBriefing(List sessions, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + { + foreach (var session in sessions) { - if (def.Blacklist.IsValid(entity.Value, EntityManager)) - return false; + SendBriefing(session, briefing, briefingColor, briefingSound); } - - return true; } -} - -/// -/// 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(); + /// + /// 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 List Coordinates = new(); + public void SendBriefing(ICommonSession session, string briefing, Color? briefingColor, SoundSpecifier? briefingSound) + { + _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); + } + #endregion } - -/// -/// 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 deleted file mode 100644 index 096be14049a..00000000000 --- a/Content.Server/Antag/Components/AntagSelectionComponent.cs +++ /dev/null @@ -1,189 +0,0 @@ -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 deleted file mode 100644 index fcaa4d42672..00000000000 --- a/Content.Server/Antag/Components/GhostRoleAntagSpawnerComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -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 18837b5a7c8..ba09c84bce4 100644 --- a/Content.Server/Antag/MobReplacementRuleSystem.cs +++ b/Content.Server/Antag/MobReplacementRuleSystem.cs @@ -1,16 +1,45 @@ +using System.Numerics; +using Content.Server.Advertise.Components; +using Content.Server.Advertise.EntitySystems; using Content.Server.Antag.Mimic; -using Content.Server.GameTicking.Components; +using Content.Server.Chat.Systems; 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) { @@ -18,21 +47,133 @@ 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)) { - if (!_random.Prob(component.Chance)) + 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)) continue; spawns.Add((vendingUid, xform.Coordinates)); } - foreach (var entity in spawns) + if (spawns == null) { - var coordinates = entity.Coordinates; - Del(entity.Entity); + //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)>(); - Spawn(component.Proto, coordinates); + foreach (var name in mimicComponents.Except(vendorComponents)) + { + var newComponent = _componentFactory.GetComponent(name.name); + EntityManager.AddComponent(uid, newComponent); } + + 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/Arcade/BlockGame/BlockGame.cs b/Content.Server/Arcade/BlockGame/BlockGame.cs index 3af1828d564..675776828f8 100644 --- a/Content.Server/Arcade/BlockGame/BlockGame.cs +++ b/Content.Server/Arcade/BlockGame/BlockGame.cs @@ -2,6 +2,7 @@ using Robust.Server.GameObjects; using Robust.Shared.Random; using System.Linq; +using Content.Shared.Mood; namespace Content.Server.Arcade.BlockGame; @@ -82,6 +83,8 @@ private void InvokeGameover() { _highScorePlacement = _arcadeSystem.RegisterHighScore(meta.EntityName, Points); SendHighscoreUpdate(); + var ev = new MoodEffectEvent("ArcadePlay"); + _entityManager.EventBus.RaiseLocalEvent(meta.Owner, ev); } SendMessage(new BlockGameMessages.BlockGameGameOverScreenMessage(Points, _highScorePlacement?.LocalPlacement, _highScorePlacement?.GlobalPlacement)); } diff --git a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs index f60d88ebf78..a5ca626d131 100644 --- a/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs +++ b/Content.Server/Arcade/SpaceVillainGame/SpaceVillainArcadeSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.UserInterface; using Content.Server.Advertise; using Content.Server.Advertise.Components; +using Content.Shared.Mood; using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent; using Robust.Server.GameObjects; using Robust.Shared.Audio; @@ -76,6 +77,9 @@ private void OnSVPlayerAction(EntityUid uid, SpaceVillainArcadeComponent compone if (!TryComp(uid, out var power) || !power.Powered) return; + if (msg.Session.AttachedEntity != null) + RaiseLocalEvent(msg.Session.AttachedEntity.Value, new MoodEffectEvent("ArcadePlay")); + switch (msg.PlayerAction) { case PlayerAction.Attack: diff --git a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs index 948373940e4..9bea58330cd 100644 --- a/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs +++ b/Content.Server/Atmos/EntitySystems/BarotraumaSystem.cs @@ -8,6 +8,7 @@ using Content.Shared.FixedPoint; using Content.Shared.Inventory; using Content.Shared.Inventory.Events; +using Content.Shared.Mood; using Robust.Shared.Containers; namespace Content.Server.Atmos.EntitySystems @@ -239,14 +240,16 @@ public override void Update(float frameTime) barotrauma.TakingDamage = true; _adminLogger.Add(LogType.Barotrauma, $"{ToPrettyString(uid):entity} started taking low pressure damage"); } - - _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 2); + RaiseLocalEvent(uid, new MoodEffectEvent("MobLowPressure")); + _alertsSystem.ShowAlert(uid, AlertType.LowPressure, 2); } else if (pressure >= Atmospherics.HazardHighPressure) { var damageScale = MathF.Min(((pressure / Atmospherics.HazardHighPressure) - 1) * Atmospherics.PressureDamageCoefficient, Atmospherics.MaxHighPressureDamage); _damageableSystem.TryChangeDamage(uid, barotrauma.Damage * damageScale, true, false); + RaiseLocalEvent(uid, new MoodEffectEvent("MobHighPressure")); + if (!barotrauma.TakingDamage) { barotrauma.TakingDamage = true; diff --git a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs index d34de937a41..7890b7751ac 100644 --- a/Content.Server/Atmos/EntitySystems/FlammableSystem.cs +++ b/Content.Server/Atmos/EntitySystems/FlammableSystem.cs @@ -23,6 +23,7 @@ using Content.Shared.Weapons.Melee.Events; using Content.Shared.FixedPoint; using Robust.Server.Audio; +using Content.Shared.Mood; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; @@ -410,10 +411,12 @@ public override void Update(float frameTime) if (!flammable.OnFire) { _alertsSystem.ClearAlert(uid, AlertType.Fire); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("OnFire")); continue; } _alertsSystem.ShowAlert(uid, AlertType.Fire); + RaiseLocalEvent(uid, new MoodEffectEvent("OnFire")); if (flammable.FireStacks > 0) { diff --git a/Content.Server/Bible/BibleSystem.cs b/Content.Server/Bible/BibleSystem.cs index c845b17230a..2798103cc1c 100644 --- a/Content.Server/Bible/BibleSystem.cs +++ b/Content.Server/Bible/BibleSystem.cs @@ -14,6 +14,7 @@ using Content.Shared.Popups; using Content.Shared.Timing; using Content.Shared.Verbs; +using Content.Shared.Mood; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; @@ -153,6 +154,8 @@ private void OnAfterInteract(EntityUid uid, BibleComponent component, AfterInter _audio.PlayPvs(component.HealSoundPath, args.User); _delay.TryResetDelay((uid, useDelay)); } + + RaiseLocalEvent(args.Target.Value, new MoodEffectEvent("GotBlessed")); } private void AddSummonVerb(EntityUid uid, SummonableComponent component, GetVerbsEvent args) diff --git a/Content.Server/Body/Systems/RespiratorSystem.cs b/Content.Server/Body/Systems/RespiratorSystem.cs index c7266e2c463..389e5fbab72 100644 --- a/Content.Server/Body/Systems/RespiratorSystem.cs +++ b/Content.Server/Body/Systems/RespiratorSystem.cs @@ -10,6 +10,7 @@ using Content.Shared.Damage; using Content.Shared.Database; using Content.Shared.Mobs.Systems; +using Content.Shared.Mood; using JetBrains.Annotations; using Robust.Shared.Timing; @@ -177,6 +178,7 @@ private void TakeSuffocationDamage(Entity ent) { _alertsSystem.ShowAlert(ent, comp.Alert); } + RaiseLocalEvent(ent, new MoodEffectEvent("Suffocating")); } _damageableSys.TryChangeDamage(ent, ent.Comp.Damage, interruptsDoAfters: false); diff --git a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs index 8d5a583f6d8..5654f9067b5 100644 --- a/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs +++ b/Content.Server/Chemistry/ReagentEffects/MakeSentient.cs @@ -8,8 +8,6 @@ using Content.Shared.Language.Systems; using Content.Shared.Mind.Components; using Robust.Shared.Prototypes; -using Content.Server.Psionics; -using Content.Shared.Body.Part; //Nyano - Summary: pulls in the ability for the sentient creature to become psionic. using Content.Shared.Humanoid; using Content.Shared.Language.Components; //Delta-V - Banning humanoids from becoming ghost roles. using Content.Shared.Language.Events; @@ -68,7 +66,6 @@ public override void Effect(ReagentEffectArgs args) ghostRole = entityManager.AddComponent(uid); entityManager.EnsureComponent(uid); - entityManager.EnsureComponent(uid); //Nyano - Summary:. Makes the animated body able to get psionics. var entityData = entityManager.GetComponent(uid); ghostRole.RoleName = entityData.EntityName; diff --git a/Content.Server/Cloning/CloningSystem.cs b/Content.Server/Cloning/CloningSystem.cs index 92e658591a0..5d311f3ce10 100644 --- a/Content.Server/Cloning/CloningSystem.cs +++ b/Content.Server/Cloning/CloningSystem.cs @@ -50,6 +50,7 @@ using Content.Shared.Damage.ForceSay; using Content.Server.Polymorph.Components; using Content.Shared.Chat; +using Content.Shared.Abilities.Psionics; namespace Content.Server.Cloning { @@ -252,7 +253,7 @@ public bool TryCloning(EntityUid uid, EntityUid bodyToClone, Entity(mob); + EnsureComp(mob); var ev = new CloningEvent(bodyToClone, mob); RaiseLocalEvent(bodyToClone, ref ev); @@ -438,7 +439,7 @@ private EntityUid FetchAndSpawnMob(CloningPodComponent clonePod, HumanoidCharact grammar.Gender = humanoid.Gender; Dirty(grammar); - EnsureComp(mob); + EnsureComp(mob); EnsureComp(mob); EnsureComp(mob); EnsureComp(mob); diff --git a/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs b/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs index abdc9500202..d8ca263d5b2 100644 --- a/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs +++ b/Content.Server/DeltaV/ParadoxAnomaly/Systems/ParadoxAnomalySystem.cs @@ -7,6 +7,7 @@ using Content.Server.Spawners.Components; using Content.Server.Station.Systems; using Content.Server.Terminator.Systems; +using Content.Shared.Abilities.Psionics; using Content.Shared.Humanoid; using Content.Shared.Humanoid.Prototypes; using Content.Shared.Mind; @@ -16,7 +17,6 @@ using Content.Shared.Roles.Jobs; using Robust.Shared.Prototypes; using Robust.Shared.Random; -using Robust.Shared.Utility; using System.Diagnostics.CodeAnalysis; namespace Content.Server.DeltaV.ParadoxAnomaly.Systems; @@ -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); + _stationSpawning.EquipStartingGear(spawned, gear, profile); _stationSpawning.EquipIdCard(spawned, profile.Name, job, @@ -156,8 +156,9 @@ private bool TrySpawnParadoxAnomaly(string rule, [NotNullWhen(true)] out EntityU special.AfterEquip(spawned); } - var psi = EnsureComp(spawned); - _psionics.RollPsionics(spawned, psi, false, 100); + // TODO: In a future PR, make it so that the Paradox Anomaly spawns with a completely 1:1 clone of the victim's entire PsionicComponent. + if (HasComp(uid)) + EnsureComp(spawned); return spawned; } diff --git a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs index 6849c508a1f..ec9ec770313 100644 --- a/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs +++ b/Content.Server/DeltaV/StationEvents/Events/GlimmerMobSpawnRule.cs @@ -1,5 +1,4 @@ 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 c5d199164b4..ba042d89662 100644 --- a/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs +++ b/Content.Server/DeltaV/StationEvents/Events/PirateRadioSpawnRule.cs @@ -19,7 +19,6 @@ 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 c44864183ab..b438e7c0e8d 100644 --- a/Content.Server/Destructible/Thresholds/MinMax.cs +++ b/Content.Server/Destructible/Thresholds/MinMax.cs @@ -1,6 +1,4 @@ -using Robust.Shared.Random; - -namespace Content.Server.Destructible.Thresholds +namespace Content.Server.Destructible.Thresholds { [Serializable] [DataDefinition] @@ -11,16 +9,5 @@ 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 48a65973491..a04f274491c 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/Components/DelayedStartRuleComponent.cs b/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs deleted file mode 100644 index de4be83627d..00000000000 --- a/Content.Server/GameTicking/Components/DelayedStartRuleComponent.cs +++ /dev/null @@ -1,16 +0,0 @@ -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/GameTicker.GameRule.cs b/Content.Server/GameTicking/GameTicker.GameRule.cs index f52a3cb296d..4ebe946af4a 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.Components; +using Content.Server.GameTicking.Rules.Components; using Content.Shared.Administration; using Content.Shared.Database; using Content.Shared.Prototypes; @@ -102,22 +102,6 @@ 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)}"); @@ -271,18 +255,6 @@ 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)] @@ -351,3 +323,38 @@ 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 fa23312268f..efda3df0ca1 100644 --- a/Content.Server/GameTicking/GameTicker.cs +++ b/Content.Server/GameTicking/GameTicker.cs @@ -133,7 +133,6 @@ public override void Update(float frameTime) return; base.Update(frameTime); UpdateRoundFlow(frameTime); - UpdateGameRules(); } } } diff --git a/Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs similarity index 84% rename from Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs rename to Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs index b9e6fa5d4b8..956768bdd99 100644 --- a/Content.Server/GameTicking/Components/ActiveGameRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ActiveGameRuleComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.GameTicking.Components; +namespace Content.Server.GameTicking.Rules.Components; /// /// Added to game rules before and removed before . diff --git a/Content.Server/GameTicking/Components/EndedGameRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs similarity index 81% rename from Content.Server/GameTicking/Components/EndedGameRuleComponent.cs rename to Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs index 3234bfff3a0..4484abd4d0b 100644 --- a/Content.Server/GameTicking/Components/EndedGameRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/EndedGameRuleComponent.cs @@ -1,4 +1,4 @@ -namespace Content.Server.GameTicking.Components; +namespace Content.Server.GameTicking.Rules.Components; /// /// Added to game rules before . diff --git a/Content.Server/GameTicking/Components/GameRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs similarity index 83% rename from Content.Server/GameTicking/Components/GameRuleComponent.cs rename to Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs index 1e6c3f0ab1d..6309b974020 100644 --- a/Content.Server/GameTicking/Components/GameRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/GameRuleComponent.cs @@ -1,7 +1,6 @@ -using Content.Server.Destructible.Thresholds; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; -namespace Content.Server.GameTicking.Components; +namespace Content.Server.GameTicking.Rules.Components; /// /// Component attached to all gamerule entities. @@ -21,12 +20,6 @@ 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/Rules/Components/LoadMapRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs deleted file mode 100644 index 463aecbff54..00000000000 --- a/Content.Server/GameTicking/Rules/Components/LoadMapRuleComponent.cs +++ /dev/null @@ -1,29 +0,0 @@ -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 fa352eb320b..e6966c1e377 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 bb1b7c87460..e02d90c18bf 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeOperativeSpawnerComponent.cs @@ -1,3 +1,6 @@ +using Content.Shared.Roles; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; + namespace Content.Server.GameTicking.Rules.Components; /// @@ -6,5 +9,11 @@ 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; +public sealed partial class NukeOperativeSpawnerComponent : Component +{ + [DataField("name", required:true)] + public string OperativeName = default!; + [DataField] + public NukeopSpawnPreset SpawnDetails = default!; +} diff --git a/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs b/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs index 3d097cd7c79..358b157cdf3 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeOpsShuttleComponent.cs @@ -6,6 +6,4 @@ [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 f64947e286e..8efd61b4694 100644 --- a/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/NukeopsRuleComponent.cs @@ -1,9 +1,10 @@ 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.Audio; +using Robust.Shared.Map; using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; @@ -13,9 +14,18 @@ namespace Content.Server.GameTicking.Rules.Components; -[RegisterComponent, Access(typeof(NukeopsRuleSystem))] +[RegisterComponent, Access(typeof(NukeopsRuleSystem), typeof(LoneOpsSpawnRule))] 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. /// @@ -46,6 +56,12 @@ 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 /// @@ -68,7 +84,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 @@ -83,22 +99,48 @@ public sealed partial class NukeopsRuleComponent : Component public int WarDeclarationMinOps = 4; [DataField] - public WinType WinType = WinType.Neutral; + public EntProtoId SpawnPointProto = "SpawnPointNukies"; [DataField] - public List WinConditions = new (); + public EntProtoId GhostSpawnPointProto = "SpawnPointGhostNukeOperative"; [DataField] - public EntityUid? TargetStation; + public string OperationName = "Test Operation"; [DataField] - public ProtoId Faction = "Syndicate"; + 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; /// - /// Path to antagonist alert sound. + /// Data to be used in for an operative once the Mind has been added. /// [DataField] - public SoundSpecifier GreetSoundNotification = new SoundPathSpecifier("/Audio/Ambience/Antag/nukeops_start.ogg"); + 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" }; + + [DataField] + public NukeopSpawnPreset AgentSpawnDetails = new() { AntagRoleProto = "NukeopsMedic", GearProto = "SyndicateOperativeMedicFull", NamePrefix = "nukeops-role-agent", NameList = "SyndicateNamesNormal" }; + + [DataField] + public NukeopSpawnPreset OperativeSpawnDetails = new(); } /// diff --git a/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs new file mode 100644 index 00000000000..1d03b41d773 --- /dev/null +++ b/Content.Server/GameTicking/Rules/Components/PiratesRuleComponent.cs @@ -0,0 +1,24 @@ +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 3b19bbffb6a..2ce3f1f9a66 100644 --- a/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/RevolutionaryRuleComponent.cs @@ -22,6 +22,43 @@ 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 01a078625ae..9dfd6e6627c 100644 --- a/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ThiefRuleComponent.cs @@ -1,11 +1,12 @@ 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 @@ -22,9 +23,42 @@ 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 dd359969b6f..62619db76a2 100644 --- a/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/TraitorRuleComponent.cs @@ -57,19 +57,4 @@ 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 59d1940eafe..4fe91e3a5f5 100644 --- a/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs +++ b/Content.Server/GameTicking/Rules/Components/ZombieRuleComponent.cs @@ -8,6 +8,12 @@ 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. /// @@ -20,9 +26,61 @@ 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 78b8a8a85c8..82ac755592e 100644 --- a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs @@ -1,6 +1,5 @@ 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; @@ -34,6 +33,7 @@ public override void Initialize() SubscribeLocalEvent(OnSpawnComplete); SubscribeLocalEvent(OnKillReported); SubscribeLocalEvent(OnPointChanged); + SubscribeLocalEvent(OnRoundEndTextAppend); } private void OnBeforeSpawn(PlayerBeforeSpawnEvent ev) @@ -113,17 +113,21 @@ private void OnPointChanged(EntityUid uid, DeathMatchRuleComponent component, re _roundEnd.EndRound(component.RestartDelay); } - protected override void AppendRoundEndText(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, ref RoundEndTextAppendEvent args) + private void OnRoundEndTextAppend(RoundEndTextAppendEvent ev) { - if (!TryComp(uid, out var point)) - return; - - if (component.Victor != null && _player.TryGetPlayerData(component.Victor.Value, out var data)) + var query = EntityQueryEnumerator(); + while (query.MoveNext(out var uid, out var dm, out var point, out var rule)) { - args.AddLine(Loc.GetString("point-scoreboard-winner", ("player", data.UserName))); - args.AddLine(""); + if (!GameTicker.IsGameRuleAdded(uid, rule)) + continue; + + 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()); } - 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 27a9edbad71..a60a2bfe22f 100644 --- a/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs +++ b/Content.Server/GameTicking/Rules/GameRuleSystem.Utility.cs @@ -1,5 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Station.Components; using Robust.Shared.Collections; @@ -16,12 +15,29 @@ protected EntityQueryEnumerator Q return EntityQueryEnumerator(); } - /// - /// Queries all gamerules, regardless of if they're active or not. - /// - protected EntityQueryEnumerator QueryAllRules() + protected bool TryRoundStartAttempt(RoundStartAttemptEvent ev, string localizedPresetName) { - return EntityQueryEnumerator(); + 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; } /// diff --git a/Content.Server/GameTicking/Rules/GameRuleSystem.cs b/Content.Server/GameTicking/Rules/GameRuleSystem.cs index c167ae7b6c7..363c2ad7f75 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.Components; +using Content.Server.GameTicking.Rules.Components; using Robust.Server.GameObjects; using Robust.Shared.Random; using Robust.Shared.Timing; @@ -22,31 +22,9 @@ 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) @@ -70,12 +48,6 @@ 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 @@ -101,14 +73,6 @@ 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 01fa387595c..b775b7af564 100644 --- a/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/InactivityTimeRestartRuleSystem.cs @@ -1,6 +1,5 @@ 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 3da55e30c9e..01fd97d9a79 100644 --- a/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs @@ -1,5 +1,4 @@ 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 deleted file mode 100644 index aba9ed9e583..00000000000 --- a/Content.Server/GameTicking/Rules/LoadMapRuleSystem.cs +++ /dev/null @@ -1,80 +0,0 @@ -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 ee3a025533a..e792a004df5 100644 --- a/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/MaxTimeRestartRuleSystem.cs @@ -1,6 +1,5 @@ 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 d06b9fb899c..46040e29450 100644 --- a/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/NukeopsRuleSystem.cs @@ -1,51 +1,77 @@ +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"; @@ -53,67 +79,141 @@ 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) { - var eligible = new List>(); - var eligibleQuery = EntityQueryEnumerator(); - while (eligibleQuery.MoveNext(out var eligibleUid, out var eligibleComp, out var member)) + 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 _)) { - if (!_npcFaction.IsFactionHostile(component.Faction, eligibleUid, member)) + if (!SpawnMap((uid, nukeops))) + { + _sawmill.Info("Failed to load map for nukeops"); continue; + } - eligible.Add((eligibleUid, eligibleComp, member)); - } + //Handle there being nobody readied up + if (ev.PlayerPool.Count == 0) + continue; - if (eligible.Count == 0) - return; + 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)); + } + } - component.TargetStation = RobustRandom.Pick(eligible); + SpawnOperatives(operatives, _cfg.GetCVar(CCVars.NukeopsSpawnGhostRoles), nukeops); + + foreach (var nukieSpawn in operatives) + { + if (nukieSpawn.Session == null) + continue; + + GameTicker.PlayerJoinGame(nukieSpawn.Session); + } + } } - #region Event Handlers - protected override void AppendRoundEndText(EntityUid uid, NukeopsRuleComponent component, GameRuleComponent gameRule, - ref RoundEndTextAppendEvent args) + private void OnRoundEndText(RoundEndTextAppendEvent ev) { - var winText = Loc.GetString($"nukeops-{component.WinType.ToString().ToLower()}"); - args.AddLine(winText); - - foreach (var cond in component.WinConditions) + var ruleQuery = QueryActiveRules(); + while (ruleQuery.MoveNext(out _, out _, out var nukeops, out _)) { - var text = Loc.GetString($"nukeops-cond-{cond.ToString().ToLower()}"); - args.AddLine(text); - } + var winText = Loc.GetString($"nukeops-{nukeops.WinType.ToString().ToLower()}"); + ev.AddLine(winText); - args.AddLine(Loc.GetString("nukeops-list-start")); + foreach (var cond in nukeops.WinConditions) + { + var text = Loc.GetString($"nukeops-cond-{cond.ToString().ToLower()}"); + ev.AddLine(text); + } + } - var antags =_antag.GetAntagIdentifiers(uid); + ev.AddLine(Loc.GetString("nukeops-list-start")); - foreach (var (_, sessionData, name) in antags) + var nukiesQuery = EntityQueryEnumerator(); + while (nukiesQuery.MoveNext(out var nukeopsUid, out _, out var mindContainer)) { - args.AddLine(Loc.GetString("nukeops-list-name-user", ("name", name), ("user", sessionData.UserName))); + if (!_mind.TryGetMind(nukeopsUid, out _, out var mind, mindContainer)) + continue; + + 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)))); } } @@ -124,10 +224,10 @@ private void OnNukeExploded(NukeExplodedEvent ev) { if (ev.OwningStation != null) { - if (ev.OwningStation == GetOutpost(uid)) + if (ev.OwningStation == nukeops.NukieOutpost) { nukeops.WinConditions.Add(WinCondition.NukeExplodedOnNukieOutpost); - SetWinType((uid, nukeops), WinType.CrewMajor); + SetWinType(uid, WinType.CrewMajor, nukeops); continue; } @@ -142,7 +242,7 @@ private void OnNukeExploded(NukeExplodedEvent ev) } nukeops.WinConditions.Add(WinCondition.NukeExplodedOnCorrectStation); - SetWinType((uid, nukeops), WinType.OpsMajor); + SetWinType(uid, WinType.OpsMajor, nukeops); correctStation = true; } @@ -163,85 +263,19 @@ 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 _)) { - 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)) + switch (ev.New) { - ent.Comp.WinConditions.Add(WinCondition.NukeActiveAtCentCom); - SetWinType((ent, ent), WinType.OpsMajor); - return; + case GameRunLevel.InRound: + OnRoundStart(uid, nukeops); + break; + case GameRunLevel.PostRound: + OnRoundEnd(uid, nukeops); + break; } - - 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) @@ -260,31 +294,66 @@ private void OnMobStateChanged(EntityUid uid, NukeOperativeComponent component, CheckRoundShouldEnd(); } - private void OnOperativeZombified(EntityUid uid, NukeOperativeComponent component, ref EntityZombifiedEvent args) + private void OnPlayersGhostSpawning(EntityUid uid, NukeOperativeComponent component, GhostRoleSpawnerUsedEvent args) { - RemCompDeferred(uid, component); + 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); + } } - private void OnMapInit(Entity ent, ref MapInitEvent args) + private void OnMindAdded(EntityUid uid, NukeOperativeComponent component, MindAddedMessage args) { - var map = Transform(ent).MapID; + if (!_mind.TryGetMind(uid, out var mindId, out var mind)) + return; - var rules = EntityQueryEnumerator(); - while (rules.MoveNext(out var uid, out _, out var mapRule)) + var query = QueryActiveRules(); + while (query.MoveNext(out _, out _, out var nukeops, out _)) { - if (map != mapRule.Map) - continue; - ent.Comp.AssociatedRule = uid; - break; + 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); + } } } + 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 var uid, out _, out var nukeops, out _)) + while (query.MoveNext(out _, out _, out var nukeops, out _)) { - if (ev.Uid != GetShuttle((uid, nukeops))) + if (ev.Uid != nukeops.NukieShuttle) continue; if (nukeops.WarDeclaredTime != null) @@ -328,12 +397,12 @@ private void OnWarDeclared(ref WarDeclaredEvent ev) { // TODO: this is VERY awful for multi-nukies var query = QueryActiveRules(); - while (query.MoveNext(out var uid, out _, out var nukeops, out _)) + while (query.MoveNext(out _, out _, out var nukeops, out _)) { if (nukeops.WarDeclaredTime != null) continue; - if (TryComp(uid, out var mapComp) && Transform(ev.DeclaratorEntity).MapID != mapComp.Map) + if (Transform(ev.DeclaratorEntity).MapID != nukeops.NukiePlanet) continue; var newStatus = GetWarCondition(nukeops, ev.Status); @@ -344,7 +413,7 @@ private void OnWarDeclared(ref WarDeclaredEvent ev) var timeRemain = nukeops.WarNukieArriveDelay + Timing.CurTime; ev.DeclaratorEntity.Comp.ShuttleDisabledTime = timeRemain; - DistributeExtraTc((uid, nukeops)); + DistributeExtraTc(nukeops); } } } @@ -371,7 +440,7 @@ public WarConditionStatus GetWarCondition(NukeopsRuleComponent nukieRule, WarCon return WarConditionStatus.YesWar; } - private void DistributeExtraTc(Entity nukieRule) + private void DistributeExtraTc(NukeopsRuleComponent nukieRule) { var enumerator = EntityQueryEnumerator(); while (enumerator.MoveNext(out var uid, out var component)) @@ -379,22 +448,161 @@ private void DistributeExtraTc(Entity nukieRule) if (!_tag.HasTag(uid, NukeOpsUplinkTagPrototype)) continue; - if (GetOutpost(nukieRule.Owner) is not { } outpost) + if (!nukieRule.NukieOutpost.HasValue) continue; - if (Transform(uid).MapID != Transform(outpost).MapID) // Will receive bonus TC only on their start outpost + if (Transform(uid).MapID != Transform(nukieRule.NukieOutpost.Value).MapID) // Will receive bonus TC only on their start outpost continue; - _store.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.Comp.WarTcAmountPerNukie } }, uid, component); + _store.TryAddCurrency(new () { { TelecrystalCurrencyPrototype, nukieRule.WarTCAmountPerNukie } }, uid, component); var msg = Loc.GetString("store-currency-war-boost-given", ("target", uid)); _popupSystem.PopupEntity(msg, uid); } } - private void SetWinType(Entity ent, WinType type, bool endRound = true) + private void OnRoundStart(EntityUid uid, NukeopsRuleComponent? component = null) { - ent.Comp.WinType = type; + 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; if (endRound && (type == WinType.CrewMajor || type == WinType.OpsMajor)) _roundEndSystem.EndRound(); @@ -405,130 +613,243 @@ private void CheckRoundShouldEnd() var query = QueryActiveRules(); while (query.MoveNext(out var uid, out _, out var nukeops, out _)) { - CheckRoundShouldEnd((uid, nukeops)); + 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; } + + return true; } - private void CheckRoundShouldEnd(Entity ent) + /// + /// Adds missing nuke operative components, equips starting gear and renames the entity. + /// + private void SetupOperativeEntity(EntityUid mob, string name, NukeopSpawnPreset spawnDetails, HumanoidCharacterProfile? profile) { - var nukeops = ent.Comp; + _metaData.SetEntityName(mob, name); + EnsureComp(mob); + + if (profile != null) + _humanoid.LoadProfile(mob, profile); + + var gear = _prototypeManager.Index(spawnDetails.GearProto); + _stationSpawning.EquipStartingGear(mob, gear, profile); - if (nukeops.RoundEndBehavior == RoundEndBehavior.Nothing || nukeops.WinType == WinType.CrewMajor || nukeops.WinType == WinType.OpsMajor) + _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) 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; + } - // If there are any nuclear bombs that are active, immediately return. We're not over yet. - foreach (var nuke in EntityQuery()) + //Fallback, spawn at the centre of the map + if (spawns.Count == 0) { - if (nuke.Status == NukeStatus.ARMED) - return; + spawns.Add(Transform(outpostUid).Coordinates); + _sawmill.Warning($"Fell back to default spawn for nukies!"); } - var shuttle = GetShuttle((ent, ent)); + //Spawn the team + foreach (var nukieSession in sessions) + { + var name = $"{Loc.GetString(nukieSession.Type.NamePrefix)} {RobustRandom.PickAndTake(_prototypeManager.Index(nukieSession.Type.NameList).Values.ToList())}"; - MapId? shuttleMapId = Exists(shuttle) - ? Transform(shuttle.Value).MapID - : null; + var nukeOpsAntag = _prototypeManager.Index(nukieSession.Type.AntagRoleProto); - 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; + //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; + } } + } - // 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; + /// + /// 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); } - // this should really go anywhere else but im tired. - private void OnAntagSelectEntity(Entity ent, ref AntagSelectEntityEvent args) + /// + /// Spawn nukie ghost roles if this gamerule was started mid round + /// + private void SpawnOperativesForGhostRoles(EntityUid uid, NukeopsRuleComponent? component = null) { - if (args.Handled) + if (!Resolve(uid, ref component)) return; - 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)) + if (!SpawnMap((uid, component))) { - species = _prototypeManager.Index(SharedHumanoidAppearanceSystem.DefaultSpecies); + _sawmill.Info("Failed to load map for nukeops"); + return; } - args.Entity = Spawn(species.Prototype); - _humanoid.LoadProfile(args.Entity.Value, profile); - } + var numNukies = _antagSelection.CalculateAntagCount(_playerManager.PlayerCount, component.PlayersPerOperative, component.MaxOps); - private void OnAfterAntagEntSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) - { - if (ent.Comp.TargetStation is not { } station) + //Dont continue if we have no nukies to spawn + if (numNukies == 0) return; - _antag.SendBriefing(args.Session, Loc.GetString("nukeops-welcome", - ("station", station), - ("name", Name(ent))), - Color.Red, - ent.Comp.GreetSoundNotification); + //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); } - /// - /// 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) + //For admins forcing someone to nukeOps. + public void MakeLoneNukie(EntityUid entity) { - if (!Resolve(ent, ref ent.Comp, false)) - return null; + if (!_mind.TryGetMind(entity, out var mindId, out var mindComponent)) + return; - return ent.Comp.MapGrids.Where(e => HasComp(e) && !HasComp(e)).FirstOrNull(); + //ok hardcoded value bad but so is everything else here + _roles.MindAddRole(mindId, new NukeopsRoleComponent { PrototypeId = NukeopsId }, mindComponent); + SetOutfitCommand.SetOutfit(entity, "SyndicateOperativeGearFull", EntityManager); } - /// - /// 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) + private sealed class NukieSpawn { - if (!Resolve(ent, ref ent.Comp, false)) - return null; + public ICommonSession? Session { get; private set; } + public NukeopSpawnPreset Type { get; private set; } - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var comp)) + public NukieSpawn(ICommonSession? session, NukeopSpawnPreset type) { - if (comp.AssociatedRule == ent.Owner) - return uid; + Session = session; + Type = type; } - - return null; } } diff --git a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs index e69de29bb2d..128f1123043 100644 --- a/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/PiratesRuleSystem.cs @@ -0,0 +1,321 @@ +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 5215da96aa8..b11c28fb2b0 100644 --- a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs @@ -1,5 +1,4 @@ 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 7e6901e6c48..d20775c7343 100644 --- a/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RevolutionaryRuleSystem.cs @@ -16,6 +16,7 @@ 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; @@ -23,11 +24,12 @@ 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 Content.Server.GameTicking.Components; +using System.Linq; namespace Content.Server.GameTicking.Rules; @@ -38,7 +40,7 @@ public sealed class RevolutionaryRuleSystem : GameRuleSystem RevolutionaryNpcFaction = "Revolutionary"; @@ -57,12 +60,23 @@ 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); @@ -84,29 +98,40 @@ protected override void ActiveTick(EntityUid uid, RevolutionaryRuleComponent com } } - protected override void AppendRoundEndText(EntityUid uid, RevolutionaryRuleComponent component, GameRuleComponent gameRule, - ref RoundEndTextAppendEvent args) + private void OnRoundEndText(RoundEndTextAppendEvent ev) { - base.AppendRoundEndText(uid, component, gameRule, ref args); - var revsLost = CheckRevsLose(); var commandLost = CheckCommandLose(); - // 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) + var query = AllEntityQuery(); + while (query.MoveNext(out var headrev)) { - var count = CompOrNull(mind)?.ConvertedCount ?? 0; - args.AddLine(Loc.GetString("rev-headrev-name-user", - ("name", name), - ("username", data.UserName), - ("count", count))); + // 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))); + } - // TODO: someone suggested listing all alive? revs maybe implement at some point + // TODO: someone suggested listing all alive? revs maybe implement at some point + } } } @@ -119,6 +144,57 @@ 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. /// @@ -157,7 +233,22 @@ private void OnPostFlash(EntityUid uid, HeadRevolutionaryComponent comp, ref Aft } if (mind?.Session != null) - _antag.SendBriefing(mind.Session, Loc.GetString("rev-role-greeting"), Color.Red, revComp.RevStartSound); + _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); } //TODO: Enemies of the revolution @@ -218,7 +309,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 _, mc)) + if (!_mind.TryGetMind(uid, out var mindId, out var mind, mc)) continue; // remove their antag role diff --git a/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs b/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs index f09ed3ebc3c..7755f684be2 100644 --- a/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/RoundstartStationVariationRuleSystem.cs @@ -1,5 +1,4 @@ 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 c60670a3ad7..a26a2d783c7 100644 --- a/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SandboxRuleSystem.cs @@ -1,4 +1,3 @@ -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 d5adb8fdb78..fa5f17b4f37 100644 --- a/Content.Server/GameTicking/Rules/SecretRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/SecretRuleSystem.cs @@ -1,5 +1,4 @@ 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 4486ee40fbb..42e7e82335c 100644 --- a/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs +++ b/Content.Server/GameTicking/Rules/SubGamemodesSystem.cs @@ -1,4 +1,3 @@ -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 083085fa0d8..32f9040f89f 100644 --- a/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ThiefRuleSystem.cs @@ -3,38 +3,118 @@ 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 AntagSelectionSystem _antag = default!; + [Dependency] private readonly SharedRoleSystem _roleSystem = default!; [Dependency] private readonly ObjectivesSystem _objectives = default!; + [Dependency] private readonly InventorySystem _inventory = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(AfterAntagSelected); + SubscribeLocalEvent(OnPlayersSpawned); SubscribeLocalEvent(OnGetBriefing); SubscribeLocalEvent(OnObjectivesTextGetInfo); } - private void AfterAntagSelected(Entity ent, ref AfterAntagEntitySelectedEvent args) + private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev) { - if (!_mindSystem.TryGetMind(args.EntityUid, out var mindId, out var mind)) + 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)) 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, ent); - _antag.SendBriefing(args.EntityUid, MakeBriefing(args.EntityUid), null, null); + 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); } private void GenerateObjectives(EntityUid mindId, MindComponent mind, ThiefRuleComponent thiefRule) @@ -80,7 +160,8 @@ private void OnGetBriefing(Entity thief, ref GetBriefingEven private string MakeBriefing(EntityUid thief) { var isHuman = HasComp(thief); - var briefing = isHuman + var briefing = "\n"; + briefing = isHuman ? Loc.GetString("thief-role-greeting-human") : Loc.GetString("thief-role-greeting-animal"); @@ -88,9 +169,9 @@ private string MakeBriefing(EntityUid thief) return briefing; } - private void OnObjectivesTextGetInfo(Entity ent, ref ObjectivesTextGetInfoEvent args) + private void OnObjectivesTextGetInfo(Entity thiefs, ref ObjectivesTextGetInfoEvent args) { - args.Minds = _antag.GetAntagMindEntityUids(ent.Owner); + args.Minds = thiefs.Comp.ThievesMinds; 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 1cc5e577041..a6f8522346f 100644 --- a/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/TraitorRuleSystem.cs @@ -6,61 +6,97 @@ 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; +using Content.Shared.Mood; 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 AntagSelectionSystem _antag = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = 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!; - public const int MaxPicks = 20; + private int PlayersPerTraitor => _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor); + private int MaxTraitors => _cfg.GetCVar(CCVars.TraitorMaxTraitors); public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(AfterEntitySelected); + SubscribeLocalEvent(OnStartAttempt); + SubscribeLocalEvent(OnPlayersSpawned); + SubscribeLocalEvent(HandleLatejoin); 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); } - private void AfterEntitySelected(Entity ent, ref AfterAntagEntitySelectedEvent args) + 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) { - MakeTraitor(args.EntityUid, ent); + TryRoundStartAttempt(ev, Loc.GetString("traitor-title")); } private void MakeCodewords(TraitorRuleComponent component) { - var adjectives = _prototypeManager.Index(component.CodewordAdjectives).Values; - var verbs = _prototypeManager.Index(component.CodewordVerbs).Values; + var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount); + var adjectives = _prototypeManager.Index(component.CodewordAdjectives).Values; + var verbs = _prototypeManager.Index(component.CodewordVerbs).Values; var codewordPool = adjectives.Concat(verbs).ToList(); - var finalCodewordCount = Math.Min(component.CodewordCount, codewordPool.Count); + var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count); component.Codewords = new string[finalCodewordCount]; for (var i = 0; i < finalCodewordCount; i++) { @@ -68,25 +104,66 @@ 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; - var briefing = Loc.GetString("traitor-role-codewords-short", ("codewords", string.Join(", ", component.Codewords))); - - if (TryComp(traitor, out var autoTraitorComponent)) + if (HasComp(mindId)) { - giveUplink = autoTraitorComponent.GiveUplink; - giveObjectives = autoTraitorComponent.GiveObjectives; + Log.Error($"Player {mind.CharacterName} is already a traitor."); + return false; } + 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 = component.StartingBalance; + var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance); if (_jobs.MindTryGetJob(mindId, out _, out var prototype)) startingBalance = Math.Max(startingBalance - prototype.AntagAdvantage, 0); @@ -104,25 +181,35 @@ public bool MakeTraitor(EntityUid traitor, TraitorRuleComponent component, bool Loc.GetString("traitor-role-uplink-code-short", ("code", string.Join("-", code).Replace("sharp", "#")))); } - _antag.SendBriefing(traitor, GenerateBriefing(component.Codewords, code), null, component.GreetSoundNotification); + _antagSelection.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 + Briefing = briefing.ToString() }, mind, true); // Change the faction _npcFaction.RemoveFaction(traitor, component.NanoTrasenFaction, false); _npcFaction.AddFaction(traitor, component.SyndicateFaction); + RaiseLocalEvent(traitor, new MoodEffectEvent("TraitorFocused")); + // Give traitors their objectives if (giveObjectives) { + var maxDifficulty = _cfg.GetCVar(CCVars.TraitorMaxDifficulty); + var maxPicks = _cfg.GetCVar(CCVars.TraitorMaxPicks); var difficulty = 0f; - for (var pick = 0; pick < MaxPicks && component.MaxDifficulty > difficulty; pick++) + Log.Debug($"Attempting {maxPicks} objective picks with {maxDifficulty} difficulty"); + for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++) { var objective = _objectives.GetRandomObjective(mindId, mind, component.ObjectiveGroup); if (objective == null) @@ -138,9 +225,53 @@ 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 = _antag.GetAntagMindEntityUids(uid); + args.Minds = comp.TraitorMinds; args.AgentName = Loc.GetString("traitor-round-end-agent-name"); } @@ -149,6 +280,27 @@ 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(); @@ -163,11 +315,9 @@ private string GenerateBriefing(string[] codewords, Note[]? uplinkCode) public List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind) { List<(EntityUid Id, MindComponent Mind)> allTraitors = new(); - - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var traitor)) + foreach (var traitor in EntityQuery()) { - foreach (var role in GetOtherTraitorMindsAliveAndConnected(ourMind, (uid, traitor))) + foreach (var role in GetOtherTraitorMindsAliveAndConnected(ourMind, traitor)) { if (!allTraitors.Contains(role)) allTraitors.Add(role); @@ -177,15 +327,20 @@ private string GenerateBriefing(string[] codewords, Note[]? uplinkCode) return allTraitors; } - private List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind, Entity rule) + private List<(EntityUid Id, MindComponent Mind)> GetOtherTraitorMindsAliveAndConnected(MindComponent ourMind, TraitorRuleComponent component) { var traitors = new List<(EntityUid Id, MindComponent Mind)>(); - foreach (var mind in _antag.GetAntagMinds(rule.Owner)) + foreach (var traitor in component.TraitorMinds) { - if (mind.Comp == ourMind) - continue; - - traitors.Add((mind, mind)); + 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)); + } } return traitors; diff --git a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs index f62d0b79ffb..5714337d4db 100644 --- a/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs +++ b/Content.Server/GameTicking/Rules/ZombieRuleSystem.cs @@ -1,35 +1,46 @@ +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 _antag = default!; + [Dependency] private readonly AntagSelectionSystem _antagSelection = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly AnnouncerSystem _announcer = default!; [Dependency] private readonly GameTicker _gameTicker = default!; @@ -38,56 +49,67 @@ public override void Initialize() { base.Initialize(); + SubscribeLocalEvent(OnStartAttempt); + SubscribeLocalEvent(OnRoundEndText); SubscribeLocalEvent(OnZombifySelf); } - protected override void AppendRoundEndText(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, - ref RoundEndTextAppendEvent args) + /// + /// Set the required minimum players for this gamemode to start + /// + protected override void Added(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, GameRuleAddedEvent args) { - 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) - { - args.AddLine(Loc.GetString("zombie-round-end-user-was-initial", - ("name", entName), - ("username", data.UserName))); - } + base.Added(uid, component, gameRule, args); - 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) + gameRule.MinPlayers = _cfg.GetCVar(CCVars.ZombieMinPlayers); + } + + private void OnRoundEndText(RoundEndTextAppendEvent ev) + { + foreach (var zombie in EntityQuery()) { - var meta = MetaData(survivor); - var username = string.Empty; - if (_mindSystem.TryGetMind(survivor, out _, out var mind) && mind.Session != null) + // 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")); + + ev.AddLine(Loc.GetString("zombie-round-end-initial-count", ("initialCount", zombie.InitialInfectedNames.Count))); + foreach (var player in zombie.InitialInfectedNames) { - username = mind.Session.Name; + ev.AddLine(Loc.GetString("zombie-round-end-user-was-initial", + ("name", player.Key), + ("username", player.Value))); } - args.AddLine(Loc.GetString("zombie-round-end-user-was-survivor", - ("name", meta.EntityName), - ("username", username))); + 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))); + } } } @@ -117,20 +139,38 @@ 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); - component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; + var delay = _random.Next(component.MinStartDelay, component.MaxStartDelay); + component.StartTime = _timing.CurTime + delay; } protected override void ActiveTick(EntityUid uid, ZombieRuleComponent component, GameRuleComponent gameRule, float frameTime) { base.ActiveTick(uid, component, gameRule, frameTime); - if (!component.NextRoundEndCheck.HasValue || component.NextRoundEndCheck > _timing.CurTime) - return; - CheckRoundEnd(component); - component.NextRoundEndCheck = _timing.CurTime + component.EndCheckDelay; + + 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; + } } private void OnZombifySelf(EntityUid uid, PendingZombieComponent component, ZombifySelfActionEvent args) @@ -195,4 +235,81 @@ 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/Interaction/InteractionPopupSystem.cs b/Content.Server/Interaction/InteractionPopupSystem.cs index 77b76f898a7..a028598df03 100644 --- a/Content.Server/Interaction/InteractionPopupSystem.cs +++ b/Content.Server/Interaction/InteractionPopupSystem.cs @@ -5,6 +5,7 @@ using Content.Shared.Interaction; using Content.Shared.Mobs.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Mood; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Player; @@ -78,7 +79,19 @@ private void SharedInteract( if (_random.Prob(component.SuccessChance)) { if (component.InteractSuccessString != null) + { msg = Loc.GetString(component.InteractSuccessString, ("target", Identity.Entity(uid, EntityManager))); // Success message (localized). + if (component.InteractSuccessString == "hugging-success-generic") + { + var ev = new MoodEffectEvent("BeingHugged"); + RaiseLocalEvent(target, ev); + } + else if (component.InteractSuccessString.Contains("petting-success-")) + { + var ev = new MoodEffectEvent("PetAnimal"); + RaiseLocalEvent(user, ev); + } + } if (component.InteractSuccessSound != null) sfx = component.InteractSuccessSound; diff --git a/Content.Server/IoC/ServerContentIoC.cs b/Content.Server/IoC/ServerContentIoC.cs index bc399977358..fa263e059de 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/MassMedia/Systems/NewsSystem.cs b/Content.Server/MassMedia/Systems/NewsSystem.cs index 2b18b57ff8b..c313b0d4ccd 100644 --- a/Content.Server/MassMedia/Systems/NewsSystem.cs +++ b/Content.Server/MassMedia/Systems/NewsSystem.cs @@ -141,9 +141,6 @@ private void OnWriteUiPublishMessage(Entity ent, ref NewsWr if (msg.Session.AttachedEntity is not { } author) return; - if (!_accessReader.FindStationRecordKeys(author, out _)) - return; - string? authorName = null; if (_idCardSystem.TryFindIdCard(author, out var idCard)) authorName = idCard.Comp.FullName; diff --git a/Content.Server/Medical/VomitSystem.cs b/Content.Server/Medical/VomitSystem.cs index 8c3b15aed33..dc049b2a1d6 100644 --- a/Content.Server/Medical/VomitSystem.cs +++ b/Content.Server/Medical/VomitSystem.cs @@ -11,6 +11,7 @@ using Content.Shared.Nutrition.EntitySystems; using Content.Shared.StatusEffect; using Robust.Server.Audio; +using Content.Shared.Mood; using Robust.Shared.Audio; using Robust.Shared.Prototypes; @@ -94,6 +95,8 @@ public void Vomit(EntityUid uid, float thirstAdded = -40f, float hungerAdded = - // Force sound to play as spill doesn't work if solution is empty. _audio.PlayPvs("/Audio/Effects/Fluids/splat.ogg", uid, AudioParams.Default.WithVariation(0.2f).WithVolume(-4f)); _popup.PopupEntity(Loc.GetString("disease-vomit", ("person", Identity.Entity(uid, EntityManager))), uid); + + RaiseLocalEvent(uid, new MoodEffectEvent("MobVomit")); } } } diff --git a/Content.Server/Mood/MoodComponent.cs b/Content.Server/Mood/MoodComponent.cs new file mode 100644 index 00000000000..7fd4a7136f3 --- /dev/null +++ b/Content.Server/Mood/MoodComponent.cs @@ -0,0 +1,111 @@ +using Content.Shared.Alert; +using Content.Shared.FixedPoint; +using Robust.Shared.Serialization.TypeSerializers.Implementations.Generic; + +namespace Content.Server.Mood; + +[RegisterComponent] +public sealed partial class MoodComponent : Component +{ + [DataField] + public float CurrentMoodLevel; + + [DataField] + public MoodThreshold CurrentMoodThreshold; + + [DataField] + public MoodThreshold LastThreshold; + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary CategorisedEffects = new(); + + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary UncategorisedEffects = new(); + + /// + /// The formula for the movement speed modifier is SpeedBonusGrowth ^ (MoodLevel - MoodThreshold.Neutral). + /// Change this ONLY BY 0.001 AT A TIME. + /// + [DataField] + public float SpeedBonusGrowth = 1.003f; + + /// + /// The lowest point that low morale can multiply our movement speed by. Lowering speed follows a linear curve, rather than geometric. + /// + [DataField] + public float MinimumSpeedModifier = 0.75f; + + /// + /// The maximum amount that high morale can multiply our movement speed by. This follows a significantly slower geometric sequence. + /// + [DataField] + public float MaximumSpeedModifier = 1.15f; + + [DataField] + public float IncreaseCritThreshold = 1.2f; + + [DataField] + public float DecreaseCritThreshold = 0.9f; + + [ViewVariables(VVAccess.ReadOnly)] + public FixedPoint2 CritThresholdBeforeModify; + + [DataField(customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary MoodThresholds = new() + { + { MoodThreshold.Perfect, 100f }, + { MoodThreshold.Exceptional, 80f }, + { MoodThreshold.Great, 70f }, + { MoodThreshold.Good, 60f }, + { MoodThreshold.Neutral, 50f }, + { MoodThreshold.Meh, 40f }, + { MoodThreshold.Bad, 30f }, + { MoodThreshold.Terrible, 20f }, + { MoodThreshold.Horrible, 10f }, + { MoodThreshold.Dead, 0f } + }; + + [DataField(customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary MoodThresholdsAlerts = new() + { + { MoodThreshold.Dead, AlertType.MoodDead }, + { MoodThreshold.Horrible, AlertType.Horrible }, + { MoodThreshold.Terrible, AlertType.Terrible }, + { MoodThreshold.Bad, AlertType.Bad }, + { MoodThreshold.Meh, AlertType.Meh }, + { MoodThreshold.Neutral, AlertType.Neutral }, + { MoodThreshold.Good, AlertType.Good }, + { MoodThreshold.Great, AlertType.Great }, + { MoodThreshold.Exceptional, AlertType.Exceptional }, + { MoodThreshold.Perfect, AlertType.Perfect }, + { MoodThreshold.Insane, AlertType.Insane } + }; + + /// + /// These thresholds represent a percentage of Crit-Threshold, 0.8 corresponding with 80%. + /// + [DataField(customTypeSerializer: typeof(DictionarySerializer))] + public Dictionary HealthMoodEffectsThresholds = new() + { + { "HealthHeavyDamage", 0.8f }, + { "HealthSevereDamage", 0.5f }, + { "HealthLightDamage", 0.1f }, + { "HealthNoDamage", 0.05f } + }; +} + +[Serializable] +public enum MoodThreshold : ushort +{ + Insane = 1, + Horrible = 2, + Terrible = 3, + Bad = 4, + Meh = 5, + Neutral = 6, + Good = 7, + Great = 8, + Exceptional = 9, + Perfect = 10, + Dead = 0 +} diff --git a/Content.Server/Mood/MoodSystem.cs b/Content.Server/Mood/MoodSystem.cs new file mode 100644 index 00000000000..f0c293971a1 --- /dev/null +++ b/Content.Server/Mood/MoodSystem.cs @@ -0,0 +1,422 @@ +using Content.Server.Chat.Managers; +using Content.Server.Popups; +using Content.Shared.Alert; +using Content.Shared.Chat; +using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.Movement.Systems; +using Content.Shared.Mood; +using Content.Shared.Overlays; +using Content.Shared.Popups; +using Content.Shared.Traits.Assorted.Components; +using JetBrains.Annotations; +using Robust.Shared.Prototypes; +using Timer = Robust.Shared.Timing.Timer; +using Robust.Server.Player; +using Robust.Shared.Player; +using Robust.Shared.Configuration; +using Content.Shared.CCVar; + +namespace Content.Server.Mood; + +public sealed class MoodSystem : EntitySystem +{ + [Dependency] private readonly AlertsSystem _alerts = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; + [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly MobThresholdSystem _mobThreshold = default!; + [Dependency] private readonly PopupSystem _popup = default!; + [Dependency] private readonly IConfigurationManager _config = default!; + + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnMobStateChanged); + SubscribeLocalEvent(OnMoodEffect); + SubscribeLocalEvent(OnDamageChange); + SubscribeLocalEvent(OnRefreshMoveSpeed); + SubscribeLocalEvent(OnRemoveEffect); + + SubscribeLocalEvent(OnTraitStartup); + } + + + private void OnRemoveEffect(EntityUid uid, MoodComponent component, MoodRemoveEffectEvent args) + { + if (component.UncategorisedEffects.TryGetValue(args.EffectId, out _)) + RemoveTimedOutEffect(uid, args.EffectId); + else + foreach (var (category, id) in component.CategorisedEffects) + if (id == args.EffectId) + { + RemoveTimedOutEffect(uid, args.EffectId, category); + return; + } + } + + private void OnRefreshMoveSpeed(EntityUid uid, MoodComponent component, RefreshMovementSpeedModifiersEvent args) + { + if (component.CurrentMoodThreshold is > MoodThreshold.Meh and < MoodThreshold.Good or MoodThreshold.Dead + || _jetpack.IsUserFlying(uid)) + return; + + // This ridiculous math serves a purpose making high mood less impactful on movement speed than low mood + var modifier = + Math.Clamp( + (component.CurrentMoodLevel >= component.MoodThresholds[MoodThreshold.Neutral]) + ? _config.GetCVar(CCVars.MoodIncreasesSpeed) + ? MathF.Pow(1.003f, component.CurrentMoodLevel - component.MoodThresholds[MoodThreshold.Neutral]) + : 1 + : _config.GetCVar(CCVars.MoodDecreasesSpeed) + ? 2 - component.MoodThresholds[MoodThreshold.Neutral] / component.CurrentMoodLevel + : 1, + component.MinimumSpeedModifier, + component.MaximumSpeedModifier); + + args.ModifySpeed(1, modifier); + } + + private void OnTraitStartup(EntityUid uid, MoodModifyTraitComponent component, ComponentStartup args) + { + if (component.MoodId != null) + RaiseLocalEvent(uid, new MoodEffectEvent($"{component.MoodId}")); + } + + private void OnMoodEffect(EntityUid uid, MoodComponent component, MoodEffectEvent args) + { + if (!_config.GetCVar(CCVars.MoodEnabled) + || !_prototypeManager.TryIndex(args.EffectId, out var prototype)) + return; + + var ev = new OnMoodEffect(uid, args.EffectId, args.EffectModifier, args.EffectOffset); + RaiseLocalEvent(uid, ref ev); + + ApplyEffect(uid, component, prototype, ev.EffectModifier, ev.EffectOffset); + } + + private void ApplyEffect(EntityUid uid, MoodComponent component, MoodEffectPrototype prototype, float eventModifier = 1, float eventOffset = 0) + { + // Apply categorised effect + if (prototype.Category != null) + { + if (component.CategorisedEffects.TryGetValue(prototype.Category, out var oldPrototypeId)) + { + if (!_prototypeManager.TryIndex(oldPrototypeId, out var oldPrototype)) + return; + + if (prototype.ID != oldPrototype.ID) + { + SendEffectText(uid, prototype); + component.CategorisedEffects[prototype.Category] = prototype.ID; + } + } + else + { + component.CategorisedEffects.Add(prototype.Category, prototype.ID); + } + + if (prototype.Timeout != 0) + Timer.Spawn(TimeSpan.FromSeconds(prototype.Timeout), () => RemoveTimedOutEffect(uid, prototype.ID, prototype.Category)); + } + // Apply uncategorised effect + else + { + if (component.UncategorisedEffects.TryGetValue(prototype.ID, out _)) + return; + + var moodChange = prototype.MoodChange * eventModifier + eventOffset; + if (moodChange == 0) + return; + + SendEffectText(uid, prototype); + component.UncategorisedEffects.Add(prototype.ID, moodChange); + + if (prototype.Timeout != 0) + Timer.Spawn(TimeSpan.FromSeconds(prototype.Timeout), () => RemoveTimedOutEffect(uid, prototype.ID)); + } + + RefreshMood(uid, component); + } + + private void SendEffectText(EntityUid uid, MoodEffectPrototype prototype) + { + if (!prototype.Hidden) + _popup.PopupEntity(prototype.Description, uid, uid, (prototype.MoodChange > 0) ? PopupType.Medium : PopupType.MediumCaution); + } + + private void RemoveTimedOutEffect(EntityUid uid, string prototypeId, string? category = null) + { + if (!TryComp(uid, out var comp)) + return; + + if (category == null) + { + if (!comp.UncategorisedEffects.ContainsKey(prototypeId)) + return; + comp.UncategorisedEffects.Remove(prototypeId); + } + else + { + if (!comp.CategorisedEffects.TryGetValue(category, out var currentProtoId) + || currentProtoId != prototypeId + || !_prototypeManager.HasIndex(currentProtoId)) + return; + comp.CategorisedEffects.Remove(category); + } + + RefreshMood(uid, comp); + } + + private void OnMobStateChanged(EntityUid uid, MoodComponent component, MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead && args.OldMobState != MobState.Dead) + { + var ev = new MoodEffectEvent("Dead"); + RaiseLocalEvent(uid, ev); + } + else if (args.OldMobState == MobState.Dead && args.NewMobState != MobState.Dead) + { + var ev = new MoodRemoveEffectEvent("Dead"); + RaiseLocalEvent(uid, ev); + } + RefreshMood(uid, component); + } + + // + // Recalculate the mood level of an entity by summing up all moodlets. + // + private void RefreshMood(EntityUid uid, MoodComponent component) + { + var amount = 0f; + + foreach (var (_, protoId) in component.CategorisedEffects) + { + if (!_prototypeManager.TryIndex(protoId, out var prototype)) + continue; + + amount += prototype.MoodChange; + } + + foreach (var (_, value) in component.UncategorisedEffects) + amount += value; + + SetMood(uid, amount, component, refresh: true); + } + + private void OnInit(EntityUid uid, MoodComponent component, ComponentStartup args) + { + if (TryComp(uid, out var mobThresholdsComponent) + && _mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var critThreshold, mobThresholdsComponent)) + component.CritThresholdBeforeModify = critThreshold.Value; + + EnsureComp(uid); + RefreshMood(uid, component); + } + + private void SetMood(EntityUid uid, float amount, MoodComponent? component = null, bool force = false, bool refresh = false) + { + if (!_config.GetCVar(CCVars.MoodEnabled) + || !Resolve(uid, ref component) + || component.CurrentMoodThreshold == MoodThreshold.Dead && !refresh) + return; + + var neutral = component.MoodThresholds[MoodThreshold.Neutral]; + var ev = new OnSetMoodEvent(uid, amount, false); + RaiseLocalEvent(uid, ref ev); + + if (ev.Cancelled) + return; + else + { + uid = ev.Receiver; + amount = ev.MoodChangedAmount; + } + + var newMoodLevel = amount + neutral; + if (!force) + newMoodLevel = Math.Clamp(amount + neutral, + component.MoodThresholds[MoodThreshold.Dead], + component.MoodThresholds[MoodThreshold.Perfect]); + + component.CurrentMoodLevel = newMoodLevel; + + if (TryComp(uid, out var mood)) + { + mood.CurrentMoodLevel = component.CurrentMoodLevel; + mood.NeutralMoodThreshold = component.MoodThresholds.GetValueOrDefault(MoodThreshold.Neutral); + } + + UpdateCurrentThreshold(uid, component); + } + + private void UpdateCurrentThreshold(EntityUid uid, MoodComponent? component = null) + { + if (!Resolve(uid, ref component)) + return; + + var calculatedThreshold = GetMoodThreshold(component); + if (calculatedThreshold == component.CurrentMoodThreshold) + return; + + component.CurrentMoodThreshold = calculatedThreshold; + + DoMoodThresholdsEffects(uid, component); + } + + private void DoMoodThresholdsEffects(EntityUid uid, MoodComponent? component = null, bool force = false) + { + if (!Resolve(uid, ref component) + || component.CurrentMoodThreshold == component.LastThreshold && !force) + return; + + var modifier = GetMovementThreshold(component.CurrentMoodThreshold); + + // Modify mob stats + if (modifier != GetMovementThreshold(component.LastThreshold)) + { + _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + SetCritThreshold(uid, component, modifier); + RefreshShaders(uid, modifier); + } + + // Modify interface + if (component.MoodThresholdsAlerts.TryGetValue(component.CurrentMoodThreshold, out var alertId)) + _alerts.ShowAlert(uid, alertId); + else + _alerts.ClearAlertCategory(uid, AlertCategory.Mood); + + component.LastThreshold = component.CurrentMoodThreshold; + } + + private void RefreshShaders(EntityUid uid, int modifier) + { + if (modifier == -1) + EnsureComp(uid); + else + RemComp(uid); + } + + private void SetCritThreshold(EntityUid uid, MoodComponent component, int modifier) + { + if (!TryComp(uid, out var mobThresholds) + || !_mobThreshold.TryGetThresholdForState(uid, MobState.Critical, out var key)) + return; + + var newKey = modifier switch + { + 1 => FixedPoint2.New(key.Value.Float() * component.IncreaseCritThreshold), + -1 => FixedPoint2.New(key.Value.Float() * component.DecreaseCritThreshold), + _ => component.CritThresholdBeforeModify + }; + + component.CritThresholdBeforeModify = key.Value; + _mobThreshold.SetMobStateThreshold(uid, newKey, MobState.Critical, mobThresholds); + } + + private MoodThreshold GetMoodThreshold(MoodComponent component, float? moodLevel = null) + { + moodLevel ??= component.CurrentMoodLevel; + var result = MoodThreshold.Dead; + var value = component.MoodThresholds[MoodThreshold.Perfect]; + + foreach (var threshold in component.MoodThresholds) + if (threshold.Value <= value && threshold.Value >= moodLevel) + { + result = threshold.Key; + value = threshold.Value; + } + + return result; + } + + private int GetMovementThreshold(MoodThreshold threshold) + { + return threshold switch + { + >= MoodThreshold.Good => 1, + <= MoodThreshold.Meh => -1, + _ => 0 + }; + } + + private void OnDamageChange(EntityUid uid, MoodComponent component, DamageChangedEvent args) + { + if (!_mobThreshold.TryGetPercentageForState(uid, MobState.Critical, args.Damageable.TotalDamage, out var damage)) + return; + + var protoId = "HealthNoDamage"; + var value = component.HealthMoodEffectsThresholds["HealthNoDamage"]; + + foreach (var threshold in component.HealthMoodEffectsThresholds) + if (threshold.Value <= damage && threshold.Value >= value) + { + protoId = threshold.Key; + value = threshold.Value; + } + + var ev = new MoodEffectEvent(protoId); + RaiseLocalEvent(uid, ev); + } +} + +[UsedImplicitly] +[DataDefinition] +public sealed partial class ShowMoodEffects : IAlertClick +{ + public void AlertClicked(EntityUid uid) + { + var entityManager = IoCManager.Resolve(); + var prototypeManager = IoCManager.Resolve(); + var chatManager = IoCManager.Resolve(); + var playerManager = IoCManager.Resolve(); + + if (!entityManager.TryGetComponent(uid, out var comp) + || comp.CurrentMoodThreshold == MoodThreshold.Dead + || !playerManager.TryGetSessionByEntity(uid, out var session) + || session == null) + return; + + var msgStart = Loc.GetString("mood-show-effects-start"); + chatManager.ChatMessageToOne(ChatChannel.Emotes, msgStart, msgStart, EntityUid.Invalid, false, + session.Channel); + + foreach (var (_, protoId) in comp.CategorisedEffects) + { + if (!prototypeManager.TryIndex(protoId, out var proto) + || proto.Hidden) + continue; + + SendDescToChat(proto, session); + } + + foreach (var (protoId, _) in comp.UncategorisedEffects) + { + if (!prototypeManager.TryIndex(protoId, out var proto) + || proto.Hidden) + continue; + + SendDescToChat(proto, session); + } + } + + private void SendDescToChat(MoodEffectPrototype proto, ICommonSession session) + { + if (session == null) + return; + + var chatManager = IoCManager.Resolve(); + + var color = (proto.MoodChange > 0) ? "#008000" : "#BA0000"; + var msg = $"[font size=10][color={color}]{proto.Description}[/color][/font]"; + + chatManager.ChatMessageToOne(ChatChannel.Emotes, msg, msg, EntityUid.Invalid, false, + session.Channel); + } +} diff --git a/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs b/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs index a23a5b3d77d..859f22cd4a9 100644 --- a/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs +++ b/Content.Server/Nyanotrasen/Chemistry/Effects/ChemRemovePsionic.cs @@ -21,7 +21,7 @@ public override void Effect(ReagentEffectArgs args) var psySys = args.EntityManager.EntitySysManager.GetEntitySystem(); - psySys.RemovePsionics(args.SolutionEntity); + psySys.RemoveAllPsionicPowers(args.SolutionEntity, true); } } } diff --git a/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs index 94a488bd84b..7abbdcdab3b 100644 --- a/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs +++ b/Content.Server/Nyanotrasen/StationEvents/Events/MidRoundAntagRule.cs @@ -1,4 +1,3 @@ -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 47fe4eb5f88..20205b8b72f 100644 --- a/Content.Server/Objectives/ObjectivesSystem.cs +++ b/Content.Server/Objectives/ObjectivesSystem.cs @@ -1,7 +1,10 @@ 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; @@ -9,9 +12,7 @@ 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; @@ -19,8 +20,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() @@ -178,9 +179,7 @@ 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) @@ -245,14 +244,8 @@ private bool IsInCustody(EntityUid mindId, MindComponent? mind = null) return null; var name = mind.CharacterName; - var username = (string?) null; - - if (mind.OriginalOwnerUserId != null && - _player.TryGetPlayerData(mind.OriginalOwnerUserId.Value, out var sessionData)) - { - username = sessionData.UserName; - } - + _mind.TryGetSession(mindId, out var session); + var username = session?.Name; if (username != null) { diff --git a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs index 0e20f007d71..107d09c8980 100644 --- a/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs +++ b/Content.Server/Power/EntitySystems/PowerMonitoringConsoleSystem.cs @@ -17,7 +17,6 @@ using Robust.Shared.Utility; using System.Linq; using System.Diagnostics.CodeAnalysis; -using Content.Server.GameTicking.Components; namespace Content.Server.Power.EntitySystems; @@ -724,8 +723,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 c3efe14be96..e262fde64d2 100644 --- a/Content.Server/Preferences/Managers/ServerPreferencesManager.cs +++ b/Content.Server/Preferences/Managers/ServerPreferencesManager.cs @@ -256,20 +256,6 @@ 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/Psionics/AntiPsychicWeaponComponent.cs b/Content.Server/Psionics/AntiPsychicWeaponComponent.cs index 00528afbe95..32238c2adbf 100644 --- a/Content.Server/Psionics/AntiPsychicWeaponComponent.cs +++ b/Content.Server/Psionics/AntiPsychicWeaponComponent.cs @@ -2,23 +2,59 @@ namespace Content.Server.Psionics { + /// + /// A component for weapons intended to have special effects when wielded against Psionic Entities. + /// [RegisterComponent] public sealed partial class AntiPsionicWeaponComponent : Component { - [DataField("modifiers", required: true)] + [DataField(required: true)] public DamageModifierSet Modifiers = default!; - [DataField("psychicStaminaDamage")] + [DataField] public float PsychicStaminaDamage = 30f; - [DataField("disableChance")] + /// + /// How long (in seconds) should this weapon temporarily disable powers + /// + [DataField] + public float DisableDuration = 10f; + + /// + /// The chances of this weapon temporarily disabling psionic powers + /// + [DataField] public float DisableChance = 0.3f; /// - /// Punish when used against a non-psychic. + /// The condition to be inflicted on a Psionic entity + /// + [DataField] + public string DisableStatus = "PsionicsDisabled"; + + /// + /// Whether or not the user of this weapon risks Punishment by the gods if they dare use it on non-Psionic Entities /// + /// The odds of divine punishment per non-Psionic Entity attacked + /// + [DataField] + public float PunishChances = 0.5f; + + /// + /// How much Shock damage to take when Punish(ed) by the gods for using this weapon + /// + [DataField] + public int PunishSelfDamage = 20; + + /// + /// How long (in seconds) should the user be stunned when punished by the gods + /// + [DataField] + public float PunishStunDuration = 5f; } } diff --git a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs index 31e6b89f13d..751dc28f8cf 100644 --- a/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs +++ b/Content.Server/Psionics/Invisibility/PsionicInvisibilitySystem.cs @@ -3,6 +3,7 @@ using Content.Shared.Eye; using Content.Server.NPC.Systems; using Robust.Shared.Containers; +using Robust.Shared.Player; using Robust.Server.GameObjects; namespace Content.Server.Psionics @@ -17,13 +18,12 @@ public override void Initialize() { base.Initialize(); /// Masking - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnInsulInit); SubscribeLocalEvent(OnInsulShutdown); - SubscribeLocalEvent(OnEyeInit); /// Layer - SubscribeLocalEvent(OnInvisInit); + SubscribeLocalEvent(OnInvisInit); SubscribeLocalEvent(OnInvisShutdown); // PVS Stuff @@ -31,16 +31,14 @@ public override void Initialize() SubscribeLocalEvent(OnEntRemoved); } - private void OnInit(EntityUid uid, PotentialPsionicComponent component, ComponentInit args) + private void OnInit(EntityUid uid, ActorComponent component, ComponentInit args) { - SetCanSeePsionicInvisiblity(uid, false); + if (!HasComp(uid)) + SetCanSeePsionicInvisiblity(uid, false); } private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, ComponentInit args) { - if (!HasComp(uid)) - return; - if (HasComp(uid)) _invisSystem.ToggleInvisibility(uid); @@ -61,9 +59,6 @@ private void OnInsulInit(EntityUid uid, PsionicInsulationComponent component, Co private void OnInsulShutdown(EntityUid uid, PsionicInsulationComponent component, ComponentShutdown args) { - if (!HasComp(uid)) - return; - SetCanSeePsionicInvisiblity(uid, false); if (!HasComp(uid)) @@ -79,7 +74,7 @@ private void OnInsulShutdown(EntityUid uid, PsionicInsulationComponent component component.SuppressedFactions.Clear(); } - private void OnInvisInit(EntityUid uid, PsionicallyInvisibleComponent component, ComponentInit args) + private void OnInvisInit(EntityUid uid, PsionicallyInvisibleComponent component, ComponentStartup args) { var visibility = EntityManager.EnsureComponent(uid); @@ -99,10 +94,6 @@ private void OnInvisShutdown(EntityUid uid, PsionicallyInvisibleComponent compon } } - private void OnEyeInit(EntityUid uid, EyeComponent component, ComponentInit args) - { - //SetCanSeePsionicInvisiblity(uid, true); //JJ Comment - Not allowed to modifies .yml on spawn any longer. See UninitializedSaveTest. - } private void OnEntInserted(EntityUid uid, PsionicallyInvisibleComponent component, EntInsertedIntoContainerMessage args) { DirtyEntity(args.Entity); @@ -121,11 +112,12 @@ public void SetCanSeePsionicInvisiblity(EntityUid uid, bool set) { _eye.SetVisibilityMask(uid, eye.VisibilityMask | (int) VisibilityFlags.PsionicInvisibility, eye); } - } else + } + else { if (EntityManager.TryGetComponent(uid, out EyeComponent? eye)) { - _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~ (int) VisibilityFlags.PsionicInvisibility, eye); + _eye.SetVisibilityMask(uid, eye.VisibilityMask & ~(int) VisibilityFlags.PsionicInvisibility, eye); } } } diff --git a/Content.Server/Psionics/PotentialPsionicComponent.cs b/Content.Server/Psionics/PotentialPsionicComponent.cs deleted file mode 100644 index 9499497cd1d..00000000000 --- a/Content.Server/Psionics/PotentialPsionicComponent.cs +++ /dev/null @@ -1,14 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PotentialPsionicComponent : Component - { - [DataField("chance")] - public float Chance = 0.04f; - - /// - /// YORO (you only reroll once) - /// - public bool Rerolled = false; - } -} diff --git a/Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs b/Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs deleted file mode 100644 index f9cc9339d4e..00000000000 --- a/Content.Server/Psionics/PsionicAwaitingPlayerComponent.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Content.Server.Psionics -{ - /// - /// Will open the 'accept psionics' UI when a player attaches. - /// - [RegisterComponent] - public sealed partial class PsionicAwaitingPlayerComponent : Component - {} -} diff --git a/Content.Server/Psionics/PsionicBonusChanceComponent.cs b/Content.Server/Psionics/PsionicBonusChanceComponent.cs deleted file mode 100644 index d9cbc511477..00000000000 --- a/Content.Server/Psionics/PsionicBonusChanceComponent.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace Content.Server.Psionics -{ - [RegisterComponent] - public sealed partial class PsionicBonusChanceComponent : Component - { - [DataField("multiplier")] - public float Multiplier = 1f; - [DataField("flatBonus")] - public float FlatBonus = 0; - - /// - /// Whether we should warn the user they are about to receive psionics. - /// It's here because AddComponentSpecial can't overwrite a component, and this is very role dependent. - /// - [DataField("warn")] - public bool Warn = true; - } -} diff --git a/Content.Server/Psionics/PsionicsCommands.cs b/Content.Server/Psionics/PsionicsCommands.cs index 959251d1fb7..5c273461f1e 100644 --- a/Content.Server/Psionics/PsionicsCommands.cs +++ b/Content.Server/Psionics/PsionicsCommands.cs @@ -1,10 +1,7 @@ using Content.Server.Administration; using Content.Shared.Administration; using Content.Shared.Abilities.Psionics; -using Content.Shared.Mobs.Components; using Robust.Shared.Console; -using Robust.Server.GameObjects; -using Content.Shared.Actions; using Robust.Shared.Player; namespace Content.Server.Psionics; @@ -17,19 +14,14 @@ public sealed class ListPsionicsCommand : IConsoleCommand public string Help => Loc.GetString("command-lspsionic-help"); public async void Execute(IConsoleShell shell, string argStr, string[] args) { - SharedActionsSystem actions = default!; var entMan = IoCManager.Resolve(); - foreach (var (actor, mob, psionic, meta) in entMan.EntityQuery()){ - // filter out xenos, etc, with innate telepathy - actions.TryGetActionData( psionic.PsionicAbility, out var actionData ); - if (actionData == null || actionData.ToString() == null) - return; + foreach (var (actor, psionic, meta) in entMan.EntityQuery()) + { + var powerList = new List(); + foreach (var power in psionic.ActivePowers) + powerList.Add(power.Name); - var psiPowerName = actionData.ToString(); - if (psiPowerName == null) - return; - - shell.WriteLine(meta.EntityName + " (" + meta.Owner + ") - " + actor.PlayerSession.Name + Loc.GetString(psiPowerName)); + shell.WriteLine(meta.EntityName + " (" + meta.Owner + ") - " + actor.PlayerSession.Name + powerList); } } } diff --git a/Content.Server/Psionics/PsionicsSystem.Events.cs b/Content.Server/Psionics/PsionicsSystem.Events.cs new file mode 100644 index 00000000000..82a0faec64b --- /dev/null +++ b/Content.Server/Psionics/PsionicsSystem.Events.cs @@ -0,0 +1,19 @@ + +namespace Content.Server.Psionics +{ + /// + /// Raised on an entity about to roll for a Psionic Power, after their baseline chances of success are calculated. + /// + [ByRefEvent] + public struct OnRollPsionicsEvent + { + public readonly EntityUid Roller; + public float BaselineChance; + public OnRollPsionicsEvent(EntityUid roller, float baselineChance) + { + Roller = roller; + BaselineChance = baselineChance; + } + } +} + diff --git a/Content.Server/Psionics/PsionicsSystem.cs b/Content.Server/Psionics/PsionicsSystem.cs index fb5d18f2843..23cf6aeb80b 100644 --- a/Content.Server/Psionics/PsionicsSystem.cs +++ b/Content.Server/Psionics/PsionicsSystem.cs @@ -5,7 +5,6 @@ using Content.Shared.Damage.Events; using Content.Shared.CCVar; using Content.Server.Abilities.Psionics; -using Content.Server.Chat.Systems; using Content.Server.Electrocution; using Content.Server.NPC.Components; using Content.Server.NPC.Systems; @@ -27,11 +26,14 @@ public sealed class PsionicsSystem : EntitySystem [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; + private const string BaselineAmplification = "Baseline Amplification"; + private const string BaselineDampening = "Baseline Dampening"; + /// /// Unfortunately, since spawning as a normal role and anything else is so different, /// this is the only way to unify them, for now at least. /// - Queue<(PotentialPsionicComponent component, EntityUid uid)> _rollers = new(); + Queue<(PsionicComponent component, EntityUid uid)> _rollers = new(); public override void Update(float frameTime) { base.Update(frameTime); @@ -42,19 +44,16 @@ public override void Update(float frameTime) public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnStartup); + SubscribeLocalEvent(OnStartup); SubscribeLocalEvent(OnMeleeHit); SubscribeLocalEvent(OnStamHit); - SubscribeLocalEvent(OnInit); + SubscribeLocalEvent(OnInit); SubscribeLocalEvent(OnRemove); } - private void OnStartup(EntityUid uid, PotentialPsionicComponent component, MapInitEvent args) + private void OnStartup(EntityUid uid, PsionicComponent component, MapInitEvent args) { - if (HasComp(uid)) - return; - _rollers.Enqueue((component, uid)); } @@ -67,7 +66,7 @@ private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, Mel _audio.PlayPvs("/Audio/Effects/lightburn.ogg", entity); args.ModifiersList.Add(component.Modifiers); if (_random.Prob(component.DisableChance)) - _statusEffects.TryAddStatusEffect(entity, "PsionicsDisabled", TimeSpan.FromSeconds(10), true, "PsionicsDisabled"); + _statusEffects.TryAddStatusEffect(entity, component.DisableStatus, TimeSpan.FromSeconds(component.DisableDuration), true, component.DisableStatus); } if (TryComp(entity, out var swapped)) @@ -76,13 +75,16 @@ private void OnMeleeHit(EntityUid uid, AntiPsionicWeaponComponent component, Mel return; } - if (component.Punish && HasComp(entity) && !HasComp(entity) && _random.Prob(0.5f)) - _electrocutionSystem.TryDoElectrocution(args.User, null, 20, TimeSpan.FromSeconds(5), false); + if (component.Punish && !HasComp(entity) && _random.Prob(component.PunishChances)) + _electrocutionSystem.TryDoElectrocution(args.User, null, component.PunishSelfDamage, TimeSpan.FromSeconds(component.PunishStunDuration), false); } } - private void OnInit(EntityUid uid, PsionicComponent component, ComponentInit args) + private void OnInit(EntityUid uid, PsionicComponent component, ComponentStartup args) { + component.AmplificationSources.Add(BaselineAmplification, _random.NextFloat(component.BaselineAmplification.Item1, component.BaselineAmplification.Item2)); + component.DampeningSources.Add(BaselineDampening, _random.NextFloat(component.BaselineDampening.Item1, component.BaselineDampening.Item2)); + if (!component.Removable || !TryComp(uid, out var factions) || _npcFactonSystem.ContainsFaction(uid, "GlimmerMonster", factions)) @@ -105,40 +107,43 @@ private void OnStamHit(EntityUid uid, AntiPsionicWeaponComponent component, Take args.FlatModifier += component.PsychicStaminaDamage; } - public void RollPsionics(EntityUid uid, PotentialPsionicComponent component, bool applyGlimmer = true, float multiplier = 1f) + public void RollPsionics(EntityUid uid, PsionicComponent component, bool applyGlimmer = true, float rollEventMultiplier = 1f) { - if (HasComp(uid) - || !_cfg.GetCVar(CCVars.PsionicRollsEnabled)) + if (!_cfg.GetCVar(CCVars.PsionicRollsEnabled) + || !component.Removable) return; - var chance = component.Chance; - var warn = true; - if (TryComp(uid, out var bonus)) - { - chance *= bonus.Multiplier; - chance += bonus.FlatBonus; - warn = bonus.Warn; - } + // Calculate the initial odds based on the innate potential + var baselineChance = component.Chance + * component.PowerRollMultiplier + + component.PowerRollFlatBonus; - if (applyGlimmer) - chance += ((float) _glimmerSystem.Glimmer / 1000); + // Increase the initial odds based on Glimmer. + // TODO: Change this equation when I do my Glimmer Refactor + baselineChance += applyGlimmer + ? (float) _glimmerSystem.Glimmer / 1000 //Convert from Glimmer to %chance + : 0; - chance *= multiplier; + // Certain sources of power rolls provide their own multiplier. + baselineChance *= rollEventMultiplier; - chance = Math.Clamp(chance, 0, 1); + // Ask if the Roller has any other effects to contribute, such as Traits. + var ev = new OnRollPsionicsEvent(uid, baselineChance); + RaiseLocalEvent(uid, ref ev); - if (_random.Prob(chance)) - _psionicAbilitiesSystem.AddPsionics(uid, warn); + if (_random.Prob(Math.Clamp(ev.BaselineChance, 0, 1))) + _psionicAbilitiesSystem.AddPsionics(uid); } - public void RerollPsionics(EntityUid uid, PotentialPsionicComponent? psionic = null, float bonusMuliplier = 1f) + public void RerollPsionics(EntityUid uid, PsionicComponent? psionic = null, float bonusMuliplier = 1f) { if (!Resolve(uid, ref psionic, false) - || psionic.Rerolled) + || !psionic.Removable + || psionic.CanReroll) return; - RollPsionics(uid, psionic, multiplier: bonusMuliplier); - psionic.Rerolled = true; + RollPsionics(uid, psionic, true, bonusMuliplier); + psionic.CanReroll = true; } } } diff --git a/Content.Server/RandomMetadata/RandomMetadataSystem.cs b/Content.Server/RandomMetadata/RandomMetadataSystem.cs index 0c254c52ac0..c088d57fd96 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,12 +47,9 @@ public string GetRandomFromSegments(List segments, string? separator) var outputSegments = new List(); foreach (var segment in segments) { - 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); + outputSegments.Add(_prototype.TryIndex(segment, out var proto) + ? Loc.GetString(_random.Pick(proto.Values)) + : Loc.GetString(segment)); } return string.Join(separator, outputSegments); } diff --git a/Content.Server/Research/Oracle/OracleSystem.cs b/Content.Server/Research/Oracle/OracleSystem.cs index 63dcefbadd7..a93627dbd3a 100644 --- a/Content.Server/Research/Oracle/OracleSystem.cs +++ b/Content.Server/Research/Oracle/OracleSystem.cs @@ -5,7 +5,6 @@ using Content.Server.Chat.Systems; using Content.Server.Chemistry.Containers.EntitySystems; using Content.Server.Fluids.EntitySystems; -using Content.Server.Psionics; using Content.Server.Research.Systems; using Content.Shared.Abilities.Psionics; using Content.Shared.Chat; @@ -74,7 +73,7 @@ public override void Initialize() private void OnInteractHand(Entity oracle, ref InteractHandEvent args) { - if (!HasComp(args.User) || HasComp(args.User) + if (!HasComp(args.User) || HasComp(args.User) || !TryComp(args.User, out var actor)) return; diff --git a/Content.Server/Revolutionary/Components/CommandStaffComponent.cs b/Content.Server/Revolutionary/Components/CommandStaffComponent.cs index 8e42f41cb3d..d7395bf16e5 100644 --- a/Content.Server/Revolutionary/Components/CommandStaffComponent.cs +++ b/Content.Server/Revolutionary/Components/CommandStaffComponent.cs @@ -3,10 +3,11 @@ namespace Content.Server.Revolutionary.Components; /// -/// Given to heads at round start for Revs. Used for tracking if heads died or not. +/// Component for tracking if someone is a Head of Staff. /// [RegisterComponent, Access(typeof(RevolutionaryRuleSystem))] public sealed partial class CommandStaffComponent : Component { - + public float PsionicBonusModifier = 1; + public float PsionicBonusOffset = 0.25f; } diff --git a/Content.Server/Revolutionary/Components/CommandStaffSystem.cs b/Content.Server/Revolutionary/Components/CommandStaffSystem.cs new file mode 100644 index 00000000000..d940ec9c267 --- /dev/null +++ b/Content.Server/Revolutionary/Components/CommandStaffSystem.cs @@ -0,0 +1,16 @@ +using Content.Server.Psionics; + +namespace Content.Server.Revolutionary.Components; +public sealed partial class CommandStaffSystem : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnRollPsionics); + } + + private void OnRollPsionics(EntityUid uid, CommandStaffComponent component, ref OnRollPsionicsEvent args) + { + args.BaselineChance = args.BaselineChance * component.PsionicBonusModifier + component.PsionicBonusOffset; + } +} \ No newline at end of file diff --git a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs index 75f86187989..506fd61d559 100644 --- a/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs +++ b/Content.Server/Spawners/EntitySystems/ConditionalSpawnerSystem.cs @@ -1,6 +1,5 @@ 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 61fc1e0a51b..2e6edfab611 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); + EquipStartingGear(entity.Value, startingGear, profile); if (profile != null) EquipIdCard(entity.Value, profile.Name, prototype, station); InternalEncryptionKeySpawner.TryInsertEncryptionKey(entity.Value, startingGear, EntityManager, profile); // Parkstation - IPC diff --git a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs index b9eb3b7b09d..0243a00c9a7 100644 --- a/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/BasicStationEventSchedulerSystem.cs @@ -1,6 +1,5 @@ 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 new file mode 100644 index 00000000000..92911e08584 --- /dev/null +++ b/Content.Server/StationEvents/Components/LoneOpsSpawnRuleComponent.cs @@ -0,0 +1,18 @@ +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/Components/NoosphericZapRuleComponent.cs b/Content.Server/StationEvents/Components/NoosphericZapRuleComponent.cs index bfa82644cd6..56c56005b77 100644 --- a/Content.Server/StationEvents/Components/NoosphericZapRuleComponent.cs +++ b/Content.Server/StationEvents/Components/NoosphericZapRuleComponent.cs @@ -5,4 +5,18 @@ namespace Content.Server.StationEvents.Components; [RegisterComponent, Access(typeof(NoosphericZapRule))] public sealed partial class NoosphericZapRuleComponent : Component { + /// + /// How long (in seconds) should this event stun its victims. + /// + public float StunDuration = 5f; + + /// + /// How long (in seconds) should this event give its victims the Stuttering condition. + /// + public float StutterDuration = 10f; + + /// + /// When paralyzing a Psion with a reroll still available, how much should this event modify the odds of generating a power. + /// + public float PowerRerollMultiplier = 0.25f; } diff --git a/Content.Server/StationEvents/Events/AnomalySpawnRule.cs b/Content.Server/StationEvents/Events/AnomalySpawnRule.cs index 98d5aa76a6a..4cd94d3e719 100644 --- a/Content.Server/StationEvents/Events/AnomalySpawnRule.cs +++ b/Content.Server/StationEvents/Events/AnomalySpawnRule.cs @@ -1,5 +1,4 @@ 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 29c18976576..b25c1d6561c 100644 --- a/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs +++ b/Content.Server/StationEvents/Events/BluespaceArtifactRule.cs @@ -1,5 +1,4 @@ -using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.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 eef9850e739..709b750334e 100644 --- a/Content.Server/StationEvents/Events/BluespaceLockerRule.cs +++ b/Content.Server/StationEvents/Events/BluespaceLockerRule.cs @@ -1,5 +1,4 @@ -using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.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 3b2368556be..e7574f27ad5 100644 --- a/Content.Server/StationEvents/Events/BreakerFlipRule.cs +++ b/Content.Server/StationEvents/Events/BreakerFlipRule.cs @@ -1,5 +1,4 @@ -using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.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 282e28e4991..feb88d9b848 100644 --- a/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs +++ b/Content.Server/StationEvents/Events/BureaucraticErrorRule.cs @@ -1,5 +1,4 @@ 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 62f01f58fe6..80af23c6fa4 100644 --- a/Content.Server/StationEvents/Events/CargoGiftsRule.cs +++ b/Content.Server/StationEvents/Events/CargoGiftsRule.cs @@ -2,7 +2,6 @@ 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 854ee685b33..dd4473952cb 100644 --- a/Content.Server/StationEvents/Events/ClericalErrorRule.cs +++ b/Content.Server/StationEvents/Events/ClericalErrorRule.cs @@ -1,5 +1,4 @@ -using Content.Server.GameTicking.Components; -using Content.Server.GameTicking.Rules.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 2d129b35584..cd434a721b1 100644 --- a/Content.Server/StationEvents/Events/FalseAlarmRule.cs +++ b/Content.Server/StationEvents/Events/FalseAlarmRule.cs @@ -1,5 +1,4 @@ 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 a5dfdd6b6ea..0aa8ecc47cc 100644 --- a/Content.Server/StationEvents/Events/FreeProberRule.cs +++ b/Content.Server/StationEvents/Events/FreeProberRule.cs @@ -1,4 +1,3 @@ -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 1221612171d..68544e416c3 100644 --- a/Content.Server/StationEvents/Events/GasLeakRule.cs +++ b/Content.Server/StationEvents/Events/GasLeakRule.cs @@ -1,5 +1,4 @@ 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 3e0762c8346..a3d36ae7157 100644 --- a/Content.Server/StationEvents/Events/GlimmerEventSystem.cs +++ b/Content.Server/StationEvents/Events/GlimmerEventSystem.cs @@ -1,4 +1,3 @@ -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 578f8bf48bd..152ff77853e 100644 --- a/Content.Server/StationEvents/Events/GlimmerRandomSentienceRule.cs +++ b/Content.Server/StationEvents/Events/GlimmerRandomSentienceRule.cs @@ -1,8 +1,7 @@ using System.Linq; -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Ghost.Roles.Components; -using Content.Server.Psionics; +using Content.Shared.Abilities.Psionics; using Content.Server.Speech.Components; using Content.Server.StationEvents.Components; using Content.Shared.Mobs.Systems; @@ -51,7 +50,7 @@ protected override void Started(EntityUid uid, GlimmerRandomSentienceRuleCompone comp.RoleDescription = Loc.GetString("station-event-random-sentience-role-description", ("name", comp.RoleName)); RemComp(target); RemComp(target); - EnsureComp(target); + EnsureComp(target); EnsureComp(target); } } diff --git a/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs b/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs index 152d6d9fe59..8bab321db75 100644 --- a/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs +++ b/Content.Server/StationEvents/Events/GlimmerRevenantSpawnRule.cs @@ -1,4 +1,3 @@ -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 c2cb4eca6d4..66eea988aeb 100644 --- a/Content.Server/StationEvents/Events/GlimmerWispSpawnRule.cs +++ b/Content.Server/StationEvents/Events/GlimmerWispSpawnRule.cs @@ -1,5 +1,4 @@ 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 781d0368f47..a61c6b69e1a 100644 --- a/Content.Server/StationEvents/Events/ImmovableRodRule.cs +++ b/Content.Server/StationEvents/Events/ImmovableRodRule.cs @@ -1,5 +1,4 @@ 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 8361cc6048a..cd3cd63ae86 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 5b56e03846f..3fa12cd4e9f 100644 --- a/Content.Server/StationEvents/Events/KudzuGrowthRule.cs +++ b/Content.Server/StationEvents/Events/KudzuGrowthRule.cs @@ -1,4 +1,3 @@ -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 new file mode 100644 index 00000000000..4b15e590997 --- /dev/null +++ b/Content.Server/StationEvents/Events/LoneOpsSpawnRule.cs @@ -0,0 +1,47 @@ +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 2239db7f701..4fc158f8646 100644 --- a/Content.Server/StationEvents/Events/MassHallucinationsRule.cs +++ b/Content.Server/StationEvents/Events/MassHallucinationsRule.cs @@ -1,4 +1,3 @@ -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 3b90e6204da..c7e9f1e0298 100644 --- a/Content.Server/StationEvents/Events/MassMindSwapRule.cs +++ b/Content.Server/StationEvents/Events/MassMindSwapRule.cs @@ -1,7 +1,6 @@ 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; @@ -28,7 +27,7 @@ protected override void Started(EntityUid uid, MassMindSwapRuleComponent compone List psionicPool = new(); List psionicActors = new(); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); while (query.MoveNext(out var psion, out _, out _)) { if (_mobStateSystem.IsAlive(psion) && !HasComp(psion)) diff --git a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs index 455011259dc..ad56479b379 100644 --- a/Content.Server/StationEvents/Events/MeteorSwarmRule.cs +++ b/Content.Server/StationEvents/Events/MeteorSwarmRule.cs @@ -1,5 +1,4 @@ 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 d9d68a386cf..8ad5c8602e3 100644 --- a/Content.Server/StationEvents/Events/NinjaSpawnRule.cs +++ b/Content.Server/StationEvents/Events/NinjaSpawnRule.cs @@ -1,4 +1,3 @@ -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 85f98d6f4be..c04543d2195 100644 --- a/Content.Server/StationEvents/Events/NoosphericFryRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericFryRule.cs @@ -3,7 +3,6 @@ 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 19720e68c34..2465cb37fa5 100644 --- a/Content.Server/StationEvents/Events/NoosphericStormRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericStormRule.cs @@ -1,6 +1,5 @@ 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; @@ -24,17 +23,14 @@ protected override void Started(EntityUid uid, NoosphericStormRuleComponent comp List validList = new(); - var query = EntityManager.EntityQueryEnumerator(); - while (query.MoveNext(out var potentialPsionic, out var potentialPsionicComponent)) + var query = EntityManager.EntityQueryEnumerator(); + while (query.MoveNext(out var Psionic, out var PsionicComponent)) { - if (_mobStateSystem.IsDead(potentialPsionic)) + if (_mobStateSystem.IsDead(Psionic) + || HasComp(Psionic)) continue; - // Skip over those who are already psionic or those who are insulated, or zombies. - if (HasComp(potentialPsionic) || HasComp(potentialPsionic) || HasComp(potentialPsionic)) - continue; - - validList.Add(potentialPsionic); + validList.Add(Psionic); } // Give some targets psionic abilities. diff --git a/Content.Server/StationEvents/Events/NoosphericZapRule.cs b/Content.Server/StationEvents/Events/NoosphericZapRule.cs index 2d0f89fd17c..a452f55ed2a 100644 --- a/Content.Server/StationEvents/Events/NoosphericZapRule.cs +++ b/Content.Server/StationEvents/Events/NoosphericZapRule.cs @@ -1,4 +1,3 @@ -using Content.Server.GameTicking.Components; using Content.Server.GameTicking.Rules.Components; using Content.Server.Popups; using Content.Server.Psionics; @@ -26,29 +25,25 @@ protected override void Started(EntityUid uid, NoosphericZapRuleComponent compon { base.Started(uid, component, gameRule, args); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); - while (query.MoveNext(out var psion, out var potentialPsionicComponent, out _)) + while (query.MoveNext(out var psion, out var psionicComponent, out _)) { if (!_mobStateSystem.IsAlive(psion) || HasComp(psion)) continue; - _stunSystem.TryParalyze(psion, TimeSpan.FromSeconds(5), false); - _statusEffectsSystem.TryAddStatusEffect(psion, "Stutter", TimeSpan.FromSeconds(10), false, "StutteringAccent"); + _stunSystem.TryParalyze(psion, TimeSpan.FromSeconds(component.StunDuration), false); + _statusEffectsSystem.TryAddStatusEffect(psion, "Stutter", TimeSpan.FromSeconds(component.StutterDuration), false, "StutteringAccent"); - if (HasComp(psion)) - _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution); + if (psionicComponent.CanReroll) + { + psionicComponent.CanReroll = false; + _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize-potential-regained"), psion, psion, Shared.Popups.PopupType.LargeCaution); + } else { - if (potentialPsionicComponent.Rerolled) - { - potentialPsionicComponent.Rerolled = false; - _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize-potential-regained"), psion, psion, Shared.Popups.PopupType.LargeCaution); - } else - { - _psionicsSystem.RollPsionics(psion, potentialPsionicComponent, multiplier: 0.25f); - _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution); - } + _psionicsSystem.RollPsionics(psion, psionicComponent, true, component.PowerRerollMultiplier); + _popupSystem.PopupEntity(Loc.GetString("noospheric-zap-seize"), psion, psion, Shared.Popups.PopupType.LargeCaution); } } } diff --git a/Content.Server/StationEvents/Events/PowerGridCheckRule.cs b/Content.Server/StationEvents/Events/PowerGridCheckRule.cs index b0a0bbc9fe0..97e89484612 100644 --- a/Content.Server/StationEvents/Events/PowerGridCheckRule.cs +++ b/Content.Server/StationEvents/Events/PowerGridCheckRule.cs @@ -1,5 +1,4 @@ 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 45e8b94fe4b..f5afa1e4587 100644 --- a/Content.Server/StationEvents/Events/PsionicCatGotYourTongueRule.cs +++ b/Content.Server/StationEvents/Events/PsionicCatGotYourTongueRule.cs @@ -1,7 +1,5 @@ -using Content.Server.GameTicking.Components; using Robust.Shared.Random; using Robust.Shared.Player; -using Content.Server.Psionics; using Content.Server.GameTicking.Rules.Components; using Content.Server.StationEvents.Components; using Content.Shared.Mobs.Components; @@ -29,7 +27,7 @@ protected override void Started(EntityUid uid, PsionicCatGotYourTongueRuleCompon List psionicList = new(); - var query = EntityQueryEnumerator(); + var query = EntityQueryEnumerator(); while (query.MoveNext(out var psion, out _, out _)) { if (_mobStateSystem.IsAlive(psion) && !HasComp(psion)) diff --git a/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs b/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs index 87d50fc8b2a..c3cd719cc4c 100644 --- a/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs +++ b/Content.Server/StationEvents/Events/RandomEntityStorageSpawnRule.cs @@ -1,4 +1,3 @@ -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 7b9173241f7..f667ad79750 100644 --- a/Content.Server/StationEvents/Events/RandomSentienceRule.cs +++ b/Content.Server/StationEvents/Events/RandomSentienceRule.cs @@ -1,5 +1,4 @@ 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 77744d44e46..c514acc6236 100644 --- a/Content.Server/StationEvents/Events/RandomSpawnRule.cs +++ b/Content.Server/StationEvents/Events/RandomSpawnRule.cs @@ -1,4 +1,3 @@ -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 0370b4ee61d..a4ec74b43ba 100644 --- a/Content.Server/StationEvents/Events/SolarFlareRule.cs +++ b/Content.Server/StationEvents/Events/SolarFlareRule.cs @@ -1,4 +1,3 @@ -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 257babd0d2c..6de8024bd0a 100644 --- a/Content.Server/StationEvents/Events/StationEventSystem.cs +++ b/Content.Server/StationEvents/Events/StationEventSystem.cs @@ -1,6 +1,5 @@ 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 867f41dcccf..e263a5f4f69 100644 --- a/Content.Server/StationEvents/Events/VentClogRule.cs +++ b/Content.Server/StationEvents/Events/VentClogRule.cs @@ -6,7 +6,6 @@ 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 c2605039bce..cdcf2bf6ff2 100644 --- a/Content.Server/StationEvents/Events/VentCrittersRule.cs +++ b/Content.Server/StationEvents/Events/VentCrittersRule.cs @@ -1,4 +1,3 @@ -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 a6c38ef765f..aa0c9b214b4 100644 --- a/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs +++ b/Content.Server/StationEvents/RampingStationEventSchedulerSystem.cs @@ -1,5 +1,4 @@ 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 473441ccec2..ab4bee2f267 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, ViewVariables(VVAccess.ReadWrite)] + [DataField("giveUplink"), ViewVariables(VVAccess.ReadWrite)] public bool GiveUplink = true; /// /// Whether to give the traitor objectives or not. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField("giveObjectives"), ViewVariables(VVAccess.ReadWrite)] public bool GiveObjectives = true; } diff --git a/Content.Server/Traitor/Systems/AutoTraitorSystem.cs b/Content.Server/Traitor/Systems/AutoTraitorSystem.cs index e9307effbc6..15deae25529 100644 --- a/Content.Server/Traitor/Systems/AutoTraitorSystem.cs +++ b/Content.Server/Traitor/Systems/AutoTraitorSystem.cs @@ -1,7 +1,6 @@ -using Content.Server.Antag; +using Content.Server.GameTicking.Rules; using Content.Server.Traitor.Components; using Content.Shared.Mind.Components; -using Robust.Shared.Prototypes; namespace Content.Server.Traitor.Systems; @@ -10,10 +9,7 @@ namespace Content.Server.Traitor.Systems; /// public sealed class AutoTraitorSystem : EntitySystem { - [Dependency] private readonly AntagSelectionSystem _antag = default!; - - [ValidatePrototypeId] - private const string DefaultTraitorRule = "Traitor"; + [Dependency] private readonly TraitorRuleSystem _traitorRule = default!; public override void Initialize() { @@ -24,6 +20,44 @@ public override void Initialize() private void OnMindAdded(EntityUid uid, AutoTraitorComponent comp, MindAddedMessage args) { - _antag.ForceMakeAntag(args.Mind.Comp.Session, DefaultTraitorRule); + 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; } } diff --git a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs index 79192f6b496..cdaed3f928e 100644 --- a/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs +++ b/Content.Server/Traitor/Uplink/Commands/AddUplinkCommand.cs @@ -83,9 +83,12 @@ 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, 20, uplinkEntity: uplinkEntity)) + if (!uplinkSys.AddUplink(user, FixedPoint2.New(tcCount), uplinkEntity: uplinkEntity)) { shell.WriteLine(Loc.GetString("add-uplink-command-error-2")); } diff --git a/Content.Server/Traits/Assorted/ModifyMoodTraitComponent.cs b/Content.Server/Traits/Assorted/ModifyMoodTraitComponent.cs new file mode 100644 index 00000000000..f9ae3b36f3a --- /dev/null +++ b/Content.Server/Traits/Assorted/ModifyMoodTraitComponent.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Traits.Assorted.Components; + +/// +/// Used for traits that add a starting moodlet. +/// +[RegisterComponent] +public sealed partial class MoodModifyTraitComponent : Component +{ + [DataField] + public string? MoodId = null; +} diff --git a/Content.Server/Zombies/PendingZombieComponent.cs b/Content.Server/Zombies/PendingZombieComponent.cs index 1bb0ef28720..a49b424c53f 100644 --- a/Content.Server/Zombies/PendingZombieComponent.cs +++ b/Content.Server/Zombies/PendingZombieComponent.cs @@ -1,5 +1,4 @@ using Content.Shared.Damage; -using Robust.Shared.Prototypes; using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom; namespace Content.Server.Zombies; @@ -36,21 +35,6 @@ 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 2b39404ab50..5b38e6f766f 100644 --- a/Content.Server/Zombies/ZombieSystem.Transform.cs +++ b/Content.Server/Zombies/ZombieSystem.Transform.cs @@ -1,4 +1,3 @@ -using Content.Server.Actions; using Content.Server.Atmos.Components; using Content.Server.Body.Components; using Content.Server.Chat; @@ -37,6 +36,7 @@ using Content.Shared.Prying.Components; using Robust.Shared.Audio.Systems; using Content.Shared.Traits.Assorted.Components; +using Content.Server.Abilities.Psionics; namespace Content.Server.Zombies { @@ -60,6 +60,7 @@ 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 PsionicAbilitiesSystem _psionic = default!; /// /// Handles an entity turning into a zombie when they die or go into crit @@ -107,17 +108,9 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null) RemComp(target); RemComp(target); - if (TryComp(target, out var psionic)) // DeltaV - Prevent psionic zombies + if (HasComp(target)) // Prevent psionic zombies { - if (psionic.ActivePowers.Count > 0) - { - foreach (var power in psionic.ActivePowers) - { - RemComp(target, power); - } - psionic.ActivePowers.Clear(); - } - RemComp(target); + _psionic.RemoveAllPsionicPowers(target, true); } //funny voice diff --git a/Content.Server/Zombies/ZombieSystem.cs b/Content.Server/Zombies/ZombieSystem.cs index 09c8fa26db6..080bef44e7a 100644 --- a/Content.Server/Zombies/ZombieSystem.cs +++ b/Content.Server/Zombies/ZombieSystem.cs @@ -1,5 +1,4 @@ using System.Linq; -using Content.Server.Actions; using Content.Server.Body.Systems; using Content.Server.Chat; using Content.Server.Chat.Systems; @@ -31,7 +30,6 @@ 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!; @@ -76,8 +74,6 @@ 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/Alert/AlertCategory.cs b/Content.Shared/Alert/AlertCategory.cs index 7450f585a4e..57a3e40f70e 100644 --- a/Content.Shared/Alert/AlertCategory.cs +++ b/Content.Shared/Alert/AlertCategory.cs @@ -10,6 +10,7 @@ public enum AlertCategory Breathing, Buckled, Health, + Mood, Internals, Stamina, Piloting, diff --git a/Content.Shared/Alert/AlertType.cs b/Content.Shared/Alert/AlertType.cs index dc323dc64a8..1130e25b66d 100644 --- a/Content.Shared/Alert/AlertType.cs +++ b/Content.Shared/Alert/AlertType.cs @@ -25,6 +25,22 @@ public enum AlertType : byte HumanHealth, BorgBattery, BorgBatteryNone, + + // Mood + Bleeding, + Insane, + Horrible, + Terrible, + Bad, + Meh, + Neutral, + Good, + Great, + Exceptional, + Perfect, + MoodDead, + CultBuffed, + PilotingShuttle, Peckish, Starving, diff --git a/Content.Shared/Antag/AntagAcceptability.cs b/Content.Shared/Antag/AntagAcceptability.cs index 02d0b5f58fe..98abe713ebe 100644 --- a/Content.Shared/Antag/AntagAcceptability.cs +++ b/Content.Shared/Antag/AntagAcceptability.cs @@ -20,8 +20,3 @@ 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 174c7a13cb1..63b9c3ba099 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -537,6 +537,91 @@ 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 */ @@ -2214,7 +2299,7 @@ public static readonly CVarDef /// public static readonly CVarDef StationGoalsChance = CVarDef.Create("game.station_goals_chance", 0.1f, CVar.SERVERONLY); - + #region CPR System /// @@ -2261,7 +2346,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 @@ -2304,6 +2389,12 @@ public static readonly CVarDef public static readonly CVarDef DoMindContests = CVarDef.Create("contests.do_mind_contests", true, CVar.REPLICATED | CVar.SERVER); + /// + /// Toggles all MoodContest functions. All mood contests output 1f when false. + /// + public static readonly CVarDef DoMoodContests = + CVarDef.Create("contests.do_mood_contests", true, CVar.REPLICATED | CVar.SERVER); + /// /// The maximum amount that Mass Contests can modify a physics multiplier, given as a +/- percentage /// Default of 0.25f outputs between * 0.75f and 1.25f @@ -2361,5 +2452,18 @@ public static readonly CVarDef CVarDef.Create("supermatter.rads_modifier", 1f, CVar.SERVER); #endregion + + #region Mood System + + public static readonly CVarDef MoodEnabled = + CVarDef.Create("mood.enabled", true, CVar.SERVER); + + public static readonly CVarDef MoodIncreasesSpeed = + CVarDef.Create("mood.increases_speed", true, CVar.SERVER); + + public static readonly CVarDef MoodDecreasesSpeed = + CVarDef.Create("mood.decreases_speed", true, CVar.SERVER); + + #endregion } } diff --git a/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs b/Content.Shared/Clothing/Loadouts/Systems/LoadoutSystem.cs index 08ca204372f..e7a0eef80ee 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); + _station.EquipStartingGear(uid, proto, null); } diff --git a/Content.Shared/Contests/ContestsSystem.cs b/Content.Shared/Contests/ContestsSystem.cs index 47924f4d795..008e5e9ca97 100644 --- a/Content.Shared/Contests/ContestsSystem.cs +++ b/Content.Shared/Contests/ContestsSystem.cs @@ -2,6 +2,7 @@ using Content.Shared.Damage; using Content.Shared.Damage.Components; using Content.Shared.Mobs.Systems; +using Content.Shared.Mood; using Robust.Shared.Configuration; using Robust.Shared.Physics.Components; @@ -251,6 +252,52 @@ public float MindContest(EntityUid performer, EntityUid target, bool bypassClamp #endregion + #region Mood Contests + + /// + /// Outputs the ratio of an Entity's mood level and its Neutral Mood threshold. + /// + /// + /// + /// + public float MoodContest(EntityUid performer, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMoodContests) + || !TryComp(performer, out var mood)) + return 1f; + + return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp + ? mood.CurrentMoodLevel / mood.NeutralMoodThreshold + : Math.Clamp(mood.CurrentMoodLevel / mood.NeutralMoodThreshold, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor); + } + + /// + /// Outputs the ratio of mood level between two Entities. + /// + /// + /// + /// + /// + public float MoodContest(EntityUid performer, EntityUid target, bool bypassClamp = false, float rangeFactor = 1f) + { + if (!_cfg.GetCVar(CCVars.DoContestsSystem) + || !_cfg.GetCVar(CCVars.DoMoodContests) + || !TryComp(performer, out var performerMood) + || !TryComp(target, out var targetMood)) + return 1f; + + return _cfg.GetCVar(CCVars.AllowClampOverride) && bypassClamp + ? performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel + : Math.Clamp(performerMood.CurrentMoodLevel / targetMood.CurrentMoodLevel, + 1 - _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor, + 1 + _cfg.GetCVar(CCVars.MassContestsMaxPercentage) * rangeFactor); + } + + #endregion + #region EVERY CONTESTS public float EveryContest( @@ -259,34 +306,40 @@ public float EveryContest( bool bypassClampStamina = false, bool bypassClampHealth = false, bool bypassClampMind = false, + bool bypassClampMood = false, float rangeFactorMass = 1f, float rangeFactorStamina = 1f, float rangeFactorHealth = 1f, float rangeFactorMind = 1f, + float rangeFactorMood = 1f, float weightMass = 1f, float weightStamina = 1f, float weightHealth = 1f, float weightMind = 1f, + float weightMood = 1f, bool sumOrMultiply = false) { if (!_cfg.GetCVar(CCVars.DoContestsSystem)) return 1f; - var weightTotal = weightMass + weightStamina + weightHealth + weightMind; + var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; var massMultiplier = weightMass / weightTotal; var staminaMultiplier = weightStamina / weightTotal; var healthMultiplier = weightHealth / weightTotal; var mindMultiplier = weightMind / weightTotal; + var moodMultiplier = weightMood / weightTotal; return sumOrMultiply ? MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier + StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier + MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier + + MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier : MassContest(performer, bypassClampMass, rangeFactorMass) * massMultiplier * StaminaContest(performer, bypassClampStamina, rangeFactorStamina) * staminaMultiplier * HealthContest(performer, bypassClampHealth, rangeFactorHealth) * healthMultiplier - * MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier; + * MindContest(performer, bypassClampMind, rangeFactorMind) * mindMultiplier + * MoodContest(performer, bypassClampMood, rangeFactorMood) * moodMultiplier; } public float EveryContest( @@ -296,34 +349,40 @@ public float EveryContest( bool bypassClampStamina = false, bool bypassClampHealth = false, bool bypassClampMind = false, + bool bypassClampMood = false, float rangeFactorMass = 1f, float rangeFactorStamina = 1f, float rangeFactorHealth = 1f, float rangeFactorMind = 1f, + float rangeFactorMood = 1f, float weightMass = 1f, float weightStamina = 1f, float weightHealth = 1f, float weightMind = 1f, + float weightMood = 1f, bool sumOrMultiply = false) { if (!_cfg.GetCVar(CCVars.DoContestsSystem)) return 1f; - var weightTotal = weightMass + weightStamina + weightHealth + weightMind; + var weightTotal = weightMass + weightStamina + weightHealth + weightMind + weightMood; var massMultiplier = weightMass / weightTotal; var staminaMultiplier = weightStamina / weightTotal; var healthMultiplier = weightHealth / weightTotal; var mindMultiplier = weightMind / weightTotal; + var moodMultiplier = weightMood / weightTotal; return sumOrMultiply ? MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier + StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier + HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier + MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier + + MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier : MassContest(performer, target, bypassClampMass, rangeFactorMass) * massMultiplier * StaminaContest(performer, target, bypassClampStamina, rangeFactorStamina) * staminaMultiplier * HealthContest(performer, target, bypassClampHealth, rangeFactorHealth) * healthMultiplier - * MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier; + * MindContest(performer, target, bypassClampMind, rangeFactorMind) * mindMultiplier + * MoodContest(performer, target, bypassClampMood, rangeFactorMood) * moodMultiplier; } #endregion } diff --git a/Content.Shared/Cuffs/SharedCuffableSystem.cs b/Content.Shared/Cuffs/SharedCuffableSystem.cs index b47fa08bc78..ebbafef7f0e 100644 --- a/Content.Shared/Cuffs/SharedCuffableSystem.cs +++ b/Content.Shared/Cuffs/SharedCuffableSystem.cs @@ -28,6 +28,7 @@ using Content.Shared.Verbs; using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Audio.Systems; +using Content.Shared.Mood; using Robust.Shared.Containers; using Robust.Shared.Network; using Robust.Shared.Player; @@ -174,9 +175,15 @@ public void UpdateCuffState(EntityUid uid, CuffableComponent component) _actionBlocker.UpdateCanMove(uid); if (component.CanStillInteract) + { _alerts.ClearAlert(uid, AlertType.Handcuffed); + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("Handcuffed")); + } else + { _alerts.ShowAlert(uid, AlertType.Handcuffed); + RaiseLocalEvent(uid, new MoodEffectEvent("Handcuffed")); + } var ev = new CuffedStateChangeEvent(); RaiseLocalEvent(uid, ref ev); diff --git a/Content.Shared/DeltaV/Abilities/UltraVisionComponent.cs b/Content.Shared/DeltaV/Abilities/UltraVisionComponent.cs deleted file mode 100644 index 5f631c54f25..00000000000 --- a/Content.Shared/DeltaV/Abilities/UltraVisionComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.GameStates; -namespace Content.Shared.Abilities; - -[RegisterComponent] -[NetworkedComponent] - -public sealed partial class UltraVisionComponent : Component -{} diff --git a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs index ce49f80af3b..ece4b59e91a 100644 --- a/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs +++ b/Content.Shared/Humanoid/SharedHumanoidAppearanceSystem.cs @@ -328,11 +328,8 @@ 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 7e325abe216..811387d3750 100644 --- a/Content.Shared/Inventory/InventorySystem.Helpers.cs +++ b/Content.Shared/Inventory/InventorySystem.Helpers.cs @@ -1,6 +1,8 @@ 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; @@ -94,7 +96,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/Mood/MoodCategoryPrototype.cs b/Content.Shared/Mood/MoodCategoryPrototype.cs new file mode 100644 index 00000000000..13d5f8b7ea6 --- /dev/null +++ b/Content.Shared/Mood/MoodCategoryPrototype.cs @@ -0,0 +1,13 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Mood; + +/// +/// A prototype defining a category for moodlets, where only a single moodlet of a given category is permitted. +/// +[Prototype] +public sealed class MoodCategoryPrototype : IPrototype +{ + [IdDataField] + public string ID { get; } = default!; +} diff --git a/Content.Shared/Mood/MoodEffectPrototype.cs b/Content.Shared/Mood/MoodEffectPrototype.cs new file mode 100644 index 00000000000..ad21faec807 --- /dev/null +++ b/Content.Shared/Mood/MoodEffectPrototype.cs @@ -0,0 +1,35 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Mood; + +[Prototype] +public sealed class MoodEffectPrototype : IPrototype +{ + /// + /// The ID of the moodlet to use. + /// + [IdDataField] + public string ID { get; } = default!; + + public string Description => Loc.GetString($"mood-effect-{ID}"); + /// + /// If they already have an effect with the same category, the new one will replace the old one. + /// + [DataField, ValidatePrototypeId] + public string? Category; + /// + /// How much should this moodlet modify an entity's Mood. + /// + [DataField(required: true)] + public float MoodChange; + /// + /// How long, in Seconds, does this moodlet last? If omitted, the moodlet will last until canceled by any system. + /// + [DataField] + public int Timeout; + /// + /// Should this moodlet be hidden from the player? EG: No popups or chat messages. + /// + [DataField] + public bool Hidden; +} diff --git a/Content.Shared/Mood/MoodEvents.cs b/Content.Shared/Mood/MoodEvents.cs new file mode 100644 index 00000000000..58a993d2b7f --- /dev/null +++ b/Content.Shared/Mood/MoodEvents.cs @@ -0,0 +1,59 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared.Mood; + +[Serializable, NetSerializable] +public sealed class MoodEffectEvent : EntityEventArgs +{ + /// + /// ID of the moodlet prototype to use + /// + public string EffectId; + + /// + /// How much should the mood change be multiplied by + ///
+ /// This does nothing if the moodlet ID matches one with the same Category + ///
+ public float EffectModifier = 1f; + + /// + /// How much should the mood change be offset by, after multiplication + ///
+ /// This does nothing if the moodlet ID matches one with the same Category + ///
+ public float EffectOffset = 0f; + + public MoodEffectEvent(string effectId, float effectModifier = 1f, float effectOffset = 0f) + { + EffectId = effectId; + EffectModifier = effectModifier; + EffectOffset = effectOffset; + } +} + +[Serializable, NetSerializable] +public sealed class MoodRemoveEffectEvent : EntityEventArgs +{ + public string EffectId; + + public MoodRemoveEffectEvent(string effectId) + { + EffectId = effectId; + } +} + +/// +/// This event is raised whenever an entity sets their mood, allowing other systems to modify the end result of mood math. +/// EG: The end result after tallying up all Moodlets comes out to 70, but a trait multiplies it by 0.8 to make it 56. +/// +[ByRefEvent] +public record struct OnSetMoodEvent(EntityUid Receiver, float MoodChangedAmount, bool Cancelled); + +/// +/// This event is raised on an entity when it receives a mood effect, but before the effects are calculated. +/// Allows for other systems to pick and choose specific events to modify. +/// +[ByRefEvent] +public record struct OnMoodEffect(EntityUid Receiver, string EffectId, float EffectModifier = 1, float EffectOffset = 0); + diff --git a/Content.Shared/Mood/SharedMoodComponent.cs b/Content.Shared/Mood/SharedMoodComponent.cs new file mode 100644 index 00000000000..566f5c7b668 --- /dev/null +++ b/Content.Shared/Mood/SharedMoodComponent.cs @@ -0,0 +1,15 @@ +namespace Content.Shared.Mood; + +/// +/// This component exists solely to network CurrentMoodLevel, so that clients can make use of its value for math Prediction. +/// All mood logic is otherwise handled by the Server, and the client is not allowed to know the identity of its mood events. +/// +[RegisterComponent, AutoGenerateComponentState] +public sealed partial class NetMoodComponent : Component +{ + [DataField, AutoNetworkedField] + public float CurrentMoodLevel; + + [DataField, AutoNetworkedField] + public float NeutralMoodThreshold; +} \ No newline at end of file diff --git a/Content.Shared/NukeOps/NukeOperativeComponent.cs b/Content.Shared/NukeOps/NukeOperativeComponent.cs index d19f0ae3e9d..cdbefece9d6 100644 --- a/Content.Shared/NukeOps/NukeOperativeComponent.cs +++ b/Content.Shared/NukeOps/NukeOperativeComponent.cs @@ -13,9 +13,14 @@ 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/Nutrition/EntitySystems/HungerSystem.cs b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs index d8808b6e4ab..1bc2a945f31 100644 --- a/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/HungerSystem.cs @@ -4,8 +4,12 @@ using Content.Shared.Movement.Systems; using Content.Shared.Nutrition.Components; using Content.Shared.Rejuvenate; +using Content.Shared.Mood; +using Robust.Shared.Network; using Robust.Shared.Random; using Robust.Shared.Timing; +using Robust.Shared.Configuration; +using Content.Shared.CCVar; namespace Content.Shared.Nutrition.EntitySystems; @@ -18,6 +22,8 @@ public sealed class HungerSystem : EntitySystem [Dependency] private readonly MobStateSystem _mobState = default!; [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!; [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly INetManager _net = default!; + [Dependency] private readonly IConfigurationManager _config = default!; public override void Initialize() { @@ -44,10 +50,9 @@ private void OnShutdown(EntityUid uid, HungerComponent component, ComponentShutd private void OnRefreshMovespeed(EntityUid uid, HungerComponent component, RefreshMovementSpeedModifiersEvent args) { - if (component.CurrentThreshold > HungerThreshold.Starving) - return; - - if (_jetpack.IsUserFlying(uid)) + if (_config.GetCVar(CCVars.MoodEnabled) + || component.CurrentThreshold > HungerThreshold.Starving + || _jetpack.IsUserFlying(uid)) return; args.ModifySpeed(component.StarvingSlowdownModifier, component.StarvingSlowdownModifier); @@ -111,7 +116,13 @@ private void DoHungerThresholdEffects(EntityUid uid, HungerComponent? component if (GetMovementThreshold(component.CurrentThreshold) != GetMovementThreshold(component.LastThreshold)) { - _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + if (!_config.GetCVar(CCVars.MoodEnabled)) + _movementSpeedModifier.RefreshMovementSpeedModifiers(uid); + else if (_net.IsServer) + { + var ev = new MoodEffectEvent("Hunger" + component.CurrentThreshold); + RaiseLocalEvent(uid, ev); + } } if (component.HungerThresholdAlerts.TryGetValue(component.CurrentThreshold, out var alertId)) diff --git a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs index bd7251b9438..c61e8e5ba42 100644 --- a/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/SharedCreamPieSystem.cs @@ -1,6 +1,7 @@ using Content.Shared.Nutrition.Components; using Content.Shared.Stunnable; using Content.Shared.Throwing; +using Content.Shared.Mood; using JetBrains.Annotations; namespace Content.Shared.Nutrition.EntitySystems @@ -44,6 +45,11 @@ public void SetCreamPied(EntityUid uid, CreamPiedComponent creamPied, bool value { _appearance.SetData(uid, CreamPiedVisuals.Creamed, value, appearance); } + + if (value) + RaiseLocalEvent(uid, new MoodEffectEvent("Creampied")); + else + RaiseLocalEvent(uid, new MoodRemoveEffectEvent("Creampied")); } private void OnCreamPieLand(EntityUid uid, CreamPieComponent component, ref LandEvent args) diff --git a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs index 29218f57198..f1ddc9b2b5e 100644 --- a/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs +++ b/Content.Shared/Nutrition/EntitySystems/ThirstSystem.cs @@ -6,6 +6,9 @@ using JetBrains.Annotations; using Robust.Shared.Random; using Robust.Shared.Timing; +using Content.Shared.Mood; +using Robust.Shared.Configuration; +using Content.Shared.CCVar; namespace Content.Shared.Nutrition.EntitySystems; @@ -17,6 +20,7 @@ public sealed class ThirstSystem : EntitySystem [Dependency] private readonly AlertsSystem _alerts = default!; [Dependency] private readonly MovementSpeedModifierSystem _movement = default!; [Dependency] private readonly SharedJetpackSystem _jetpack = default!; + [Dependency] private readonly IConfigurationManager _config = default!; public override void Initialize() { @@ -49,7 +53,8 @@ private void OnMapInit(EntityUid uid, ThirstComponent component, MapInitEvent ar private void OnRefreshMovespeed(EntityUid uid, ThirstComponent component, RefreshMovementSpeedModifiersEvent args) { // TODO: This should really be taken care of somewhere else - if (_jetpack.IsUserFlying(uid)) + if (_config.GetCVar(CCVars.MoodEnabled) + || _jetpack.IsUserFlying(uid)) return; var mod = component.CurrentThirstThreshold <= ThirstThreshold.Parched ? 0.75f : 1.0f; @@ -109,8 +114,9 @@ private bool IsMovementThreshold(ThirstThreshold threshold) private void UpdateEffects(EntityUid uid, ThirstComponent component) { - if (IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) && - TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent)) + if (!_config.GetCVar(CCVars.MoodEnabled) + && IsMovementThreshold(component.LastThirstThreshold) != IsMovementThreshold(component.CurrentThirstThreshold) + && TryComp(uid, out MovementSpeedModifierComponent? movementSlowdownComponent)) { _movement.RefreshMovementSpeedModifiers(uid, movementSlowdownComponent); } @@ -125,6 +131,9 @@ private void UpdateEffects(EntityUid uid, ThirstComponent component) _alerts.ClearAlertCategory(uid, AlertCategory.Thirst); } + var ev = new MoodEffectEvent("Thirst" + component.CurrentThirstThreshold); + RaiseLocalEvent(uid, ev); + switch (component.CurrentThirstThreshold) { case ThirstThreshold.OverHydrated: diff --git a/Content.Shared/Nyanotrasen/Abilities/DogVisionComponent.cs b/Content.Shared/Nyanotrasen/Abilities/DogVisionComponent.cs deleted file mode 100644 index b0cf6cf0d1e..00000000000 --- a/Content.Shared/Nyanotrasen/Abilities/DogVisionComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using Robust.Shared.GameStates; -namespace Content.Shared.Abilities; - -[RegisterComponent] -[NetworkedComponent] - -public sealed partial class DogVisionComponent : Component -{} diff --git a/Content.Shared/Overlays/SaturationScaleComponent.cs b/Content.Shared/Overlays/SaturationScaleComponent.cs new file mode 100644 index 00000000000..3318ddff6d4 --- /dev/null +++ b/Content.Shared/Overlays/SaturationScaleComponent.cs @@ -0,0 +1,6 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Overlays; + +[RegisterComponent, NetworkedComponent] +public sealed partial class SaturationScaleOverlayComponent : Component { } diff --git a/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs b/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs index ce86111fc4b..fd3c947136a 100644 --- a/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs +++ b/Content.Shared/Psionics/Abilities/Dispel/DamageOnDispelComponent.cs @@ -8,10 +8,10 @@ namespace Content.Shared.Abilities.Psionics [RegisterComponent] public sealed partial class DamageOnDispelComponent : Component { - [DataField("damage", required: true)] - public DamageSpecifier Damage = default!; + [DataField(required: true)] + public DamageSpecifier Damage = default!; - [DataField("variance")] + [DataField] public float Variance = 0.5f; } } diff --git a/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs b/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs index cd887866364..6c37ff0b6c3 100644 --- a/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Dispel/DispelPowerComponent.cs @@ -1,20 +1,5 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class DispelPowerComponent : Component - { - [DataField("range")] - public float Range = 10f; - - [DataField("dispelActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? DispelActionId = "ActionDispel"; - - [DataField("dispelActionEntity")] - public EntityUid? DispelActionEntity; - } + public sealed partial class DispelPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs b/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs index 7d611c63dac..97ed24cdde0 100644 --- a/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerComponent.cs @@ -1,18 +1,8 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class MassSleepPowerComponent : Component { - public float Radius = 1.25f; - [DataField("massSleepActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MassSleepActionId = "ActionMassSleep"; - - [DataField("massSleepActionEntity")] - public EntityUid? MassSleepActionEntity; + public readonly float Radius = 1.25f; } } diff --git a/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs b/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs index e36a3c70e8a..484aa6da14f 100644 --- a/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs +++ b/Content.Shared/Psionics/Abilities/MassSleep/MassSleepPowerSystem.cs @@ -1,47 +1,21 @@ -using Content.Shared.Actions; using Content.Shared.Bed.Sleep; -using Content.Shared.Magic.Events; using Content.Shared.Damage; using Content.Shared.Mobs.Components; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Content.Shared.Mind; using Content.Shared.Actions.Events; namespace Content.Shared.Abilities.Psionics { public sealed class MassSleepPowerSystem : EntitySystem { - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPsionicAbilitiesSystem _psionics = default!; - [Dependency] private readonly IGameTiming _gameTiming = default!; - [Dependency] private readonly SharedMindSystem _mindSystem = default!; public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); } - private void OnInit(EntityUid uid, MassSleepPowerComponent component, ComponentInit args) - { - _actions.AddAction(uid, ref component.MassSleepActionEntity, component.MassSleepActionId ); - _actions.TryGetActionData( component.MassSleepActionEntity, out var actionData ); - if (actionData is { UseDelay: not null }) - _actions.StartUseDelay(component.MassSleepActionEntity); - if (TryComp(uid, out var psionic) && psionic.PsionicAbility == null) - psionic.PsionicAbility = component.MassSleepActionEntity; - } - - private void OnShutdown(EntityUid uid, MassSleepPowerComponent component, ComponentShutdown args) - { - _actions.RemoveAction(uid, component.MassSleepActionEntity); - } - private void OnPowerUsed(EntityUid uid, MassSleepPowerComponent component, MassSleepPowerActionEvent args) { foreach (var entity in _lookup.GetEntitiesInRange(args.Target, component.Radius)) diff --git a/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs b/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs index c9d0130221a..941a2776a49 100644 --- a/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Metapsionics/MetapsionicPowerComponent.cs @@ -1,21 +1,8 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class MetapsionicPowerComponent : Component { - [DataField("range")] - public float Range = 5f; - - public InstantActionComponent? MetapsionicPowerAction = null; - [DataField("metapsionicActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MetapsionicActionId = "ActionMetapsionic"; - - [DataField("metapsionicActionEntity")] - public EntityUid? MetapsionicActionEntity; + public readonly float Range = 5f; } } diff --git a/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs b/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs index 6a3fc811c89..216972df1e0 100644 --- a/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/MindSwap/MindSwapPowerComponent.cs @@ -1,16 +1,5 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class MindSwapPowerComponent : Component - { - [DataField("mindSwapActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? MindSwapActionId = "ActionMindSwap"; - - [DataField("mindSwapActionEntity")] - public EntityUid? MindSwapActionEntity; - } + public sealed partial class MindSwapPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs b/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs index 0e91894b1dc..abbab16db76 100644 --- a/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/NoosphericZap/NoosphericZapPowerComponent.cs @@ -1,17 +1,5 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class NoosphericZapPowerComponent : Component - { - [DataField("noosphericZapActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? NoosphericZapActionId = "ActionNoosphericZap"; - - [DataField("noosphericZapActionEntity")] - public EntityUid? NoosphericZapActionEntity; - } + public sealed partial class NoosphericZapPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs index 3e198aa9303..ff983bf9efa 100644 --- a/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicInvisibility/PsionicInvisibilityPowerComponent.cs @@ -1,16 +1,5 @@ -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class PsionicInvisibilityPowerComponent : Component - { - [DataField("psionicInvisibilityActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PsionicInvisibilityActionId = "ActionPsionicInvisibility"; - - [DataField("psionicInvisibilityActionEntity")] - public EntityUid? PsionicInvisibilityActionEntity; - } + public sealed partial class PsionicInvisibilityPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs b/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs index 895c5201c3a..93a5076506f 100644 --- a/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/PsionicRegeneration/PsionicRegenerationPowerComponent.cs @@ -1,31 +1,22 @@ using Robust.Shared.Audio; using Content.Shared.DoAfter; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; namespace Content.Shared.Abilities.Psionics { [RegisterComponent] public sealed partial class PsionicRegenerationPowerComponent : Component { - [DataField("doAfter")] + [DataField] public DoAfterId? DoAfter; - [DataField("essence")] + [DataField] public float EssenceAmount = 20; - [DataField("useDelay")] + [DataField] public float UseDelay = 8f; - [DataField("soundUse")] + [DataField] public SoundSpecifier SoundUse = new SoundPathSpecifier("/Audio/Psionics/heartbeat_fast.ogg"); - - [DataField("psionicRegenerationActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PsionicRegenerationActionId = "ActionPsionicRegeneration"; - - [DataField("psionicRegenerationActionEntity")] - public EntityUid? PsionicRegenerationActionEntity; } } diff --git a/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs b/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs index 28425afdb4c..a8867c080cb 100644 --- a/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs +++ b/Content.Shared/Psionics/Abilities/Pyrokinesis/PyrokinesisPowerComponent.cs @@ -1,18 +1,5 @@ -using Content.Shared.Actions; -using Robust.Shared.Prototypes; -using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype; - namespace Content.Shared.Abilities.Psionics { [RegisterComponent] - public sealed partial class PyrokinesisPowerComponent : Component - { - public EntityTargetActionComponent? PyrokinesisPowerAction = null; - [DataField("pyrokinesisActionId", - customTypeSerializer: typeof(PrototypeIdSerializer))] - public string? PyrokinesisActionId = "ActionPyrokinesis"; - - [DataField("pyrokinesisActionEntity")] - public EntityUid? PyrokinesisActionEntity; - } + public sealed partial class PyrokinesisPowerComponent : Component { } } diff --git a/Content.Shared/Psionics/Items/PsionicItemsSystem.cs b/Content.Shared/Psionics/Items/PsionicItemsSystem.cs index f88acf61f3c..17ee9b25ef0 100644 --- a/Content.Shared/Psionics/Items/PsionicItemsSystem.cs +++ b/Content.Shared/Psionics/Items/PsionicItemsSystem.cs @@ -8,7 +8,6 @@ public sealed class PsionicItemsSystem : EntitySystem { [Dependency] private readonly StatusEffectsSystem _statusEffects = default!; [Dependency] private readonly IComponentFactory _componentFactory = default!; - [Dependency] private readonly SharedPsionicAbilitiesSystem _psiAbilities = default!; public override void Initialize() { base.Initialize(); @@ -29,7 +28,6 @@ private void OnTinfoilEquipped(EntityUid uid, TinfoilHatComponent component, Got var insul = EnsureComp(args.Equipee); insul.Passthrough = component.Passthrough; component.IsActive = true; - _psiAbilities.SetPsionicsThroughEligibility(args.Equipee); } private void OnTinfoilUnequipped(EntityUid uid, TinfoilHatComponent component, GotUnequippedEvent args) @@ -41,7 +39,6 @@ private void OnTinfoilUnequipped(EntityUid uid, TinfoilHatComponent component, G RemComp(args.Equipee); component.IsActive = false; - _psiAbilities.SetPsionicsThroughEligibility(args.Equipee); } private void OnGranterEquipped(EntityUid uid, ClothingGrantPsionicPowerComponent component, GotEquippedEvent args) diff --git a/Content.Shared/Psionics/MindbrokenComponent.cs b/Content.Shared/Psionics/MindbrokenComponent.cs new file mode 100644 index 00000000000..9c0e6152e53 --- /dev/null +++ b/Content.Shared/Psionics/MindbrokenComponent.cs @@ -0,0 +1,5 @@ +namespace Content.Shared.Abilities.Psionics +{ + [RegisterComponent] + public sealed partial class MindbrokenComponent : Component { } +} diff --git a/Content.Shared/Psionics/PsionicComponent.cs b/Content.Shared/Psionics/PsionicComponent.cs index 9091e03cfc3..9ff332327a4 100644 --- a/Content.Shared/Psionics/PsionicComponent.cs +++ b/Content.Shared/Psionics/PsionicComponent.cs @@ -1,20 +1,144 @@ -using Content.Shared.Actions; +using Content.Shared.Psionics; using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; namespace Content.Shared.Abilities.Psionics { [RegisterComponent, NetworkedComponent] public sealed partial class PsionicComponent : Component { - public EntityUid? PsionicAbility = null; + /// + /// How close a Psion is to awakening a new power. + /// TODO: Implement this in a separate PR. + /// + [DataField] + public float Potentia = 0; + + /// + /// The baseline chance of obtaining a psionic power when rolling for one. + /// + [DataField] + public float Chance = 0.04f; + + /// + /// Whether or not a Psion has an available "Reroll" to spend on attempting to gain powers. + /// + [DataField] + public bool CanReroll; + + /// + /// The Base amount of time (in minutes) this Psion is given the stutter effect if they become mindbroken. + /// + [DataField] + public float MindbreakingStutterTime = 5; + + public string MindbreakingStutterCondition = "Stutter"; + + public string MindbreakingStutterAccent = "StutteringAccent"; + + public string MindbreakingFeedback = "mindbreaking-feedback"; /// - /// Ifrits, revenants, etc are explicitly magical beings that shouldn't get mindbreakered. + /// How much should the odds of obtaining a Psionic Power be multiplied when rolling for one. /// - [DataField("removable")] + [DataField] + public float PowerRollMultiplier = 1f; + + /// + /// How much the odds of obtaining a Psionic Power should be multiplied when rolling for one. + + /// + [DataField] + public float PowerRollFlatBonus = 0; + + private (float, float) _baselineAmplification = (0.4f, 1.2f); + + /// + /// Use this datafield to change the range of Baseline Amplification. + /// + [DataField] + private (float, float) _baselineAmplificationFactors = (0.4f, 1.2f); + + /// + /// All Psionics automatically possess a random amount of initial starting Amplification, regardless of if they have any powers or not. + /// The game will crash if Robust.Random is handed a (bigger number, smaller number), so the logic here prevents any funny business. + /// + public (float, float) BaselineAmplification + { + get { return _baselineAmplification; } + private set + { + _baselineAmplification = (Math.Min( + _baselineAmplificationFactors.Item1, _baselineAmplificationFactors.Item2), + Math.Max(_baselineAmplificationFactors.Item1, _baselineAmplificationFactors.Item2)); + } + } + private (float, float) _baselineDampening = (0.4f, 1.2f); + + /// + /// Use this datafield to change the range of Baseline Amplification. + /// + [DataField] + private (float, float) _baselineDampeningFactors = (0.4f, 1.2f); + + /// + /// All Psionics automatically possess a random amount of initial starting Dampening, regardless of if they have any powers or not. + /// The game will crash if Robust.Random is handed a (bigger number, smaller number), so the logic here prevents any funny business. + /// + public (float, float) BaselineDampening + { + get { return _baselineDampening; } + private set + { + _baselineDampening = (Math.Min( + _baselineDampeningFactors.Item1, _baselineDampeningFactors.Item2), + Math.Max(_baselineDampeningFactors.Item1, _baselineDampeningFactors.Item2)); + } + } + + /// + /// Ifrits, revenants, etc are explicitly magical beings that shouldn't get mindbroken + /// + [DataField] public bool Removable = true; - [DataField("activePowers")] - public HashSet ActivePowers = new(); + /// + /// The list of all powers currently active on a Psionic, by power Prototype. + /// TODO: Not in this PR due to scope, but this needs to go to Server and not Shared. + /// + [ViewVariables(VVAccess.ReadOnly)] + public HashSet ActivePowers = new(); + + /// + /// The list of each Psionic Power by action with entityUid. + /// + [ViewVariables(VVAccess.ReadOnly)] + public Dictionary Actions = new(); + + /// + /// What sources of Amplification does this Psion have? + /// + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary AmplificationSources = new(); + + /// + /// A measure of how "Powerful" a Psion is. + /// TODO: Implement this in a separate PR. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float CurrentAmplification; + + /// + /// What sources of Dampening does this Psion have? + /// + [ViewVariables(VVAccess.ReadOnly)] + public readonly Dictionary DampeningSources = new(); + + /// + /// A measure of how "Controlled" a Psion is. + /// TODO: Implement this in a separate PR. + /// + [ViewVariables(VVAccess.ReadWrite)] + public float CurrentDampening; } } diff --git a/Content.Shared/Psionics/PsionicEvents.cs b/Content.Shared/Psionics/PsionicEvents.cs new file mode 100644 index 00000000000..be3bf03af62 --- /dev/null +++ b/Content.Shared/Psionics/PsionicEvents.cs @@ -0,0 +1,11 @@ +namespace Content.Shared.Psionics; + +/// +/// This event is raised whenever a psionic entity sets their casting stats(Amplification and Dampening), allowing other systems to modify the end result +/// of casting stat math. Useful if for example you want a species to have 5% higher Amplification overall. Or a drug inhibits total Dampening, etc. +/// +/// +/// +/// +[ByRefEvent] +public record struct OnSetPsionicStatsEvent(float AmplificationChangedAmount, float DampeningChangedAmount); \ No newline at end of file diff --git a/Content.Shared/Psionics/PsionicPowerPrototype.cs b/Content.Shared/Psionics/PsionicPowerPrototype.cs new file mode 100644 index 00000000000..621a3ceb114 --- /dev/null +++ b/Content.Shared/Psionics/PsionicPowerPrototype.cs @@ -0,0 +1,61 @@ +using Robust.Shared.Prototypes; + +namespace Content.Shared.Psionics; + +[Prototype] +public sealed partial class PsionicPowerPrototype : IPrototype +{ + /// + /// The ID of the psionic power to use. + /// + [IdDataField] + public string ID { get; } = default!; + + /// + /// The name of the psionic power. + /// + [DataField(required: true)] + public string Name = default!; + + /// + /// The description of a power in yml, used for player notifications. + /// + [DataField(required: true)] + public string Description = default!; + + /// + /// The list of each Action that this power adds in the form of ActionId and ActionEntity + /// + [DataField] + public List Actions = new(); + + /// + /// The list of what Components this power adds. + /// + [DataField] + public ComponentRegistry Components = new(); + + /// + /// What message will play as a popup when the power is initialized. + /// + [DataField(required: true)] + public string InitializationFeedback = "psionic-power-initialization-default"; + + /// + /// What message will this power generate when scanned by a Metempsionic Focused Pulse. + /// + [DataField(required: true)] + public string MetapsionicFeedback = "psionic-metapsionic-feedback-default"; + + /// + /// How much this power will increase or decrease a user's Amplification. + /// + [DataField] + public float AmplificationModifier = 0; + + /// + /// How much this power will increase or decrease a user's Dampening. + /// + [DataField] + public float DampeningModifier = 0; +} \ No newline at end of file diff --git a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs index 2739d5ba31a..f1f03bcb9ed 100644 --- a/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs +++ b/Content.Shared/Psionics/SharedPsionicAbilitiesSystem.cs @@ -1,7 +1,4 @@ -using Content.Shared.Actions; using Content.Shared.Administration.Logs; -using Content.Shared.Mobs; -using Content.Shared.Mobs.Components; using Content.Shared.Popups; using Content.Shared.Psionics.Glimmer; using Robust.Shared.Random; @@ -11,7 +8,6 @@ namespace Content.Shared.Abilities.Psionics { public sealed class SharedPsionicAbilitiesSystem : EntitySystem { - [Dependency] private readonly SharedActionsSystem _actions = default!; [Dependency] private readonly EntityLookupSystem _lookup = default!; [Dependency] private readonly SharedPopupSystem _popups = default!; [Dependency] private readonly ISharedAdminLogManager _adminLogger = default!; @@ -21,11 +17,7 @@ public sealed class SharedPsionicAbilitiesSystem : EntitySystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnInit); - SubscribeLocalEvent(OnShutdown); SubscribeLocalEvent(OnPowerUsed); - - SubscribeLocalEvent(OnMobStateChanged); } private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicPowerUsedEvent args) @@ -41,47 +33,6 @@ private void OnPowerUsed(EntityUid uid, PsionicComponent component, PsionicPower } } - private void OnInit(EntityUid uid, PsionicsDisabledComponent component, ComponentInit args) - { - SetPsionicsThroughEligibility(uid); - } - - private void OnShutdown(EntityUid uid, PsionicsDisabledComponent component, ComponentShutdown args) - { - SetPsionicsThroughEligibility(uid); - } - - private void OnMobStateChanged(EntityUid uid, PsionicComponent component, MobStateChangedEvent args) - { - SetPsionicsThroughEligibility(uid); - } - - /// - /// Checks whether the entity is eligible to use its psionic ability. This should be run after anything that could effect psionic eligibility. - /// - public void SetPsionicsThroughEligibility(EntityUid uid) - { - PsionicComponent? component = null; - if (!Resolve(uid, ref component, false)) - return; - - if (component.PsionicAbility == null) - return; - - _actions.TryGetActionData( component.PsionicAbility, out var actionData ); - - if (actionData == null) - return; - - _actions.SetEnabled(actionData.Owner, IsEligibleForPsionics(uid)); - } - - private bool IsEligibleForPsionics(EntityUid uid) - { - return !HasComp(uid) - && (!TryComp(uid, out var mobstate) || mobstate.CurrentState == MobState.Alive); - } - public void LogPowerUsed(EntityUid uid, string power, int minGlimmer = 8, int maxGlimmer = 12) { _adminLogger.Add(Database.LogType.Psionics, Database.LogImpact.Medium, $"{ToPrettyString(uid):player} used {power}"); diff --git a/Content.Shared/Roles/SharedRoleSystem.cs b/Content.Shared/Roles/SharedRoleSystem.cs index 94ad32164b3..e8053e4c678 100644 --- a/Content.Shared/Roles/SharedRoleSystem.cs +++ b/Content.Shared/Roles/SharedRoleSystem.cs @@ -1,4 +1,3 @@ -using System.Linq; using Content.Shared.Administration.Logs; using Content.Shared.Database; using Content.Shared.Mind; @@ -63,64 +62,6 @@ 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. /// @@ -236,11 +177,6 @@ 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/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index ff8b597a0d5..0b52cdde92e 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -7,6 +7,7 @@ using Content.Shared.StepTrigger.Systems; using Content.Shared.Stunnable; using Content.Shared.Throwing; +using Content.Shared.Mood; using JetBrains.Annotations; using Robust.Shared.Audio.Systems; using Robust.Shared.Containers; @@ -101,6 +102,8 @@ private void TrySlip(EntityUid uid, SlipperyComponent component, EntityUid other _stun.TryParalyze(other, TimeSpan.FromSeconds(component.ParalyzeTime), true); + RaiseLocalEvent(other, new MoodEffectEvent("MobSlipped")); + // Preventing from playing the slip sound when you are already knocked down. if (playSound) { diff --git a/Content.Shared/Station/SharedStationSpawningSystem.cs b/Content.Shared/Station/SharedStationSpawningSystem.cs index 59c875f30e1..715ee2a1494 100644 --- a/Content.Shared/Station/SharedStationSpawningSystem.cs +++ b/Content.Shared/Station/SharedStationSpawningSystem.cs @@ -1,17 +1,16 @@ 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!; @@ -22,27 +21,14 @@ public abstract class SharedStationSpawningSystem : EntitySystem /// /// Entity to load out. /// Starting gear to use. - public void EquipStartingGear(EntityUid entity, ProtoId? startingGear) + /// Character profile to use, if any. + public void EquipStartingGear(EntityUid entity, StartingGearPrototype startingGear, HumanoidCharacterProfile? profile) { - 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, null); + var equipmentStr = startingGear.GetGear(slot.Name, profile); if (string.IsNullOrEmpty(equipmentStr)) continue; diff --git a/Content.Shared/Traits/Assorted/Components/DogVisionComponent.cs b/Content.Shared/Traits/Assorted/Components/DogVisionComponent.cs new file mode 100644 index 00000000000..0979da8c352 --- /dev/null +++ b/Content.Shared/Traits/Assorted/Components/DogVisionComponent.cs @@ -0,0 +1,5 @@ +using Robust.Shared.GameStates; +namespace Content.Shared.Traits.Assorted.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class DogVisionComponent : Component { } diff --git a/Content.Shared/Traits/Assorted/Components/UltraVisionComponent.cs b/Content.Shared/Traits/Assorted/Components/UltraVisionComponent.cs new file mode 100644 index 00000000000..cbe4eb1a7b5 --- /dev/null +++ b/Content.Shared/Traits/Assorted/Components/UltraVisionComponent.cs @@ -0,0 +1,5 @@ +using Robust.Shared.GameStates; +namespace Content.Shared.Traits.Assorted.Components; + +[RegisterComponent, NetworkedComponent] +public sealed partial class UltraVisionComponent : Component { } diff --git a/Resources/Changelog/Changelog.yml b/Resources/Changelog/Changelog.yml index 73025dd7043..24e7ed8d9b6 100644 --- a/Resources/Changelog/Changelog.yml +++ b/Resources/Changelog/Changelog.yml @@ -5378,3 +5378,84 @@ Entries: message: Adds a new blindfold variant into loadouts. id: 6268 time: '2024-08-16T12:43:33.0000000+00:00' +- author: Skubman + changes: + - type: Add + message: >- + 16 new horns for Onis have been added, with three-tone variants and some + striped variants! The new horns include the Serket, Nepeta, Vantas, + Makara, and more. + - type: Add + message: >- + The Oni horns Double Curved and Double Curved Outwards have received + three-tone variants. + id: 6269 + time: '2024-08-18T05:52:22.0000000+00:00' +- author: Mnemotechnician + changes: + - type: Fix + message: You no longer need to have a station record to publish news. + id: 6270 + time: '2024-08-18T18:11:10.0000000+00:00' +- author: ShatteredSwords + changes: + - type: Add + message: Several useful items have been added to loadouts + - type: Add + message: Cowtools are selectable for clown in the "Jobs" category of loadouts + id: 6271 + time: '2024-08-20T06:48:59.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: >- + The Mood System has been ported from White Dream. Mood acts as a 3rd + healthbar, alongside Health and Stamina, representing your character's + current mental state. Having either high or low mood can modify certain + physical attributes. + - type: Add + message: >- + Mood modifies your Critical Threshold. Your critical threshold can be + increased or decreased depending on how high or low your character's + mood is. + - type: Add + message: >- + Mood modifies your Movement Speed. Characters move faster when they have + an overall high mood, and move slower when they have a lower mood. + - type: Add + message: >- + Saturnine and Sanguine have been added to the list of Mental traits, + both providing innate modifiers to a character's Morale. + id: 6272 + time: '2024-08-20T08:16:05.0000000+00:00' +- author: VMSolidus + changes: + - type: Add + message: Latent Psychic has been added as a new positive trait. + - type: Tweak + message: >- + Psionics have received a substantial refactor. While no new powers have + been added this patch, this initial refactor lays the groundwork so that + new psionic powers will be easier to create. + - type: Tweak + message: >- + Latent Psychic is now fully required to become psionic, or to interact + with Oracle. + - type: Tweak + message: Psychics can now have more than one active power. + - type: Remove + message: Mimes are no longer Psionic. + - type: Tweak + message: >- + Chaplain, Mantis, & Mystagogue all receive the Latent Psychic trait for + free, automatically. + id: 6273 + time: '2024-08-20T18:42:44.0000000+00:00' +- author: VMSolidus + changes: + - type: Fix + message: >- + Fixed an issue where Overlays(Dogvision, Ultravision, Mood) would apply + globally to all entities when updating. + id: 6274 + time: '2024-08-20T18:49:28.0000000+00:00' diff --git a/Resources/Credits/GitHub.txt b/Resources/Credits/GitHub.txt index ba4a72af1fb..40424a21e0e 100644 --- a/Resources/Credits/GitHub.txt +++ b/Resources/Credits/GitHub.txt @@ -1 +1 @@ -0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, angelofallars, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, beck-thompson, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlueHNT, Boaz1111, BobdaBiscuit, brainfood1183, BramvanZijp, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CaasGit, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, clorl, Clyybber, CodedCrow, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, Deeeeja, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, Fansana, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, FoxxoTrystan, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, gluesniffler, GNF54, Golinth, GoodWheatley, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, Hebiman, HerCoyote23, HoofedEar, Hoolny, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LovelyLophi, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, Mnemotechnician, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, ShadowCommander, Shadowtheprotogen546, SignalWalker, SimpleStation14, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, SleepyScarecrow, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, stalengd, Stealthbomber16, stellar-novas, StrawberryMoses, superjj18, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, Tmanzxd, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, wafehling, WarMechanic, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem +0x6273, 2013HORSEMEATSCANDAL, 20kdc, 21Melkuu, 4dplanner, 612git, 778b, Ablankmann, Acruid, actioninja, adamsong, Admiral-Obvious-001, Adrian16199, Aerocrux, Aexxie, africalimedrop, Agoichi, Ahion, AJCM-git, AjexRose, Alekshhh, AlexMorgan3817, AlmondFlour, AlphaQwerty, Altoids1, amylizzle, ancientpower, angelofallars, ArchPigeon, Arendian, arimah, Arteben, AruMoon, as334, AsikKEsel, asperger-sind, aspiringLich, avghdev, AzzyIsNotHere, BananaFlambe, BasedUser, beck-thompson, BGare, BingoJohnson-zz, BismarckShuffle, Bixkitts, Blackern5000, Blazeror, BlueHNT, Boaz1111, BobdaBiscuit, brainfood1183, BramvanZijp, Brandon-Huu, Bribrooo, Bright0, brndd, BubblegumBlue, BYONDFuckery, c4llv07e, CaasGit, CakeQ, CaptainSqrBeard, Carbonhell, Carolyn3114, CatTheSystem, Centronias, chairbender, Charlese2, Cheackraze, cheesePizza2, Chief-Engineer, chromiumboy, Chronophylos, clorl, Clyybber, CodedCrow, ColdAutumnRain, Colin-Tel, collinlunn, ComicIronic, coolmankid12345, corentt, crazybrain23, creadth, CrigCrag, Crotalus, CrudeWax, CrzyPotato, Cyberboss, d34d10cc, Daemon, daerSeebaer, dahnte, dakamakat, dakimasu, DamianX, DangerRevolution, daniel-cr, Darkenson, DawBla, dch-GH, Deahaka, DEATHB4DEFEAT, DeathCamel58, deathride58, DebugOk, Decappi, Deeeeja, deepdarkdepths, Delete69, deltanedas, DeltaV-Bot, DerbyX, DoctorBeard, DogZeroX, dontbetank, dootythefrooty, Doru991, DoubleRiceEddiedd, DrMelon, DrSmugleaf, drteaspoon420, DTanxxx, DubiousDoggo, Duddino, Dutch-VanDerLinde, Easypoller, eclips_e, EdenTheLiznerd, EEASAS, Efruit, ElectroSR, elthundercloud, Emisse, EmoGarbage404, Endecc, enumerate0, eoineoineoin, ERORR404V1, Errant-4, estacaoespacialpirata, exincore, exp111, Fahasor, FairlySadPanda, Fansana, ficcialfaint, Fildrance, FillerVK, Fishfish458, Flareguy, FluffiestFloof, FluidRock, FoLoKe, fooberticus, Fortune117, FoxxoTrystan, freeman2651, Froffy025, Fromoriss, FungiFellow, GalacticChimp, gbasood, Geekyhobo, Genkail, geraeumig, Ghagliiarghii, Git-Nivrak, github-actions[bot], gituhabu, gluesniffler, GNF54, Golinth, GoodWheatley, graevy, GreyMario, Guess-My-Name, gusxyz, h3half, Hanzdegloker, Hardly3D, harikattar, HerCoyote23, HoofedEar, Hoolny, hord-brayden, hubismal, Hugal31, Huxellberger, iacore, IamVelcroboy, icekot8, igorsaux, ike709, Illiux, Ilya246, IlyaElDunaev, Injazz, Insineer, Interrobang01, IProduceWidgets, ItsMeThom, Jackal298, Jackrost, jamessimo, janekvap, JerryImMouse, Jessetriesagain, jessicamaybe, Jezithyr, jicksaw, JiimBob, JoeHammad1844, JohnGinnane, johnku1, joshepvodka, jproads, Jrpl, juliangiebel, JustArt1m, JustCone14, JustinTrotter, KaiShibaa, kalane15, kalanosh, Kelrak, kerisargit, keronshb, KIBORG04, Killerqu00, KingFroozy, kira-er, Kit0vras, KittenColony, Ko4ergaPunk, komunre, koteq, Krunklehorn, kxvvv, Lamrr, LankLTE, lapatison, Leander-0, leonardo-dabepis, LetterN, Level10Cybermancer, lever1209, liltenhead, LittleBuilderJane, Lomcastar, LordCarve, LordEclipse, LovelyLophi, Lukasz825700516, lunarcomets, luringens, lvvova1, lzimann, lzk228, MACMAN2003, Macoron, MagnusCrowe, ManelNavola, Matz05, MehimoNemo, MeltedPixel, MemeProof, Menshin, Mervill, metalgearsloth, mhamsterr, MilenVolf, Minty642, Mirino97, mirrorcult, misandrie, MishaUnity, MisterMecky, Mith-randalf, Mnemotechnician, Moneyl, Moomoobeef, moony, Morb0, Mr0maks, musicmanvr, Myakot, Myctai, N3X15, Nairodian, Naive817, namespace-Memory, NickPowers43, nikthechampiongr, Nimfar11, Nirnael, nmajask, nok-ko, notafet, notquitehadouken, noudoit, noverd, nuke-haus, NULL882, OCOtheOmega, OctoRocket, OldDanceJacket, onoira, osjarw, Owai-Seek, pali6, Pangogie, patrikturi, PaulRitter, Peptide90, peptron1, Phantom-Lily, PHCodes, PixelTheKermit, PJB3005, Plykiya, pofitlo, pointer-to-null, PolterTzi, PoorMansDreams, potato1234x, ProfanedBane, PrPleGoo, ps3moira, Pspritechologist, Psychpsyo, psykzz, PuroSlavKing, quatre, QuietlyWhisper, qwerltaz, Radosvik, Radrark, Rainbeon, Rainfey, Rane, ravage123321, rbertoche, Redict, RedlineTriad, RednoWCirabrab, RemberBM, RemieRichards, RemTim, rene-descartes2021, RiceMar1244, RieBi, Rinkashikachi, Rockdtben, rolfero, rosieposieeee, Saakra, Samsterious, SaphireLattice, ScalyChimp, scrato, Scribbles0, Serkket, ShadowCommander, Shadowtheprotogen546, SignalWalker, SimpleStation14, Simyon264, Sirionaut, siyengar04, Skarletto, Skrauz, Skyedra, SlamBamActionman, slarticodefast, Slava0135, SleepyScarecrow, Snowni, snowsignal, SonicHDC, SoulSloth, SpaceManiac, SpeltIncorrectyl, spoogemonster, ssdaniel24, stalengd, Stealthbomber16, stellar-novas, StrawberryMoses, superjj18, SweptWasTaken, Szunti, TadJohnson00, takemysoult, TaralGit, Tayrtahn, tday93, TekuNut, TemporalOroboros, tentekal, tgrkzus, thatrandomcanadianguy, TheArturZh, theashtronaut, thedraccx, themias, Theomund, theOperand, TheShuEd, TimrodDX, Titian3, tkdrg, Tmanzxd, tmtmtl30, TokenStyle, tom-leys, tomasalves8, Tomeno, Tornado-Technology, tosatur, Tryded, TsjipTsjip, Tunguso4ka, TurboTrackerss14, Tyler-IN, Tyzemol, UbaserB, UKNOWH, UnicornOnLSD, Uriende, UristMcDorf, Vaaankas, Varen, VasilisThePikachu, veliebm, Veritius, Vermidia, Verslebas, VigersRay, Visne, VMSolidus, volundr-, Voomra, Vordenburg, vulppine, wafehling, WarMechanic, waylon531, weaversam8, whateverusername0, Willhelm53, wixoaGit, WlarusFromDaSpace, wrexbe, xRiriq, yathxyz, Ygg01, YotaXP, YuriyKiss, zach-hill, Zandario, Zap527, Zealith-Gamer, ZelteHonor, zerorulez, zionnBE, zlodo, ZNixian, ZoldorfTheWizard, Zumorica, Zymem 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 new file mode 100644 index 00000000000..941643dd9a9 --- /dev/null +++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-pirates.ftl @@ -0,0 +1,10 @@ +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/Locale/en-US/markings/oni.ftl b/Resources/Locale/en-US/markings/oni.ftl index 6c34f437b15..abc77e96d17 100644 --- a/Resources/Locale/en-US/markings/oni.ftl +++ b/Resources/Locale/en-US/markings/oni.ftl @@ -1,7 +1,7 @@ marking-OniHornTallCurved = Tall Curved marking-OniHornTallCurved-tall_curved = Tall Curved -marking-OniHornTallCurved3Tone = Tall Curved Three Tone +marking-OniHornTallCurved3Tone = Tall Curved (Three Tone) marking-OniHornTallCurved3Tone-tall_curved_3tone_1 = Bottom Third marking-OniHornTallCurved3Tone-tall_curved_3tone_2 = Middle Third marking-OniHornTallCurved3Tone-tall_curved_3tone_3 = Top Third @@ -9,10 +9,168 @@ marking-OniHornTallCurved3Tone-tall_curved_3tone_3 = Top Third marking-OniHornTallBull= Tall Bull marking-OniHornTallBull-tall_bull = Tall Bull -marking-OniHornTallBull3Tone = Tall Bull Three Tone +marking-OniHornTallBull3Tone = Tall Bull (Three Tone) marking-OniHornTallBull3Tone-tall_bull_3tone_1 = Bottom Third marking-OniHornTallBull3Tone-tall_bull_3tone_2 = Middle Third marking-OniHornTallBull3Tone-tall_bull_3tone_3 = Top Third marking-OniHornCrowned = Crowned marking-OniHornCrowned-crowned = Crowned + +marking-OniHornSerket = Serket +marking-OniHornSerket-serket = Serket + +marking-OniHornSerket3Tone = Serket (Three Tone) +marking-OniHornSerket3Tone-serket_3tone_1 = Bottom Third +marking-OniHornSerket3Tone-serket_3tone_2 = Middle Third +marking-OniHornSerket3Tone-serket_3tone_3 = Top Third + +marking-OniHornPisces = Pisces +marking-OniHornPisces-pisces = Pisces + +marking-OniHornPisces2Tone = Pisces (Striped) +marking-OniHornPisces2Tone-pisces_2tone_1 = Even stripes +marking-OniHornPisces2Tone-pisces_2tone_2 = Odd stripes + +marking-OniHornPisces3Tone = Pisces (Three Tone) +marking-OniHornPisces3Tone-pisces_3tone_1 = Bottom Third +marking-OniHornPisces3Tone-pisces_3tone_2 = Middle Third +marking-OniHornPisces3Tone-pisces_3tone_3 = Top Third + +marking-OniHornVirgo = Virgo +marking-OniHornVirgo-virgo = Virgo + +marking-OniHornVirgo3Tone = Virgo (Three Tone) +marking-OniHornVirgo3Tone-virgo_3tone_1 = Bottom Third +marking-OniHornVirgo3Tone-virgo_3tone_2 = Middle Third +marking-OniHornVirgo3Tone-virgo_3tone_3 = Top Third + +marking-OniHornSagittarius = Sagittarius +marking-OniHornSagittarius-sagittarius = Sagittarius + +marking-OniHornSagittarius3Tone = Sagittarius (Three Tone) +marking-OniHornSagittarius3Tone-sagittarius_3tone_1 = Bottom Third +marking-OniHornSagittarius3Tone-sagittarius_3tone_2 = Middle Third +marking-OniHornSagittarius3Tone-sagittarius_3tone_3 = Top Third + +marking-OniHornVantas = Vantas +marking-OniHornVantas-vantas = Vantas + +marking-OniHornVantas3Tone = Vantas (Three Tone) +marking-OniHornVantas3Tone-vantas_3tone_1 = Bottom Third +marking-OniHornVantas3Tone-vantas_3tone_2 = Middle Third +marking-OniHornVantas3Tone-vantas_3tone_3 = Top Third + +marking-OniHornMakara = Makara +marking-OniHornMakara-makara = Makara + +marking-OniHornMakara2Tone = Makara (Striped) +marking-OniHornMakara2Tone-makara_2tone_1 = Even stripes +marking-OniHornMakara2Tone-makara_2tone_2 = Odd stripes + +marking-OniHornMakara3Tone = Makara (Three Tone) +marking-OniHornMakara3Tone-makara_3tone_1 = Bottom Third +marking-OniHornMakara3Tone-makara_3tone_2 = Middle Third +marking-OniHornMakara3Tone-makara_3tone_3 = Top Third + +marking-OniHornNepeta = Nepeta +marking-OniHornNepeta-nepeta = Nepeta + +marking-OniHornNepeta3Tone = Nepeta (Three Tone) +marking-OniHornNepeta3Tone-nepeta_3tone_1 = Bottom Third +marking-OniHornNepeta3Tone-nepeta_3tone_2 = Middle Third +marking-OniHornNepeta3Tone-nepeta_3tone_3 = Top Third + +marking-OniHornTaurus = Taurus +marking-OniHornTaurus-taurus = Taurus + +marking-OniHornTaurus2Tone = Taurus (Striped) +marking-OniHornTaurus2Tone-taurus_2tone_1 = Even stripes +marking-OniHornTaurus2Tone-taurus_2tone_2 = Odd stripes + +marking-OniHornTaurus3Tone = Taurus (Three Tone) +marking-OniHornTaurus3Tone-taurus_3tone_1 = Bottom Third +marking-OniHornTaurus3Tone-taurus_3tone_2 = Middle Third +marking-OniHornTaurus3Tone-taurus_3tone_3 = Top Third + +marking-OniHornAries = Aries +marking-OniHornAries-aries = Aries + +marking-OniHornAries3Tone = Aries (Three Tone) +marking-OniHornAries3Tone-aries_3tone_1 = Bottom Third +marking-OniHornAries3Tone-aries_3tone_2 = Middle Third +marking-OniHornAries3Tone-aries_3tone_3 = Top Third + +marking-OniHornTavris = Tavris +marking-OniHornTavris-tavris = Tavris + +marking-OniHornTavris3Tone = Tavris (Three Tone) +marking-OniHornTavris3Tone-tavris_3tone_1 = Bottom Third +marking-OniHornTavris3Tone-tavris_3tone_2 = Middle Third +marking-OniHornTavris3Tone-tavris_3tone_3 = Top Third + +marking-OniHornInclined = Inclined +marking-OniHornInclined-inclined = Inclined + +marking-OniHornInclined3Tone = Inclined (Three Tone) +marking-OniHornInclined3Tone-inclined_3tone_1 = Bottom Third +marking-OniHornInclined3Tone-inclined_3tone_2 = Middle Third +marking-OniHornInclined3Tone-inclined_3tone_3 = Top Third + +marking-OniHornWavy = Wavy +marking-OniHornWavy-wavy = Wavy + +marking-OniHornWavy2Tone = Wavy (Striped) +marking-OniHornWavy2Tone-wavy_2tone_1 = Even stripes +marking-OniHornWavy2Tone-wavy_2tone_2 = Odd stripes + +marking-OniHornWavy3Tone = Wavy (Three Tone) +marking-OniHornWavy3Tone-wavy_3tone_1 = Bottom Third +marking-OniHornWavy3Tone-wavy_3tone_2 = Middle Third +marking-OniHornWavy3Tone-wavy_3tone_3 = Top Third + +marking-OniHornAntlers = Antlers +marking-OniHornAntlers-antlers_2tone_1 = Base +marking-OniHornAntlers-antlers_2tone_2 = Wings + +marking-OniHornAntlers3Tone = Antlers (Three Tone) +marking-OniHornAntlers3Tone-antlers_3tone_1 = Bottom Third +marking-OniHornAntlers3Tone-antlers_3tone_2 = Middle Third +marking-OniHornAntlers3Tone-antlers_3tone_3 = Top Third + +marking-OniHornUnicorn = Unicorn +marking-OniHornUnicorn-unicorn = Unicorn + +marking-OniHornErebia = Erebia +marking-OniHornErebia-erebia = Erebia + +marking-OniHornErebia3Tone = Erebia (Three Tone) +marking-OniHornErebia3Tone-erebia_3tone_1 = Bottom Third +marking-OniHornErebia3Tone-erebia_3tone_2 = Middle Third +marking-OniHornErebia3Tone-erebia_3tone_3 = Top Third + +marking-OniHornErebiaRings = Erebia (Ringed) +marking-OniHornErebiaRings-erebia = Erebia +marking-OniHornErebiaRings-erebia_rings = Rings + +marking-OniHornDoubleThick = Double Thick +marking-OniHornDoubleThick-double_thick = Double Thick + +marking-OniHornDoubleThick2Tone = Double Thick (Striped) +marking-OniHornDoubleThick2Tone-double_thick_2tone_1 = Even stripes +marking-OniHornDoubleThick2Tone-double_thick_2tone_2 = Odd stripes + +marking-OniHornDoubleThick3Tone = Double Thick (Three Tone) +marking-OniHornDoubleThick3Tone-double_thick_3tone_1 = Bottom Third +marking-OniHornDoubleThick3Tone-double_thick_3tone_2 = Middle Third +marking-OniHornDoubleThick3Tone-double_thick_3tone_3 = Top Third + +marking-OniHornDoubleCurved3Tone = Double Curved (Three Tone) +marking-OniHornDoubleCurved3Tone-double_curved_3tone_1 = Bottom Third +marking-OniHornDoubleCurved3Tone-double_curved_3tone_2 = Middle Third +marking-OniHornDoubleCurved3Tone-double_curved_3tone_3 = Top Third + +marking-OniHornDoubleCurvedOutwards3Tone = Double Curved Outwards (Three Tone) +marking-OniHornDoubleCurvedOutwards3Tone-double_curved_outwards_3tone_1 = Bottom Third +marking-OniHornDoubleCurvedOutwards3Tone-double_curved_outwards_3tone_2 = Middle Third +marking-OniHornDoubleCurvedOutwards3Tone-double_curved_outwards_3tone_3 = Top Third diff --git a/Resources/Locale/en-US/mood/mood-alerts.ftl b/Resources/Locale/en-US/mood/mood-alerts.ftl new file mode 100644 index 00000000000..c5f76c5fb83 --- /dev/null +++ b/Resources/Locale/en-US/mood/mood-alerts.ftl @@ -0,0 +1,32 @@ +alerts-mood-dead-name = Dead +alerts-mood-dead-desc = Eternal emptiness has enveloped me, and the world no longer has power over my soul. + +alerts-mood-insane-name = Insane +alerts-mood-insane-desc = Darkness and hopelessness smolder in my soul, the world is doomed to absolute evil. + +alerts-mood-horrible-name = Horrible +alerts-mood-horrible-desc = I struggle with pain and fears, my fate is a series of torments and sufferings. + +alerts-mood-terrible-name = Terrible +alerts-mood-terrible-desc = My life has dried up like blood from a wound, and there is only darkness and despair all around. + +alerts-mood-bad-name = Bad +alerts-mood-bad-desc = My strength is leaving me, and every day becomes a difficult ordeal. + +alerts-mood-meh-name = Mediocre +alerts-mood-meh-desc = The world is full of dangers and pain, and my hopes are slowly dying. + +alerts-mood-neutral-name = Neutral +alerts-mood-neutral-desc = I continue on my way, despite threats and hardships, looking for the slightest light in the darkness. + +alerts-mood-good-name = Good +alerts-mood-good-desc = In this world of suffering, I find a little relief and hope. + +alerts-mood-great-name = Great +alerts-mood-great-desc = My strength is restored, and the world seems to be the lesser evil and pain. + +alerts-mood-exceptional-name = Exceptional +alerts-mood-exceptional-desc = Strength and hope fills me, despite the threats that lurk around me. + +alerts-mood-perfect-name = Perfect +alerts-mood-perfect-desc = My soul is full of light and power, and I am ready to fight the darkness in this cruel world. diff --git a/Resources/Locale/en-US/mood/mood.ftl b/Resources/Locale/en-US/mood/mood.ftl new file mode 100644 index 00000000000..c12ec7246ec --- /dev/null +++ b/Resources/Locale/en-US/mood/mood.ftl @@ -0,0 +1,54 @@ +mood-show-effects-start = [font size=12]Mood:[/font] + +mood-effect-HungerOverfed = I ate so much, I feel as though I'm about to burst! +mood-effect-HungerOkay = I am feeling full. +mood-effect-HungerPeckish = I could go for a snack right about now. +mood-effect-HungerStarving = I NEED FOOD! + +mood-effect-ThirstOverHydrated = I feel dizzy after drinking too much. +mood-effect-ThirstOkay = I'm feeling refreshed. +mood-effect-ThirstThirsty = My lips are a little dry. +mood-effect-ThirstParched = I NEED WATER! + +mood-effect-HealthNoDamage = I'm in no pain. +mood-effect-HealthLightDamage = It's just a scratch, but it hurts nonetheless +mood-effect-HealthSevereDamage = The pain is almost unbearable! +mood-effect-HealthHeavyDamage = Agony gnaws at my soul! + +mood-effect-Handcuffed = I am being held captive. + +mood-effect-Suffocating = I.. Can't.. Breathe... + +mood-effect-OnFire = IT BURNS!!! + +mood-effect-Creampied = I was baptized. It tastes like pie. + +mood-effect-MobSlipped = I slipped! I should be more careful next time. + +mood-effect-MobVomit = My lunch tasted awful coming back up. + +mood-effect-MobLowPressure = My whole body feels like it's going to burst! + +mood-effect-MobHighPressure = I feel as though I am being crushed on all sides! + +mood-effect-TraitSaturnine = Everything kind of sucks. I hate this job. + +mood-effect-Dead = You are dead. + +mood-effect-BeingHugged = Hugs are nice. + +mood-effect-ArcadePlay = I had fun playing an interesting arcade game. + +mood-effect-GotBlessed = I was blessed. + +mood-effect-PetAnimal = Animals are so cute, I can't stop petting them! + +mood-effect-SavedLife = It's so nice to save someone's life + +mood-effect-TraitorFocused = I have a goal, and I will accomplish it no matter what. + +mood-effect-RevolutionFocused = VIVA LA REVOLUTION!!! + +mood-effect-CultFocused = Dark Gods, grant me strength! + +mood-effect-TraitSanguine = I have nothing to worry about. I'm sure everything will turn out well in the end! \ No newline at end of file diff --git a/Resources/Locale/en-US/psionics/psionic-powers.ftl b/Resources/Locale/en-US/psionics/psionic-powers.ftl new file mode 100644 index 00000000000..1844427f67f --- /dev/null +++ b/Resources/Locale/en-US/psionics/psionic-powers.ftl @@ -0,0 +1,69 @@ +generic-power-initialization-feedback = I Awaken. + +# Dispel +dispel-power-description = Dispel summoned entities such as familiars or forcewalls. +dispel-power-initialization-feedback = The powers of fate are nothing to me. I feel as though I can reach out to the strands around me, and enforce reality upon others. +dispel-power-metapsionic-feedback = {CAPITALIZE($entity)} is a mighty stone, standing against the currents of fate + +# Mass Sleep +mass-sleep-power-description = Put targets in a small area to sleep. +mass-sleep-initialization-feedback = Reaching out to the minds around me, I have located the words that can send others to the realm of dreams. +mass-sleep-metapsionic-feedback = {CAPITALIZE($entity)} bears the indelible mark of a dream thief. + +# Mind Swap +mind-swap-power-description = Swap minds with the target. Either can change back after 20 seconds. +mind-swap-power-initialization-feedback = I can feel the bonds of soul and body wither at my whim, my vessel may be replaced with that of another. +mind-swap-power-metapsionic-feedback = {CAPITALIZE($entity)} lacks a strong bond with their vessel, as if their connection with spirit is malleable. + +# Noospheric Zap +noospheric-zap-power-description = Shocks the conciousness of the target and leaves them stunned and stuttering. +noospheric-zap-power-initialization-feedback = + In a single transcendent moment, I find myself standing in a universe tiled by silicon. + I wander this place for days, desperate to find some form of life, and yet none greet me. + Just before I succumb to thirst, a man of silver finds me. He plunges his arm into my body, and I awake screaming. +noospheric-zap-power-metapsionic-feedback = + I look inside {CAPITALIZE($entity)}'s heart, and there, nestled amidst the flesh, whirs a microscopic sliver of a being composed of pure energy. + It turns upon my gaze with malice, its silvery eyes filled with a hatred for the carbon-fleshed. + +# Pyrokinesis +pyrokinesis-power-description = Light a flammable target on fire. +pyrokinesis-power-initialization-feedback = + There is a brilliant flash of light and heat, and for an instant I feel as though every milimeter of my flesh is turned to vapor. + Yet death does not come for me, though I find myself praying it does. The world beyond is both agonizingly hot and bone chilling. + For weeks I despair that Gehenna is real, I starve, I cry, I scream, and the pain does not cease. Finally, a man in white, with the face of an ogrous + fly beckons me to offer my service. When I reach out to shake his hand, the vision fades, and I find myself standing in the prime materium. + I know His name now, it is the Secret of Fire. Merely by thinking of it, I can feel the heat of that place come to my hands. +pyrokinesis-power-metapsionic-feedback = The Secret of Fire dwells within {CAPITALIZE($entity)} + +# Metapsionic Pulse +metapsionic-power-description = Send a mental pulse through the area to see if there are any psychics nearby. +metapsionic-power-initialization-feedback = + The world around me awakens with dreamlight. For a transcendent moment, I can see all that is, all that will ever be. + I find myself staggering, my lips parched not for want of water, but to drink of the cup of knowledge. I. Must. Find. It. +metapsionic-power-metapsionic-feedback = {CAPITALIZE($entity)} gazes back upon thee. + +# Psionic Regeneration +psionic-regeneration-power-description = Push your natural metabolism to the limit to power your body's regenerative capability. +psionic-regeneration-power-initialization-feedback = + I look within myself, finding a wellspring of life. +psionic-regeneration-power-metapsionic-feedback = {CAPITALIZE($entity)} possesses an overwhelming will to live + +# Telegnosis +telegnosis-power-description = Create a telegnostic projection to remotely observe things. +telegnosis-power-initialization-feedback = + With my next step, I find that I am no longer in the material realm. My feet are trodding upon a bridge of rainbow light. + Yet strangly, as I look left and right, I first see a color that is as pink within pink, and to my right, blue within blue. + Just as my mind reels from the displeasure of knowing colors that aren't, a creature I can only describe as a + dragon with the wings of a peacock swoops down, and consumes my flesh in a single bite. I awaken in an instant, to a world utterly devoid + of true, real colors. +telegnosis-power-metapsionic-feedback = {CAPITALIZE($entity)}'s soul travels across bridges composed of dreamlight + +# Psionic Invisibility +psionic-invisibility-power-description = Render yourself invisible to any entity that could potentially be psychic. Borgs, animals, and so on are not affected. +psionic-invisibility-power-initialization-feedback = + I suddenly find myself plunged into a world utterly without light, yet I can feel the rays of warmth cast upon me. + Pondering this, I arrive at a realization that sight itself is an illusion. I reject it, I deny that light itself is real. + When I awaken, I can no longer see even myself. +psionic-invisibility-power-metapsionic-feedback = {CAPITALIZE($entity)}'s wyrd seeks to hide from thine gaze + +mindbreaking-feedback = The light of life vanishes from {CAPITALIZE($entity)}'s eyes, leaving behind a husk pretending at sapience \ No newline at end of file diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl index 828afa76669..f995a129b2c 100644 --- a/Resources/Locale/en-US/traits/traits.ftl +++ b/Resources/Locale/en-US/traits/traits.ftl @@ -86,6 +86,12 @@ trait-description-Foreigner = For one reason or another you do not speak this station's primary language. Instead, you have a translator issued to you that only you can use. +trait-name-Saturnine = Saturnine +trait-description-Saturnine = You are naturally dour and morose. Your mood is permanently decreased by a large amount. + +trait-name-Sanguine = Sanguine +trait-description-Sanguine = You are naturally upbeat and cheerful! Your mood is permanently increased by a large amount. + trait-name-WillToLive = Will To Live trait-description-WillToLive = You have an unusually strong "will to live", and will resist death more than others. @@ -189,4 +195,10 @@ trait-description-WeaponsGeneralist = Your melee damage bonus for all Brute damage types (Blunt, Slash, Piercing) becomes 25%. trait-name-Singer = Singer -trait-description-Singer = You are naturally capable of singing simple melodies with your voice. \ No newline at end of file +trait-description-Singer = You are naturally capable of singing simple melodies with your voice. + +trait-name-LatentPsychic = Latent Psychic +trait-description-LatentPsychic = + Your mind and soul are open to the noosphere, allowing for use of Telepathy. + Thus, you are eligible for potentially receiving psychic powers. + It is possible that you may be hunted by otherworldly forces, so consider keeping your powers a secret. diff --git a/Resources/Locale/ru-RU/mood/mood-alerts.ftl b/Resources/Locale/ru-RU/mood/mood-alerts.ftl new file mode 100644 index 00000000000..e96fb1f09f5 --- /dev/null +++ b/Resources/Locale/ru-RU/mood/mood-alerts.ftl @@ -0,0 +1,22 @@ +alerts-mood-insane-name = Безумие +alerts-mood-insane-desc = В моей душе тлеют мрак и безнадежность, мир обречен на абсолютное зло. +alerts-mood-horrible-name = Печально +alerts-mood-horrible-desc = Я борюсь с болями и страхами, моя судьба - череда мучений и страданий. +alerts-mood-terrible-name = Очень плохо +alerts-mood-terrible-desc = Моя жизнь иссякла, как кровь из раны, и вокруг лишь мрак и отчаяние. +alerts-mood-bad-name = Плохо +alerts-mood-bad-desc = Силы покидают меня, и каждый день становится тяжелым испытанием. +alerts-mood-meh-name = Нехорошо +alerts-mood-meh-desc = Мир полон угроз и боли, и мои надежды медленно умирают. +alerts-mood-neutral-name = Нормально +alerts-mood-neutral-desc = Я продолжаю свой путь, несмотря на угрозы и лишения, ища хоть малейший свет во мраке. +alerts-mood-good-name = Неплохо +alerts-mood-good-desc = В этом мире полном страданий, я обретаю небольшое облегчение и надежду. +alerts-mood-great-name = Хорошо +alerts-mood-great-desc = Моя сила восстанавливается, и мир кажется меньшим злом и болью. +alerts-mood-exceptional-name = Очень хорошо +alerts-mood-exceptional-desc = Я ощущаю в себе силы и надежду на лучшие дни, несмотря на угрозы, что таятся вокруг. +alerts-mood-perfect-name = Великолепно +alerts-mood-perfect-desc = Моя душа полна света и силы, и я готов сразиться с тьмой в этом жестоком мире. +alerts-mood-dead-name = Мёртв +alerts-mood-dead-desc = Вечная пустота окутала меня, и мир больше не имеет власти над моей душой. diff --git a/Resources/Locale/ru-RU/mood/mood.ftl b/Resources/Locale/ru-RU/mood/mood.ftl new file mode 100644 index 00000000000..b9619035ea2 --- /dev/null +++ b/Resources/Locale/ru-RU/mood/mood.ftl @@ -0,0 +1 @@ +mood-show-effects-start = [font size=12]Настроение:[/font] diff --git a/Resources/Maps/Shuttles/striker.yml b/Resources/Maps/Shuttles/striker.yml index 88b113d7fdb..35b6178bd45 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: 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 -... +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 +... diff --git a/Resources/Prototypes/Alerts/alerts.yml b/Resources/Prototypes/Alerts/alerts.yml index 00b799670f6..47015bf21f5 100644 --- a/Resources/Prototypes/Alerts/alerts.yml +++ b/Resources/Prototypes/Alerts/alerts.yml @@ -5,6 +5,7 @@ id: BaseAlertOrder order: - category: Health + - category: Mood - category: Stamina - alertType: SuitPower - category: Internals @@ -496,3 +497,114 @@ state: critical name: Debug6 description: Debug + +# Moods +- type: alert + id: Insane + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood_insane + name: alerts-mood-insane-name + description: alerts-mood-insane-desc + +- type: alert + id: Horrible + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood1 + name: alerts-mood-horrible-name + description: alerts-mood-horrible-desc + +- type: alert + id: Terrible + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood2 + name: alerts-mood-terrible-name + description: alerts-mood-terrible-desc + +- type: alert + id: Bad + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood3 + name: alerts-mood-bad-name + description: alerts-mood-bad-desc + +- type: alert + id: Meh + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood4 + name: alerts-mood-meh-name + description: alerts-mood-meh-desc + +- type: alert + id: Neutral + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood5 + name: alerts-mood-neutral-name + description: alerts-mood-neutral-desc + +- type: alert + id: Good + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood6 + name: alerts-mood-good-name + description: alerts-mood-good-desc + +- type: alert + id: Great + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood7 + name: alerts-mood-great-name + description: alerts-mood-great-desc + +- type: alert + id: Exceptional + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood8 + name: alerts-mood-exceptional-name + description: alerts-mood-exceptional-desc + +- type: alert + id: Perfect + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood9 + name: alerts-mood-perfect-name + description: alerts-mood-perfect-desc + +- type: alert + id: MoodDead + category: Mood + onClick: !type:ShowMoodEffects { } + icons: + - sprite: /Textures/Interface/Alerts/mood.rsi + state: mood_happiness_bad + name: alerts-mood-dead-name + description: alerts-mood-dead-desc diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml index 2dad0fe2e65..44fad43bc22 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/familiars.yml @@ -81,10 +81,11 @@ - type: ZombieImmune # No zombie servant - type: RandomMetadata nameSegments: [names_golem] - - type: PotentialPsionic - type: Psionic removable: false - # - type: PyrokinesisPower # Pending psionic rework + - type: InnatePsionicPowers + powersToAdd: + - PyrokinesisPower - type: Grammar attributes: proper: true diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml index e3eb9cd6de8..0a39ef6de88 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/NPCs/glimmer_creatures.yml @@ -22,7 +22,6 @@ reagents: - ReagentId: Ectoplasm Quantity: 15 - - type: PotentialPsionic - type: Psionic - type: GlimmerSource - type: AmbientSound diff --git a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml index 718dc3ca559..d5dbb5f7eef 100644 --- a/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml +++ b/Resources/Prototypes/DeltaV/Entities/Mobs/Player/vulpkanin.yml @@ -19,7 +19,6 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic - type: Respirator damage: types: diff --git a/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml b/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml index 2a879472e8c..0ba8f0bb7cc 100644 --- a/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml +++ b/Resources/Prototypes/DeltaV/Roles/Jobs/Justice/chief_justice.yml @@ -35,10 +35,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: CJGear diff --git a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml index 0b09e0e4c9f..712dfcf3a06 100644 --- a/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml +++ b/Resources/Prototypes/Entities/Markers/Spawners/ghost_roles.yml @@ -90,7 +90,8 @@ - !type:DepartmentTimeRequirement # DeltaV - Security dept time requirement department: Security time: 36000 # DeltaV - 10 hours - - type: GhostRoleAntagSpawner + - type: GhostRoleMobSpawner + prototype: MobHumanLoneNuclearOperative - type: Sprite sprite: Markers/jobs.rsi layers: diff --git a/Resources/Prototypes/Entities/Mobs/Customization/Markings/oni_horns.yml b/Resources/Prototypes/Entities/Mobs/Customization/Markings/oni_horns.yml index b7af09b6e72..88d4ee493db 100644 --- a/Resources/Prototypes/Entities/Mobs/Customization/Markings/oni_horns.yml +++ b/Resources/Prototypes/Entities/Mobs/Customization/Markings/oni_horns.yml @@ -51,3 +51,475 @@ sprites: - sprite: Mobs/Customization/Oni/oni_horns.rsi state: crowned + +- type: marking + id: OniHornSerket + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: serket + +- type: marking + id: OniHornSerket3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: serket_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: serket_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: serket_3tone_3 + +- type: marking + id: OniHornPisces + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: pisces + +- type: marking + id: OniHornPisces2Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: pisces_2tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: pisces_2tone_2 + +- type: marking + id: OniHornPisces3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: pisces_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: pisces_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: pisces_3tone_3 + +- type: marking + id: OniHornVirgo + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: virgo + +- type: marking + id: OniHornVirgo3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: virgo_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: virgo_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: virgo_3tone_3 + +- type: marking + id: OniHornSagittarius + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: sagittarius + +- type: marking + id: OniHornSagittarius3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: sagittarius_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: sagittarius_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: sagittarius_3tone_3 + +- type: marking + id: OniHornVantas + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: vantas + +- type: marking + id: OniHornVantas3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: vantas_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: vantas_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: vantas_3tone_3 + +- type: marking + id: OniHornMakara + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns48x48.rsi + state: makara + +- type: marking + id: OniHornMakara2Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns48x48.rsi + state: makara_2tone_1 + - sprite: Mobs/Customization/Oni/oni_horns48x48.rsi + state: makara_2tone_2 + +- type: marking + id: OniHornMakara3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns48x48.rsi + state: makara_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns48x48.rsi + state: makara_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns48x48.rsi + state: makara_3tone_3 + +- type: marking + id: OniHornNepeta + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: nepeta + +- type: marking + id: OniHornNepeta3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: nepeta_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: nepeta_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: nepeta_3tone_3 + +- type: marking + id: OniHornTaurus + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: taurus + +- type: marking + id: OniHornTaurus2Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: taurus_2tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: taurus_2tone_2 + +- type: marking + id: OniHornTaurus3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: taurus_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: taurus_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: taurus_3tone_3 + +- type: marking + id: OniHornAries + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: aries + +- type: marking + id: OniHornAries3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: aries_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: aries_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: aries_3tone_3 + +- type: marking + id: OniHornTavris + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: tavris + +- type: marking + id: OniHornTavris3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: tavris_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: tavris_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: tavris_3tone_3 + +- type: marking + id: OniHornInclined + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: inclined + +- type: marking + id: OniHornInclined3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: inclined_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: inclined_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: inclined_3tone_3 + +- type: marking + id: OniHornWavy + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: wavy + +- type: marking + id: OniHornWavy2Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: wavy_2tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: wavy_2tone_2 + +- type: marking + id: OniHornWavy3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: wavy_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: wavy_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: wavy_3tone_3 + +- type: marking + id: OniHornAntlers + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: antlers_2tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: antlers_2tone_2 + +- type: marking + id: OniHornAntlers3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: antlers_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: antlers_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: antlers_3tone_3 + +- type: marking + id: OniHornUnicorn + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: unicorn + +- type: marking + id: OniHornErebia + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: erebia + +- type: marking + id: OniHornErebia3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: erebia_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: erebia_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: erebia_3tone_3 + +- type: marking + id: OniHornErebiaRings + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: erebia + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: erebia_rings + +- type: marking + id: OniHornDoubleThick + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_thick + +- type: marking + id: OniHornDoubleThick2Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_thick_2tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_thick_2tone_2 + +- type: marking + id: OniHornDoubleThick3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_thick_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_thick_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_thick_3tone_3 + +- type: marking + id: OniHornDoubleCurved3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_curved_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_curved_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_curved_3tone_3 + +- type: marking + id: OniHornDoubleCurvedOutwards3Tone + bodyPart: HeadTop + markingCategory: HeadTop + forcedColoring: false + speciesRestriction: [Oni] + sprites: + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_curved_outwards_3tone_1 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_curved_outwards_3tone_2 + - sprite: Mobs/Customization/Oni/oni_horns.rsi + state: double_curved_outwards_3tone_3 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index d1b3bd6a6a9..cf563989bf5 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -118,7 +118,6 @@ - type: Grammar attributes: gender: male - - type: PotentialPsionic # Nyano - type: LanguageKnowledge speaks: - GalacticCommon diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 397989643e6..d618e407134 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -121,8 +121,6 @@ molsPerSecondPerUnitMass: 0.0005 - type: Speech speechVerb: LargeMob - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. - chance: -2 - type: Psionic #Nyano - Summary: makes psionic by default. removable: false - type: LanguageKnowledge diff --git a/Resources/Prototypes/Entities/Mobs/Player/arachne.yml b/Resources/Prototypes/Entities/Mobs/Player/arachne.yml index cd4123fa80d..209ad3e93ed 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/arachne.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/arachne.yml @@ -27,4 +27,3 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic diff --git a/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml b/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml index 5ebd43ddf48..d9dea3c18d9 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/arachnid.yml @@ -11,4 +11,3 @@ damageRecovery: types: Asphyxiation: -0.5 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. diff --git a/Resources/Prototypes/Entities/Mobs/Player/diona.yml b/Resources/Prototypes/Entities/Mobs/Player/diona.yml index 28687c68bfc..dfd5e9a1be7 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/diona.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/diona.yml @@ -11,7 +11,6 @@ damageRecovery: types: Asphyxiation: -1.0 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. # Reformed Diona - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml index fb84ad3650f..f8f0ddd2b9d 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dwarf.yml @@ -2,6 +2,4 @@ save: false name: Urist McHands The Dwarf parent: BaseMobDwarf - id: MobDwarf - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file + id: MobDwarf \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/harpy.yml b/Resources/Prototypes/Entities/Mobs/Player/harpy.yml index fa6aa98d93c..12df7ff1037 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/harpy.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/harpy.yml @@ -21,4 +21,3 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic diff --git a/Resources/Prototypes/Entities/Mobs/Player/human.yml b/Resources/Prototypes/Entities/Mobs/Player/human.yml index 6197c82c021..aa87f81a833 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/human.yml @@ -3,8 +3,6 @@ name: Urist McHands parent: BaseMobHuman id: MobHuman - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. #Syndie - type: entity @@ -48,9 +46,8 @@ components: - type: NukeOperative - type: RandomHumanoidAppearance - - type: PsionicBonusChance #Nyano - Summary: makes more likely to be psionic. - multiplier: 7 - warn: false + - type: Psionic + powerRollMultiplier: 7 - type: entity noSpawn: true @@ -70,9 +67,8 @@ - type: NpcFactionMember factions: - Syndicate - - type: PsionicBonusChance #Nyano - Summary: makes more likely to be psionic. - multiplier: 7 - warn: false + - type: Psionic + powerRollMultiplier: 7 # Space Ninja - type: entity diff --git a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml index 9a8ca5be00f..956e6f1260c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/humanoid.yml @@ -528,9 +528,8 @@ id: NukeOp components: - type: NukeOperative - - type: PsionicBonusChance #Nyano - Summary: makes more likely to be psionic. - multiplier: 7 - warn: false + - type: Psionic + powerRollMultiplier: 7 - type: entity id: RandomHumanoidSpawnerCluwne diff --git a/Resources/Prototypes/Entities/Mobs/Player/moth.yml b/Resources/Prototypes/Entities/Mobs/Player/moth.yml index ffdb36d86bd..e79ba1a454f 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/moth.yml @@ -1,7 +1,5 @@ - type: entity save: false name: Urist McFluff - parent: BaseMobMoth - id: MobMoth - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file + parent: BaseMobMoth + id: MobMoth \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml index 71d74222979..b9f265e0bcf 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/reptilian.yml @@ -3,7 +3,5 @@ name: Urisst' Mzhand parent: BaseMobReptilian id: MobReptilian - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. #Weh diff --git a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml index d2a3225c070..5c2a88c1064 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/skeleton.yml @@ -2,8 +2,6 @@ save: false parent: BaseMobSkeletonPerson id: MobSkeletonPerson - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. - type: entity name: skeleton pirate diff --git a/Resources/Prototypes/Entities/Mobs/Player/slime.yml b/Resources/Prototypes/Entities/Mobs/Player/slime.yml index 79669a8fe2a..d748ff8f3cb 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/slime.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/slime.yml @@ -1,6 +1,4 @@ - type: entity save: false parent: BaseMobSlimePerson - id: MobSlimePerson - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. \ No newline at end of file + id: MobSlimePerson \ No newline at end of file diff --git a/Resources/Prototypes/Entities/Mobs/Player/vox.yml b/Resources/Prototypes/Entities/Mobs/Player/vox.yml index 0a6f4f43644..de1e3da2be7 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/vox.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/vox.yml @@ -3,5 +3,3 @@ name: Vox parent: BaseMobVox id: MobVox - components: - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index d812433b44a..940e3ebccb0 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -104,6 +104,7 @@ layer: - MobLayer - type: FloorOcclusion + - type: Mood - type: RangedDamageSound soundGroups: Brute: diff --git a/Resources/Prototypes/Entities/Mobs/Species/human.yml b/Resources/Prototypes/Entities/Mobs/Species/human.yml index e00e06279e5..ac373725ce4 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/human.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/human.yml @@ -16,7 +16,6 @@ spawned: - id: FoodMeatHuman amount: 5 - - type: PotentialPsionic #Nyano - Summary: makes potentially psionic. - type: LanguageKnowledge speaks: - GalacticCommon diff --git a/Resources/Prototypes/GameRules/events.yml b/Resources/Prototypes/GameRules/events.yml index 284ed006525..ca885117449 100644 --- a/Resources/Prototypes/GameRules/events.yml +++ b/Resources/Prototypes/GameRules/events.yml @@ -441,28 +441,11 @@ weight: 2 duration: 1 - type: ZombieRule - - 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 + minStartDelay: 0 #let them know immediately + maxStartDelay: 10 + maxInitialInfected: 2 + minInitialInfectedGrace: 300 + maxInitialInfectedGrace: 450 - type: entity id: LoneOpsSpawn @@ -475,29 +458,7 @@ minimumPlayers: 15 reoccurrenceDelay: 45 duration: 1 - - 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: LoneOpsSpawnRule - type: entity id: MassHallucinations diff --git a/Resources/Prototypes/GameRules/midround.yml b/Resources/Prototypes/GameRules/midround.yml index bb870f6007e..37fc4b44cde 100644 --- a/Resources/Prototypes/GameRules/midround.yml +++ b/Resources/Prototypes/GameRules/midround.yml @@ -34,23 +34,6 @@ 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 46513376187..0af55a7f9d0 100644 --- a/Resources/Prototypes/GameRules/roundstart.yml +++ b/Resources/Prototypes/GameRules/roundstart.yml @@ -70,114 +70,31 @@ components: - type: GameRule minPlayers: 35 - - type: RandomMetadata #this generates the random operation name cuz it's cool. - nameSegments: - - operationPrefix - - operationSuffix - type: NukeopsRule - - 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 + faction: Syndicate + +- type: entity + id: Pirates + parent: BaseGameRule + noSpawn: true + components: + - type: PiratesRule - 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 - - 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 + maxHeadRevs: 2 # DeltaV + playersPerHeadRev: 30 # DeltaV - need highpop readied up for multiple headrevs - type: entity id: Sandbox @@ -198,32 +115,7 @@ 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 @@ -262,6 +154,7 @@ - id: BasicTrashVariationPass - id: SolidWallRustingVariationPass - id: ReinforcedWallRustingVariationPass + - id: CutWireVariationPass - id: BasicPuddleMessVariationPass prob: 0.99 orGroup: puddleMess diff --git a/Resources/Prototypes/Loadouts/Jobs/service.yml b/Resources/Prototypes/Loadouts/Jobs/service.yml index 4e41f035d7b..74a7364e9a4 100644 --- a/Resources/Prototypes/Loadouts/Jobs/service.yml +++ b/Resources/Prototypes/Loadouts/Jobs/service.yml @@ -92,6 +92,17 @@ items: - BedsheetClown +- type: loadout + id: LoadoutServiceClownCowToolboxFilled + category: Jobs + cost: 3 + requirements: + - !type:CharacterJobRequirement + jobs: + - Clown + items: + - CowToolboxFilled + # Mime - type: loadout id: LoadoutServiceMimeOuterWinter diff --git a/Resources/Prototypes/Loadouts/items.yml b/Resources/Prototypes/Loadouts/items.yml index 87ba2e71d42..5d42e9bb750 100644 --- a/Resources/Prototypes/Loadouts/items.yml +++ b/Resources/Prototypes/Loadouts/items.yml @@ -76,6 +76,21 @@ items: - SmokingPipeFilledTobacco +- type: loadout + id: LoadoutItemBlunt + category: Items + cost: 2 + items: + - Blunt + +- type: loadout + id: LoadoutItemJoint + category: Items + cost: 2 + items: + - Joint + + # Instruments - type: loadout id: LoadoutItemMicrophoneInstrument @@ -279,6 +294,13 @@ items: - LunchboxGenericFilledRandom +- type: loadout + id: LoadoutItemDrinkMREFlask + category: Items + cost: 2 + items: + - DrinkMREFlask + # Survival boxes - type: loadout id: LoadoutItemBoxSurvival @@ -540,3 +562,38 @@ cost: 3 items: - HandLabeler + +- type: loadout + id: LoadoutItemHandheldStationMap + category: Items + cost: 2 + items: + - HandheldStationMap + +- type: loadout + id: LoadoutItemLantern + category: Items + cost: 2 + items: + - Lantern + +- type: loadout + id: LoadoutItemDrinkShinyFlask + category: Items + cost: 1 + items: + - DrinkShinyFlask + +- type: loadout + id: LoadoutItemDrinkLithiumFlask + category: Items + cost: 1 + items: + - DrinkLithiumFlask + +- type: loadout + id: LoadoutItemDrinkVacuumFlask + category: Items + cost: 1 + items: + - DrinkVacuumFlask diff --git a/Resources/Prototypes/Mood/categories.yml b/Resources/Prototypes/Mood/categories.yml new file mode 100644 index 00000000000..8c03338ca8b --- /dev/null +++ b/Resources/Prototypes/Mood/categories.yml @@ -0,0 +1,9 @@ +# Alphabetically Ordered +- type: moodCategory + id: Health + +- type: moodCategory + id: Hunger + +- type: moodCategory + id: Thirst \ No newline at end of file diff --git a/Resources/Prototypes/Mood/genericNeeds.yml b/Resources/Prototypes/Mood/genericNeeds.yml new file mode 100644 index 00000000000..d0b24b7d7fe --- /dev/null +++ b/Resources/Prototypes/Mood/genericNeeds.yml @@ -0,0 +1,63 @@ +# Hunger +- type: moodEffect + id: HungerOverfed + moodChange: -10 + category: "Hunger" + +- type: moodEffect + id: HungerOkay + moodChange: 7 + category: "Hunger" + +- type: moodEffect + id: HungerPeckish + moodChange: -3 + category: "Hunger" + +- type: moodEffect + id: HungerStarving + moodChange: -7 + category: "Hunger" + +# Thirst +- type: moodEffect + id: ThirstOverHydrated + moodChange: -3 + category: "Thirst" + +- type: moodEffect + id: ThirstOkay + moodChange: 7 + category: "Thirst" + +- type: moodEffect + id: ThirstThirsty + moodChange: -3 + category: "Thirst" + +- type: moodEffect + id: ThirstParched + moodChange: -7 + category: "Thirst" + +# Health +- type: moodEffect + id: HealthNoDamage + moodChange: 0 + hidden: true + category: "Health" + +- type: moodEffect + id: HealthLightDamage + moodChange: -3 + category: "Health" + +- type: moodEffect + id: HealthSevereDamage + moodChange: -7 + category: "Health" + +- type: moodEffect + id: HealthHeavyDamage + moodChange: -20 + category: "Health" diff --git a/Resources/Prototypes/Mood/genericNegativeEffects.yml b/Resources/Prototypes/Mood/genericNegativeEffects.yml new file mode 100644 index 00000000000..0e1014d9074 --- /dev/null +++ b/Resources/Prototypes/Mood/genericNegativeEffects.yml @@ -0,0 +1,45 @@ +- type: moodEffect + id: Handcuffed + moodChange: -3 + +- type: moodEffect + id: Suffocating + moodChange: -7 + timeout: 6 + +- type: moodEffect + id: OnFire + moodChange: -10 + timeout: 600 + +- type: moodEffect + id: Creampied + moodChange: -3 + +- type: moodEffect + id: MobSlipped + moodChange: -3 + timeout: 180 + +- type: moodEffect + id: MobVomit + moodChange: -3 + timeout: 480 + +- type: moodEffect + id: MobLowPressure + moodChange: -7 + timeout: 10 + +- type: moodEffect + id: MobHighPressure + moodChange: -7 + timeout: 10 + +- type: moodEffect + id: TraitSaturnine + moodChange: -20 + +- type: moodEffect + id: Dead + moodChange: -1000 diff --git a/Resources/Prototypes/Mood/genericPositiveEffects.yml b/Resources/Prototypes/Mood/genericPositiveEffects.yml new file mode 100644 index 00000000000..8ac5b25dc12 --- /dev/null +++ b/Resources/Prototypes/Mood/genericPositiveEffects.yml @@ -0,0 +1,40 @@ +- type: moodEffect + id: BeingHugged + moodChange: 3 + timeout: 120 + +- type: moodEffect + id: ArcadePlay + moodChange: 3 + timeout: 480 + +- type: moodEffect + id: GotBlessed + moodChange: 3 + timeout: 480 + +- type: moodEffect + id: PetAnimal + moodChange: 3 + timeout: 300 + +- type: moodEffect + id: SavedLife + moodChange: 7 + timeout: 480 + +- type: moodEffect + id: TraitorFocused # Used for traitors to boost their goals completion. + moodChange: 7 + +- type: moodEffect + id: RevolutionFocused # Used for revolution + moodChange: 7 + +- type: moodEffect + id: CultFocused + moodChange: 10 + +- type: moodEffect + id: TraitSanguine + moodChange: 15 \ No newline at end of file diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml index 462b3254f1e..996c0d87ab1 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/NPCs/mutants.yml @@ -136,10 +136,12 @@ bloodReagent: DemonsBlood - type: Body prototype: VampiricAnimalLarge - - type: PotentialPsionic - type: Psionic removable: false - - type: MetapsionicPower + - type: InnatePsionicPowers + powersToAdd: + - MetapsionicPower + - PsionicInvisibilityPower - type: AntiPsionicWeapon punish: false modifiers: diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml index e1c867691a1..8e9e2c62dfd 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/Oni.yml @@ -27,4 +27,3 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml index 9b79c556707..18047aa2e20 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/felinid.yml @@ -44,5 +44,4 @@ - type: NpcFactionMember factions: - NanoTrasen - - type: PotentialPsionic diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml index abe2833e890..70628ec4e51 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Mobs/Player/special.yml @@ -42,6 +42,7 @@ visMask: - Normal - TelegnosticProjection + - PsionicInvisibility - type: Input context: "ghost" - type: Examiner diff --git a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml index ae85cd25e03..24aa3eba08f 100644 --- a/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml +++ b/Resources/Prototypes/Nyanotrasen/Entities/Structures/Research/sophicscribe.yml @@ -27,7 +27,6 @@ channels: - Common - Science - - type: PotentialPsionic #this makes her easier to access for glimmer events, dw about it - type: Psionic - type: Grammar attributes: diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml index e2f99548429..2617f782af8 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Epistemics/forensicmantis.yml @@ -23,7 +23,11 @@ - !type:AddComponentSpecial components: - type: Psionic - - type: MetapsionicPower + - !type:AddComponentSpecial + components: + - type: InnatePsionicPowers + powersToAdd: + - MetapsionicPower - type: startingGear id: ForensicMantisGear diff --git a/Resources/Prototypes/Psionics/psionics.yml b/Resources/Prototypes/Psionics/psionics.yml new file mode 100644 index 00000000000..d4ae73669f9 --- /dev/null +++ b/Resources/Prototypes/Psionics/psionics.yml @@ -0,0 +1,112 @@ +- type: psionicPower + id: DispelPower + name: Dispel + description: dispel-power-description + actions: + - ActionDispel + components: + - type: DispelPower + initializationFeedback: dispel-power-initialization-feedback + metapsionicFeedback: dispel-power-metapsionic-feedback + dampeningModifier: 1 + +- type: psionicPower + id: MassSleepPower + name: Mass Sleep + description: mass-sleep-power-description + actions: + - ActionMassSleep + components: + - type: MassSleepPower + initializationFeedback: mass-sleep-power-initialization-feedback + metapsionicFeedback: mass-sleep-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: MindSwapPower + name: Mind Swap + description: mind-swap-power-description + actions: + - ActionMindSwap + components: + - type: MindSwapPower + initializationFeedback: mind-swap-power-initialization-feedback + metapsionicFeedback: mind-swap-power-metapsionic-feedback + amplificationModifier: 1 + +- type: psionicPower + id: NoosphericZapPower + name: Noospheric Zap + description: noospheric-zap-power-description + actions: + - ActionNoosphericZap + components: + - type: NoosphericZapPower + initializationFeedback: noospheric-zap-power-initialization-feedback + metapsionicFeedback: noospheric-zap-power-metapsionic-feedback + amplificationModifier: 1 + +- type: psionicPower + id: PyrokinesisPower + name: Pyrokinesis + description: pyrokinesis-power-description + actions: + - ActionPyrokinesis + components: + - type: PyrokinesisPower + initializationFeedback: pyrokinesis-power-initialization-feedback + metapsionicFeedback: pyrokinesis-power-metapsionic-feedback + amplificationModifier: 1 + +- type: psionicPower + id: MetapsionicPower + name: Metapsionic Pulse + description: metapsionic-power-description + actions: + - ActionMetapsionic + components: + - type: MetapsionicPower + initializationFeedback: metapsionic-power-initialization-feedback + metapsionicFeedback: metapsionic-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: PsionicRegenerationPower + name: Psionic Regeneration + description: psionic-regeneration-power-description + actions: + - ActionPsionicRegeneration + components: + - type: PsionicRegenerationPower + initializationFeedback: psionic-regeneration-power-initialization-feedback + metapsionicFeedback: psionic-regeneration-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: TelegnosisPower + name: Telegnosis + description: telegnosis-power-description + actions: + - ActionTelegnosis + components: + - type: TelegnosisPower + initializationFeedback: telegnosis-power-initialization-feedback + metapsionicFeedback: telegnosis-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 + +- type: psionicPower + id: PsionicInvisibilityPower + name: Psionic Invisibility + description: psionic-invisibility-power-description + actions: + - ActionDispel + components: + - type: PsionicInvisibilityPower + initializationFeedback: psionic-invisibility-power-initialization-feedback + metapsionicFeedback: psionic-invisibility-power-metapsionic-feedback + amplificationModifier: 0.5 + dampeningModifier: 0.5 \ No newline at end of file diff --git a/Resources/Prototypes/Roles/Antags/nukeops.yml b/Resources/Prototypes/Roles/Antags/nukeops.yml index b02ebe67539..26024b44b28 100644 --- a/Resources/Prototypes/Roles/Antags/nukeops.yml +++ b/Resources/Prototypes/Roles/Antags/nukeops.yml @@ -40,23 +40,3 @@ 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/Civilian/chaplain.yml b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml index 9b4f5ea1487..a058c24b848 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/chaplain.yml @@ -18,10 +18,8 @@ - !type:AddComponentSpecial components: - type: BibleUser #Lets them heal with bibles - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - multiplier: 3 + - type: Psionic + powerRollMultiplier: 3 - type: startingGear id: ChaplainGear diff --git a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml index 3e04285d601..c7c659bc531 100644 --- a/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml +++ b/Resources/Prototypes/Roles/Jobs/Civilian/mime.yml @@ -16,7 +16,6 @@ special: - !type:AddComponentSpecial components: - - type: Psionic # Nyano - Summary: Makes the mime psionic. - type: MimePowers - type: FrenchAccent diff --git a/Resources/Prototypes/Roles/Jobs/Command/captain.yml b/Resources/Prototypes/Roles/Jobs/Command/captain.yml index d624b349d56..219684cc7d7 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/captain.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/captain.yml @@ -40,10 +40,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: CaptainGear diff --git a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml index 6311eb9fee6..878b184b8bc 100644 --- a/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml +++ b/Resources/Prototypes/Roles/Jobs/Command/head_of_personnel.yml @@ -66,10 +66,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: HoPGear diff --git a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml index eaa66d6f0cc..2690b9ba016 100644 --- a/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml +++ b/Resources/Prototypes/Roles/Jobs/Engineering/chief_engineer.yml @@ -35,10 +35,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: ChiefEngineerGear diff --git a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml index b2bcd8bcb49..434a7c1083e 100644 --- a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml +++ b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml @@ -293,18 +293,8 @@ #Head Rev Gear - type: startingGear id: HeadRevGear - storage: - back: - - Flash - - ClothingEyesGlassesSunglasses - -#Thief Gear -- type: startingGear - id: ThiefGear - storage: - back: - - ToolboxThief - - ClothingHandsChameleonThief + equipment: + pocket2: Flash #Gladiator with spear - type: startingGear diff --git a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml index 61b1df7784f..b132729432c 100644 --- a/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml +++ b/Resources/Prototypes/Roles/Jobs/Medical/chief_medical_officer.yml @@ -42,8 +42,6 @@ - type: CommandStaff - !type:AddComponentSpecial components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: CPRTraining - type: startingGear diff --git a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml index 747ee41b840..1697cad6748 100644 --- a/Resources/Prototypes/Roles/Jobs/Science/research_director.yml +++ b/Resources/Prototypes/Roles/Jobs/Science/research_director.yml @@ -29,12 +29,15 @@ components: - type: BibleUser # Nyano - Lets them heal with bibles - type: Psionic # Nyano - They start with telepathic chat - - type: DispelPower # Nyano - They get the Dispel psionic power on spawn - !type:AddImplantSpecial implants: [ MindShieldImplant ] - !type:AddComponentSpecial components: - type: CommandStaff + - type: InnatePsionicPowers + powersToAdd: + - DispelPower + - MetapsionicPower - type: startingGear id: ResearchDirectorGear diff --git a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml index 46e018f35a8..f1578534557 100644 --- a/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml +++ b/Resources/Prototypes/Roles/Jobs/Security/head_of_security.yml @@ -40,10 +40,6 @@ - !type:AddComponentSpecial components: - type: CommandStaff - - !type:AddComponentSpecial - components: - - type: PsionicBonusChance #Nyano - Summary: makes it more likely to become psionic. - flatBonus: 0.025 - type: startingGear id: HoSGear diff --git a/Resources/Prototypes/Shaders/shaders.yml b/Resources/Prototypes/Shaders/shaders.yml index e286dcb7a41..b495490201c 100644 --- a/Resources/Prototypes/Shaders/shaders.yml +++ b/Resources/Prototypes/Shaders/shaders.yml @@ -99,3 +99,8 @@ id: Cataracts kind: source path: "/Textures/Shaders/cataracts.swsl" + +- type: shader + id: SaturationScale + kind: source + path: "/Textures/Shaders/saturationscale.swsl" diff --git a/Resources/Prototypes/Traits/neutral.yml b/Resources/Prototypes/Traits/neutral.yml index ab5bcb238d7..16e70a2cc63 100644 --- a/Resources/Prototypes/Traits/neutral.yml +++ b/Resources/Prototypes/Traits/neutral.yml @@ -38,3 +38,39 @@ - Vulpkanin components: - type: NormalVision + +- type: trait + id: Saturnine + category: Mental + points: 3 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + - !type:CharacterTraitRequirement + inverted: true + traits: + - Sanguine + components: + - type: MoodModifyTrait + moodId: TraitSaturnine + +- type: trait + id: Sanguine + category: Mental + points: -3 + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + - !type:CharacterTraitRequirement + inverted: true + traits: + - Saturnine + components: + - type: MoodModifyTrait + moodId: TraitSanguine \ No newline at end of file diff --git a/Resources/Prototypes/Traits/skills.yml b/Resources/Prototypes/Traits/skills.yml index 7856a68ab34..61399745547 100644 --- a/Resources/Prototypes/Traits/skills.yml +++ b/Resources/Prototypes/Traits/skills.yml @@ -168,3 +168,19 @@ components: - type: Singer proto: NormalSinger + +- type: trait + id: LatentPsychic + category: Mental + points: -3 + components: + - type: Psionic + requirements: + - !type:CharacterJobRequirement + inverted: true + jobs: + - Borg + - MedicalBorg + - ResearchDirector + - ForensicMantis + - Chaplain diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml index 7d9c9786bb5..7d7169bf10a 100644 --- a/Resources/Prototypes/game_presets.yml +++ b/Resources/Prototypes/game_presets.yml @@ -164,3 +164,15 @@ - Zombie - BasicStationEventScheduler - BasicRoundstartVariation + +- type: gamePreset + id: Pirates + alias: + - pirates + name: pirates-title + description: pirates-description + showInVote: false + rules: + - Pirates + - BasicStationEventScheduler + - BasicRoundstartVariation diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/meta.json b/Resources/Textures/Interface/Alerts/mood.rsi/meta.json new file mode 100644 index 00000000000..0f6726a48d0 --- /dev/null +++ b/Resources/Textures/Interface/Alerts/mood.rsi/meta.json @@ -0,0 +1,60 @@ +{ + "version": 1, + "license": "CC-BY-SA-3.0", + "copyright": "Taken from NSV13 at b6b1e2bf2cc60455851317d8e82cca8716d9dac1", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "mood1" + }, + { + "name": "mood2" + }, + { + "name": "mood3" + }, + { + "name": "mood4" + }, + { + "name": "mood5" + }, + { + "name": "mood6" + }, + { + "name": "mood7" + }, + { + "name": "mood8" + }, + { + "name": "mood9" + }, + { + "name": "mood_happiness_bad" + }, + { + "name": "mood_happiness_good" + }, + { + "name": "mood_insane", + "delays": [ + [ + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07, + 0.07 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png new file mode 100644 index 00000000000..ae1e1386db4 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood1.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png new file mode 100644 index 00000000000..41be928f025 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood2.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png new file mode 100644 index 00000000000..179991e1984 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood3.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png new file mode 100644 index 00000000000..4ea7d701171 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood4.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png new file mode 100644 index 00000000000..c4c3370f625 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood5.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png new file mode 100644 index 00000000000..388483e1438 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood6.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png new file mode 100644 index 00000000000..b4f944d045e Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood7.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png new file mode 100644 index 00000000000..f12f71b7ffa Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood8.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png new file mode 100644 index 00000000000..e65c6501495 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood9.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png new file mode 100644 index 00000000000..4ed8f4d68f8 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_bad.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png new file mode 100644 index 00000000000..eb9943a3015 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_happiness_good.png differ diff --git a/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png b/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png new file mode 100644 index 00000000000..b2407bbdad8 Binary files /dev/null and b/Resources/Textures/Interface/Alerts/mood.rsi/mood_insane.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_2tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_2tone_1.png new file mode 100644 index 00000000000..a6241e92d2d Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_2tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_2tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_2tone_2.png new file mode 100644 index 00000000000..36cdefd238c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_2tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_1.png new file mode 100644 index 00000000000..05dd0eb54af Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_2.png new file mode 100644 index 00000000000..0b80c2dfedf Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_3.png new file mode 100644 index 00000000000..3c35f7720cb Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/antlers_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries.png new file mode 100644 index 00000000000..a18dff8b1ed Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_1.png new file mode 100644 index 00000000000..af46b769e81 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_2.png new file mode 100644 index 00000000000..2e3131634ea Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_3.png new file mode 100644 index 00000000000..1a6a2deee4b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/aries_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_1.png new file mode 100644 index 00000000000..bee9c5b581e Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_2.png new file mode 100644 index 00000000000..574c2419331 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_3.png new file mode 100644 index 00000000000..0b041de7a6b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_1.png new file mode 100644 index 00000000000..95bb26cacfc Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_2.png new file mode 100644 index 00000000000..652beba012b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_3.png new file mode 100644 index 00000000000..c3d342bb552 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_curved_outwards_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick.png new file mode 100644 index 00000000000..55749ac8747 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_2tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_2tone_1.png new file mode 100644 index 00000000000..bc13cf5b61c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_2tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_2tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_2tone_2.png new file mode 100644 index 00000000000..97c251cbc6e Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_2tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_1.png new file mode 100644 index 00000000000..49bd7c55fb5 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_2.png new file mode 100644 index 00000000000..21a856102f9 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_3.png new file mode 100644 index 00000000000..91180efbcc4 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/double_thick_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia.png new file mode 100644 index 00000000000..ad78a14ade9 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_1.png new file mode 100644 index 00000000000..fa8a079b3ce Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_2.png new file mode 100644 index 00000000000..bd601509cf7 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_3.png new file mode 100644 index 00000000000..b6fcc64aa1b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_rings.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_rings.png new file mode 100644 index 00000000000..7f6126db6aa Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/erebia_rings.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined.png new file mode 100644 index 00000000000..e034dc0cd29 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_1.png new file mode 100644 index 00000000000..42d9b8416df Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_2.png new file mode 100644 index 00000000000..d0064183729 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_3.png new file mode 100644 index 00000000000..41d85c81ad6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/inclined_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/meta.json b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/meta.json index 92b16828314..d68c9cb5acb 100644 --- a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "crowned by dootythefrooty", + "copyright": "crowned by dootythefrooty, serket/pisces/virgo/sagittarius/vantas/nepeta/taurus/aries/tavris/inclined/wavy/antlers/unicorn/erebia/double_thick by angelofallars (github), double_curved/double_curved_outwards made by Rane modified by angelofallars (github)", "size": { "x": 32, "y": 32 @@ -10,6 +10,298 @@ { "name": "crowned", "directions": 4 + }, + { + "name": "serket", + "directions": 4 + }, + { + "name": "serket_3tone_1", + "directions": 4 + }, + { + "name": "serket_3tone_2", + "directions": 4 + }, + { + "name": "serket_3tone_3", + "directions": 4 + }, + { + "name": "pisces", + "directions": 4 + }, + { + "name": "pisces_2tone_1", + "directions": 4 + }, + { + "name": "pisces_2tone_2", + "directions": 4 + }, + { + "name": "pisces_3tone_1", + "directions": 4 + }, + { + "name": "pisces_3tone_2", + "directions": 4 + }, + { + "name": "pisces_3tone_3", + "directions": 4 + }, + { + "name": "virgo", + "directions": 4 + }, + { + "name": "virgo_3tone_1", + "directions": 4 + }, + { + "name": "virgo_3tone_2", + "directions": 4 + }, + { + "name": "virgo_3tone_3", + "directions": 4 + }, + { + "name": "sagittarius", + "directions": 4 + }, + { + "name": "sagittarius_3tone_1", + "directions": 4 + }, + { + "name": "sagittarius_3tone_2", + "directions": 4 + }, + { + "name": "sagittarius_3tone_3", + "directions": 4 + }, + { + "name": "vantas", + "directions": 4 + }, + { + "name": "vantas_3tone_1", + "directions": 4 + }, + { + "name": "vantas_3tone_2", + "directions": 4 + }, + { + "name": "vantas_3tone_3", + "directions": 4 + }, + { + "name": "nepeta", + "directions": 4 + }, + { + "name": "nepeta_3tone_1", + "directions": 4 + }, + { + "name": "nepeta_3tone_2", + "directions": 4 + }, + { + "name": "nepeta_3tone_3", + "directions": 4 + }, + { + "name": "taurus", + "directions": 4 + }, + { + "name": "taurus_2tone_1", + "directions": 4 + }, + { + "name": "taurus_2tone_2", + "directions": 4 + }, + { + "name": "taurus_3tone_1", + "directions": 4 + }, + { + "name": "taurus_3tone_2", + "directions": 4 + }, + { + "name": "taurus_3tone_3", + "directions": 4 + }, + { + "name": "aries", + "directions": 4 + }, + { + "name": "aries_3tone_1", + "directions": 4 + }, + { + "name": "aries_3tone_2", + "directions": 4 + }, + { + "name": "aries_3tone_3", + "directions": 4 + }, + { + "name": "tavris", + "directions": 4 + }, + { + "name": "tavris_3tone_1", + "directions": 4 + }, + { + "name": "tavris_3tone_2", + "directions": 4 + }, + { + "name": "tavris_3tone_3", + "directions": 4 + }, + { + "name": "inclined", + "directions": 4 + }, + { + "name": "inclined_3tone_1", + "directions": 4 + }, + { + "name": "inclined_3tone_2", + "directions": 4 + }, + { + "name": "inclined_3tone_3", + "directions": 4 + }, + { + "name": "wavy", + "directions": 4 + }, + { + "name": "wavy_2tone_1", + "directions": 4 + }, + { + "name": "wavy_2tone_2", + "directions": 4 + }, + { + "name": "wavy_3tone_1", + "directions": 4 + }, + { + "name": "wavy_3tone_2", + "directions": 4 + }, + { + "name": "wavy_3tone_3", + "directions": 4 + }, + { + "name": "antlers_2tone_1", + "directions": 4 + }, + { + "name": "antlers_2tone_2", + "directions": 4 + }, + { + "name": "antlers_3tone_1", + "directions": 4 + }, + { + "name": "antlers_3tone_2", + "directions": 4 + }, + { + "name": "antlers_3tone_3", + "directions": 4 + }, + { + "name": "unicorn", + "directions": 4 + }, + { + "name": "erebia", + "directions": 4 + }, + { + "name": "erebia_3tone_1", + "directions": 4 + }, + { + "name": "erebia_3tone_2", + "directions": 4 + }, + { + "name": "erebia_3tone_3", + "directions": 4 + }, + { + "name": "erebia_rings", + "directions": 4 + }, + { + "name": "double_thick", + "directions": 4 + }, + { + "name": "double_thick_2tone_1", + "directions": 4 + }, + { + "name": "double_thick_2tone_2", + "directions": 4 + }, + { + "name": "double_thick_3tone_1", + "directions": 4 + }, + { + "name": "double_thick_3tone_2", + "directions": 4 + }, + { + "name": "double_thick_3tone_3", + "directions": 4 + }, + { + "name": "double_curved_3tone_1", + "directions": 4 + }, + { + "name": "double_curved_3tone_2", + "directions": 4 + }, + { + "name": "double_curved_3tone_3", + "directions": 4 + }, + { + "name": "double_curved_outwards_3tone_1", + "directions": 4 + }, + { + "name": "double_curved_outwards_3tone_2", + "directions": 4 + }, + { + "name": "double_curved_outwards_3tone_3", + "directions": 4 } ] } diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta.png new file mode 100644 index 00000000000..91d81f87a97 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_1.png new file mode 100644 index 00000000000..853ffb71d18 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_2.png new file mode 100644 index 00000000000..cc826595b41 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_3.png new file mode 100644 index 00000000000..4b1bdcb9a94 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/nepeta_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces.png new file mode 100644 index 00000000000..ddf6c337c8b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_2tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_2tone_1.png new file mode 100644 index 00000000000..33988ad80fb Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_2tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_2tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_2tone_2.png new file mode 100644 index 00000000000..6f1d022df0a Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_2tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_1.png new file mode 100644 index 00000000000..49bd7c55fb5 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_2.png new file mode 100644 index 00000000000..a8c77f44af9 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_3.png new file mode 100644 index 00000000000..ebece5a5dd3 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/pisces_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius.png new file mode 100644 index 00000000000..077c22535d4 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_1.png new file mode 100644 index 00000000000..ba9dd0d7bbb Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_2.png new file mode 100644 index 00000000000..20d31023640 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_3.png new file mode 100644 index 00000000000..1d9ccc5db29 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/sagittarius_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket.png new file mode 100644 index 00000000000..a027a092645 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_1.png new file mode 100644 index 00000000000..114766c8867 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_2.png new file mode 100644 index 00000000000..0cbfc6c57e0 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_3.png new file mode 100644 index 00000000000..6a998514c69 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/serket_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus.png new file mode 100644 index 00000000000..7df87b9aabb Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_2tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_2tone_1.png new file mode 100644 index 00000000000..53d2a24fff0 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_2tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_2tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_2tone_2.png new file mode 100644 index 00000000000..c614e1a5381 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_2tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_1.png new file mode 100644 index 00000000000..0dafd8e5e49 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_2.png new file mode 100644 index 00000000000..423d306dba2 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_3.png new file mode 100644 index 00000000000..bdca7edf4cb Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/taurus_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris.png new file mode 100644 index 00000000000..2543ce6cf02 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_1.png new file mode 100644 index 00000000000..27930db2027 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_2.png new file mode 100644 index 00000000000..18143a00f4e Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_3.png new file mode 100644 index 00000000000..04e3524befa Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/tavris_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/unicorn.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/unicorn.png new file mode 100644 index 00000000000..61834769015 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/unicorn.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas.png new file mode 100644 index 00000000000..12507d019ea Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_1.png new file mode 100644 index 00000000000..385f516a286 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_2.png new file mode 100644 index 00000000000..d66d97804aa Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_3.png new file mode 100644 index 00000000000..2396cbea6f2 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/vantas_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo.png new file mode 100644 index 00000000000..143525b7cde Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_1.png new file mode 100644 index 00000000000..4e66e19989e Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_2.png new file mode 100644 index 00000000000..009c1830367 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_3.png new file mode 100644 index 00000000000..10ccb2bb110 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/virgo_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy.png new file mode 100644 index 00000000000..7d503ac470b Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_2tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_2tone_1.png new file mode 100644 index 00000000000..0cc88a280fd Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_2tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_2tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_2tone_2.png new file mode 100644 index 00000000000..14b0972d1c6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_2tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_1.png new file mode 100644 index 00000000000..89cc5506f7e Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_2.png new file mode 100644 index 00000000000..4eec76fed93 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_3.png new file mode 100644 index 00000000000..8c48c771a5c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns.rsi/wavy_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara.png new file mode 100644 index 00000000000..b616e392be3 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_2tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_2tone_1.png new file mode 100644 index 00000000000..6d571687713 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_2tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_2tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_2tone_2.png new file mode 100644 index 00000000000..29984a83ced Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_2tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_1.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_1.png new file mode 100644 index 00000000000..5f2299ed3d6 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_1.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_2.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_2.png new file mode 100644 index 00000000000..e2dfa02bb8c Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_2.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_3.png b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_3.png new file mode 100644 index 00000000000..f2e1934d9d4 Binary files /dev/null and b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/makara_3tone_3.png differ diff --git a/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/meta.json b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/meta.json index bf01b0d5209..66b6d33feb6 100644 --- a/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/meta.json +++ b/Resources/Textures/Mobs/Customization/Oni/oni_horns48x48.rsi/meta.json @@ -1,7 +1,7 @@ { "version": 1, "license": "CC-BY-SA-3.0", - "copyright": "tall_curved & tall_bull by dootythefrooty", + "copyright": "tall_curved & tall_bull by dootythefrooty, makara by angelofallars (github)", "size": { "x": 48, "y": 48 @@ -38,6 +38,30 @@ { "name": "tall_bull_3tone_3", "directions": 4 + }, + { + "name": "makara", + "directions": 4 + }, + { + "name": "makara_2tone_1", + "directions": 4 + }, + { + "name": "makara_2tone_2", + "directions": 4 + }, + { + "name": "makara_3tone_1", + "directions": 4 + }, + { + "name": "makara_3tone_2", + "directions": 4 + }, + { + "name": "makara_3tone_3", + "directions": 4 } ] } diff --git a/Resources/Textures/Shaders/saturationscale.swsl b/Resources/Textures/Shaders/saturationscale.swsl new file mode 100644 index 00000000000..9829e207629 --- /dev/null +++ b/Resources/Textures/Shaders/saturationscale.swsl @@ -0,0 +1,12 @@ +uniform highp float saturation; // Between 0 and 2; +uniform sampler2D SCREEN_TEXTURE; + +void fragment() { + highp vec4 color = texture(SCREEN_TEXTURE, UV); + + highp float brightness = (color.r + color.g + color.b) / 3.0; + + color.rgb = mix(vec3(brightness), color.rgb, saturation); + + COLOR = color; +}