diff --git a/Content.Client/_Scp/Scp914/Scp914System.cs b/Content.Client/_Scp/Scp914/Scp914System.cs new file mode 100644 index 00000000000..d11568ba8de --- /dev/null +++ b/Content.Client/_Scp/Scp914/Scp914System.cs @@ -0,0 +1,8 @@ +using Content.Shared._Scp.Scp914; + +namespace Content.Client._Scp.Scp914; + +public sealed class Scp914System : SharedScp914System +{ + +} diff --git a/Content.Client/_Scp/Scp914/Ui/Scp914BoundUserInterface.cs b/Content.Client/_Scp/Scp914/Ui/Scp914BoundUserInterface.cs new file mode 100644 index 00000000000..6745731e57e --- /dev/null +++ b/Content.Client/_Scp/Scp914/Ui/Scp914BoundUserInterface.cs @@ -0,0 +1,74 @@ +using Content.Client.Anomaly.Ui; +using Content.Shared._Scp.Scp914; +using Robust.Shared.Timing; + +namespace Content.Client._Scp.Scp914.Ui; + +public sealed class Scp914BoundUserInterface : BoundUserInterface +{ + private Scp914Window? _window; + private IGameTiming _gameTiming; + + public Scp914BoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey) + { + + _gameTiming = IoCManager.Resolve(); + } + + protected override void Open() + { + base.Open(); + + _window = new Scp914Window(_gameTiming); + _window.OnClose += Close; + _window.OnNewModeSelected += OnNewModeSelected; + _window.OnStartCycle += OnStartCycle; + + if (State != null) + { + UpdateState(State); + } + + _window.OpenCentered(); + } + + private void OnStartCycle() + { + var startCycleMessage = new Scp914StartCycleMessage(); + SendPredictedMessage(startCycleMessage); + } + + private void OnNewModeSelected(Scp914CycleDirection direction) + { + var changeModeMessage = new Scp914ChangeModeRequestMessage(direction); + SendPredictedMessage(changeModeMessage); + } + + protected override void UpdateState(BoundUserInterfaceState state) + { + base.UpdateState(state); + + if (state is not Scp914BuiState newState) + { + return; + } + + _window?.UpdateState(newState); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + if (_window == null) + { + return; + } + + _window.OnClose -= Close; + _window.OnNewModeSelected -= OnNewModeSelected; + + _window.Dispose(); + _window = null; + } +} diff --git a/Content.Client/_Scp/Scp914/Ui/Scp914Window.xaml b/Content.Client/_Scp/Scp914/Ui/Scp914Window.xaml new file mode 100644 index 00000000000..f039882ea79 --- /dev/null +++ b/Content.Client/_Scp/Scp914/Ui/Scp914Window.xaml @@ -0,0 +1,14 @@ + + + + + + diff --git a/Content.Client/_Scp/Scp914/Ui/Scp914Window.xaml.cs b/Content.Client/_Scp/Scp914/Ui/Scp914Window.xaml.cs new file mode 100644 index 00000000000..12ddd9ddd75 --- /dev/null +++ b/Content.Client/_Scp/Scp914/Ui/Scp914Window.xaml.cs @@ -0,0 +1,70 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared._Scp.Scp914; +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface.XAML; +using Robust.Shared.Timing; + +namespace Content.Client._Scp.Scp914.Ui; + +[GenerateTypedNameReferences] +public sealed partial class Scp914Window : FancyWindow +{ + public Action? OnNewModeSelected; + public Action? OnStartCycle; + + private IGameTiming _gameTiming; + + private TimeSpan _nextTimeUse; + private bool _active; + + public Scp914Window(IGameTiming gameTiming) + { + RobustXamlLoader.Load(this); + + _gameTiming = gameTiming; + + TurnLeftButton.OnPressed += _ => OnTurnLeftPressed(); + TurnRightButton.OnPressed += _ => OnTurnRightPressed(); + StartCycleBytton.OnPressed += _ => OnStartCyclePressed(); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + base.FrameUpdate(args); + + var disabled = _active || _gameTiming.CurTime < _nextTimeUse; + + TurnLeftButton.Disabled = disabled; + TurnRightButton.Disabled = disabled; + StartCycleBytton.Disabled = disabled; + } + + public void UpdateState(Scp914BuiState newState) + { + CurrentModeLabel.Text = newState.NewMode.ToString(); + _active = newState.Active; + } + + private void OnStartCyclePressed() + { + OnStartCycle?.Invoke(); + SetNextTimeUse(); + } + + private void OnTurnRightPressed() + { + OnNewModeSelected?.Invoke(Scp914CycleDirection.Right); + SetNextTimeUse(); + } + + private void OnTurnLeftPressed() + { + OnNewModeSelected?.Invoke(Scp914CycleDirection.Left); + SetNextTimeUse(); + } + + private void SetNextTimeUse() + { + _nextTimeUse = _gameTiming.CurTime + TimeSpan.FromSeconds(1); + } +} diff --git a/Content.IntegrationTests/Tests/GameRules/ScpSlGameRuleTest.cs b/Content.IntegrationTests/Tests/GameRules/ScpSlGameRuleTest.cs new file mode 100644 index 00000000000..1f933e0f349 --- /dev/null +++ b/Content.IntegrationTests/Tests/GameRules/ScpSlGameRuleTest.cs @@ -0,0 +1,138 @@ +using System.Linq; +using Content.Server._Scp.GameRules.ScpSl; +using Content.Server._Sunrise.StationCentComm; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Presets; +using Content.Server.GameTicking.Rules.Components; +using Content.Server.RoundEnd; +using Content.Shared._Scp.GameRule.Sl; +using Content.Shared.GameTicking; +using Content.Shared.Pinpointer; +using Content.Shared.Station.Components; +using Robust.Server.GameObjects; +using Robust.Shared.GameObjects; +using Robust.Shared.Map.Components; + +namespace Content.IntegrationTests.Tests.GameRules; + +[TestFixture] +public sealed class ScpSlGameRuleTest +{ + [Test] + public async Task TryStartGameRuleTestWithSufficientPlayers() + { + 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 ticker = server.System(); + var xformSystem = server.System(); + var physicsSystem = server.System(); + + Assert.That(ticker.DummyTicker, Is.False); + + 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)); + + var dummies = await pair.Server.AddDummySessions(30); + + await pair.RunTicksSync(5); + + // Initially, the players have no attached entities + Assert.That(pair.Player?.AttachedEntity, Is.Null); + Assert.That(dummies.All(x => x.AttachedEntity == null)); + + 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); // Sunrise-Edit + + // And no sl related components + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + Assert.That(entMan.Count(), Is.Zero); + + ticker.ToggleReadyAll(true); + Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.ReadyToPlay)); + + await pair.WaitCommand("forcepreset ScpSl"); + await pair.RunTicksSync(10); + + Assert.That(ticker.RunLevel, Is.EqualTo(GameRunLevel.InRound)); + Assert.That(ticker.PlayerGameStatuses.Values.All(x => x == PlayerGameStatus.JoinedGame)); + Assert.That(client.EntMan.EntityExists(client.AttachedEntity)); + + Assert.That(entMan.Count(), Is.EqualTo(1)); + Assert.That(entMan.Count, Is.AtLeast(1)); + Assert.That(entMan.Count, Is.AtLeast(1)); + + + //Try escape + + var (escapeZoneXform, _) = entMan.EntityQuery().First(); + + var chaosBefore = dummies.Count(x => + entMan.TryGetComponent(x.AttachedEntity!.Value!, out var marker) && + marker.HumanoidType == ScpSlHumanoidType.Chaos); + + var dBefore = dummies.Count(x => + entMan.TryGetComponent(x.AttachedEntity!.Value!, out var marker) && + marker.HumanoidType == ScpSlHumanoidType.ClassD); + + var dummyD = dummies + .First(x => entMan.TryGetComponent(x.AttachedEntity!.Value!, out var marker) && + marker.HumanoidType == ScpSlHumanoidType.ClassD) + .AttachedEntity!.Value!; + + physicsSystem.WakeBody(escapeZoneXform.Owner); + physicsSystem.WakeBody(dummyD); + + await pair.RunTicksSync(5); + + xformSystem.SetCoordinates(dummyD, escapeZoneXform.Coordinates); + + await pair.RunTicksSync(5); + + var dAfter = dummies.Count(x => + entMan.TryGetComponent(x.AttachedEntity!.Value!, out var marker) && + marker.HumanoidType == ScpSlHumanoidType.ClassD); + + var chaosAfter = dummies.Count(x => + entMan.TryGetComponent(x.AttachedEntity!.Value!, out var marker) && + marker.HumanoidType == ScpSlHumanoidType.Chaos); + + + Assert.That(dBefore > dAfter, Is.True); + Assert.That(chaosBefore < chaosAfter, Is.True); + + // Try end round + var player = pair.Player!.AttachedEntity!.Value; + + var dummyEntity = dummies.Select(x => x.AttachedEntity ?? default).Append(player).Where(x=> entMan.HasComponent(x)).ToList(); + + await server.WaitAssertion(() => + { + foreach (var entityUid in dummyEntity) + { + entMan.DeleteEntity(entityUid); + } + + }); + + Assert.That(ticker.RunLevel == GameRunLevel.PostRound, Is.True, "All humanoids are dead, but round doesn't end!"); + + ticker.SetGamePreset((GamePresetPrototype?) null); + await pair.CleanReturnAsync(); + } +} diff --git a/Content.Server/_Scp/GameRules/ScpSl/ScpSLGameRuleSystem.Subs.cs b/Content.Server/_Scp/GameRules/ScpSl/ScpSLGameRuleSystem.Subs.cs new file mode 100644 index 00000000000..fc53101cd4b --- /dev/null +++ b/Content.Server/_Scp/GameRules/ScpSl/ScpSLGameRuleSystem.Subs.cs @@ -0,0 +1,14 @@ +namespace Content.Server._Scp.GameRules.ScpSl; + +public sealed partial class ScpSlGameRuleSystem +{ + private int _maxChaosSpawnCount; + private int _maxMogSpawnCount; + private float _chaosSpawnChance; + private void InitializeSubs() + { + _cfg.OnValueChanged(SlCvars.MaxChaosSpawnCount, newValue => _maxChaosSpawnCount = newValue); + _cfg.OnValueChanged(SlCvars.MaxMogSpawnCount, newValue => _maxMogSpawnCount = newValue); + _cfg.OnValueChanged(SlCvars.ChaosSpawnChance, newValue => _chaosSpawnChance = newValue); + } +} diff --git a/Content.Server/_Scp/GameRules/ScpSl/ScpSlEscapeZoneComponent.cs b/Content.Server/_Scp/GameRules/ScpSl/ScpSlEscapeZoneComponent.cs new file mode 100644 index 00000000000..28c518e6a0d --- /dev/null +++ b/Content.Server/_Scp/GameRules/ScpSl/ScpSlEscapeZoneComponent.cs @@ -0,0 +1,25 @@ +using Content.Shared._Scp.GameRule.Sl; +using Robust.Shared.Prototypes; + +namespace Content.Server._Scp.GameRules.ScpSl; + +[RegisterComponent] +public sealed partial class ScpSlEscapeZoneComponent : Component; + +[RegisterComponent] +public sealed partial class SlHumanoidSpawnPointComponent : Component +{ + [DataField(required: true)] + public ScpSlHumanoidType SpawnPointType { get; private set; } +} + +[RegisterComponent] +public sealed partial class SlScpSpawnPointComponent : Component +{ + [DataField(required: true)] + public EntProtoId ScpProtoId { get; private set; } + + [DataField] + public bool Playable { get; private set; } = true; +} + diff --git a/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleComponent.cs b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleComponent.cs new file mode 100644 index 00000000000..a0b1310b0d5 --- /dev/null +++ b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleComponent.cs @@ -0,0 +1,46 @@ +using Content.Shared._Scp.GameRule.Sl; +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; + +namespace Content.Server._Scp.GameRules.ScpSl; + +[RegisterComponent] +public sealed partial class ScpSlGameRuleComponent : Component +{ + + [DataField(required: true)] + public Dictionary>> HumanoidPresets { get; private set; } = new(); + + public Dictionary> Zones { get; private set; } = new(); + + [DataField(required: true)] + public ProtoId MogCaptainPrototype { get; set; } + + [DataField, ViewVariables(VVAccess.ReadWrite)] + public TimeSpan WaveSpawnCooldown { get; set; } = TimeSpan.FromSeconds(300); + + public TimeSpan NextWaveSpawnTime { get; set; } + + public int EscapedScientists = 0; + public int EscapedDClass = 0; + + public int ContainedScps = 0; + + public SlWinType WinType = SlWinType.Tie; +} + +public enum SlWinType : byte +{ + FondWin = 0, + Tie = 1, + ScpWin = 2, + ChaosWin = 3 +} + +public enum SlZoneType +{ + Light, + Hard, + Office +} diff --git a/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Escape.cs b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Escape.cs new file mode 100644 index 00000000000..522cbb17c05 --- /dev/null +++ b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Escape.cs @@ -0,0 +1,69 @@ +using Content.Shared._Scp.GameRule.Sl; +using Content.Shared.Cuffs.Components; +using Robust.Shared.Physics.Events; + +namespace Content.Server._Scp.GameRules.ScpSl; + +public sealed partial class ScpSlGameRuleSystem +{ + private void InitializeEscape() + { + SubscribeLocalEvent(OnEscapeZoneCollide); + } + + private void OnEscapeZoneCollide(Entity ent, ref StartCollideEvent args) + { + if (!TryGetActiveRule(out var rule)) + { + return; + } + + var collidedEntityUid = args.OtherEntity; + + if (!TryComp(collidedEntityUid, out var humanoidMarkerComponent) + || !_mindSystem.TryGetMind(collidedEntityUid, out var mindEntity, out var mindComponent)) + { + return; + } + + var humanoidToSpawn = humanoidMarkerComponent.HumanoidType; + + if (humanoidToSpawn == ScpSlHumanoidType.ClassD) + { + if (IsCuffed(collidedEntityUid)) + { + humanoidToSpawn = ScpSlHumanoidType.Mog; + } + else + { + humanoidToSpawn = ScpSlHumanoidType.Chaos; + rule.Value.Comp1.EscapedDClass++; + } + } + else if (humanoidToSpawn == ScpSlHumanoidType.Scientist) + { + rule.Value.Comp1.EscapedScientists++; + humanoidToSpawn = ScpSlHumanoidType.Mog; + } + else + { + return; + } + + var outfit = SelectHumanoidPreset(rule.Value, humanoidToSpawn); + + var spawnPoint = SelectRandomSpawnPosition(humanoidToSpawn); + + var newHumanoidUid = _randomHumanoidSystem.SpawnRandomHumanoid(outfit, spawnPoint, string.Empty); + + _mindSystem.TransferTo(mindEntity, newHumanoidUid); + + Del(collidedEntityUid); + } + + private bool IsCuffed(EntityUid entityUid) + { + return TryComp(entityUid, out var cuffableComponent) + && cuffableComponent.CuffedHandCount >= 2; + } +} diff --git a/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Spawns.cs b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Spawns.cs new file mode 100644 index 00000000000..7ec5b289655 --- /dev/null +++ b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Spawns.cs @@ -0,0 +1,274 @@ +using System.Linq; +using Content.Server.GameTicking; +using Content.Shared._Scp.GameRule.Sl; +using Content.Shared.Ghost; +using Content.Shared.Humanoid.Prototypes; +using Robust.Shared.Map; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; + +namespace Content.Server._Scp.GameRules.ScpSl; + +public sealed partial class ScpSlGameRuleSystem +{ + private int _tracker = 0; + + //4014314031441404134044434414 + public static IReadOnlyList SpawnQueue = [4, 0, 1, 4, 3, 1, 4, 0, 3, 1, 4, 4, 1, 4, 0, 4, 1, 3, 4, 0, 4, 4, 4, 3, 4, 4, 1, 4]; + + private Dictionary _numToHumanoidType = new() + { + { 1, ScpSlHumanoidType.Security }, + { 3, ScpSlHumanoidType.Scientist }, + { 4, ScpSlHumanoidType.ClassD }, + }; + + private void InitializeSpawn() + { + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnPlayerSpawning); + } + + private void OnPlayerSpawning(RulePlayerSpawningEvent ev) + { + if (!TryGetActiveRule(out var rule)) + { + return; + } + + InitialSpawn(rule.Value, ev.PlayerPool); + } + + private void InitialSpawn(Entity rule, List pool) + { + for (var i = 0; i < 5; i++) + { + _random.Shuffle(pool); + } + + foreach (var player in pool) + { + ProcessSpawn(rule, player); + } + + pool.Clear(); + } + + private void ProcessSpawn(Entity rule, ICommonSession playerToSpawn) + { + if (_tracker >= SpawnQueue.Count) + { + _tracker = 0; + } + + _gameTicker.PlayerJoinGame(playerToSpawn); + + var spawnType = SpawnQueue[_tracker]; + _tracker++; + + if (spawnType == 0 ) + { + if (TrySpawnScp(rule, playerToSpawn)) + { + return; + } + + spawnType = 1; + } + + if (!_numToHumanoidType.TryGetValue(spawnType, out var humanoidType)) + { + humanoidType = ScpSlHumanoidType.ClassD; + } + + SpawnHumanoid(rule, playerToSpawn, humanoidType); + } + + public void SpawnHumanoid(Entity rule, ICommonSession playerToSpawn, ScpSlHumanoidType humanoidType) + { + var spawnPosition = SelectRandomSpawnPosition(humanoidType); + + var humanoidPreset = SelectHumanoidPreset(rule, humanoidType); + + var humanoid = _randomHumanoidSystem.SpawnRandomHumanoid(humanoidPreset, spawnPosition, string.Empty); + + var mindEntity = _mindSystem.GetOrCreateMind(playerToSpawn.UserId); + + _mindSystem.TransferTo(mindEntity, humanoid); + } + + public bool TrySpawnScp(Entity rule, ICommonSession playerToSpawn) + { + var scpSpawns = GetScpSpawns(); + + if (scpSpawns.Count == 0) + { + return false; + } + + var spawn = _random.PickAndTake(scpSpawns); + var spawnPosition = spawn.Comp1.Coordinates; + var scpProto = spawn.Comp2.ScpProtoId; + + var scpEntity = Spawn(scpProto, spawnPosition); + AddComp(scpEntity); + + var mindEntity = _mindSystem.GetOrCreateMind(playerToSpawn.UserId); + + _mindSystem.TransferTo(mindEntity, scpEntity); + + Del(spawn); + return true; + } + + private void SpawnUpdate(Entity rule) + { + if (rule.Comp.NextWaveSpawnTime > _gameTiming.CurTime) + { + return; + } + + rule.Comp.NextWaveSpawnTime = _gameTiming.CurTime + rule.Comp.WaveSpawnCooldown; + + SpawnWave(rule); + } + + private void OnMapInit(Entity ent, ref MapInitEvent args) + { + var xform = Transform(ent); + + if (ent.Comp.Playable) + { + return; + } + + Spawn(ent.Comp.ScpProtoId, xform.Coordinates); + + QueueDel(ent); + } + + private void SpawnChaosWave(Entity rule, List pool) + { + if (pool.Count == 0) + { + return; + } + + var playersToSpawn = Math.Min(pool.Count, _maxChaosSpawnCount); + + for (int i = 0; i < playersToSpawn; i++) + { + var player = _random.PickAndTake(pool); + + SpawnHumanoid(rule, player, ScpSlHumanoidType.Chaos); + } + } + + private void SpawnWave(Entity rule) + { + var players = _playerManager.NetworkedSessions; + + var eligible = GetEligiblePlayersForWave(players.ToList()); + + if (eligible.Count == 0) + { + return; + } + + if (_random.Prob(_chaosSpawnChance)) + { + SpawnChaosWave(rule, eligible); + return; + } + + SpawnMogWave(rule, eligible); + } + + private void SpawnMogWave(Entity rule, List pool) + { + if (pool.Count == 0) + { + return; + } + + var spawnPoint = SelectRandomSpawnPosition(ScpSlHumanoidType.Mog); + + var captainPlayer = _random.PickAndTake(pool); + var captainEntity = _randomHumanoidSystem.SpawnRandomHumanoid(rule.Comp.MogCaptainPrototype, spawnPoint, string.Empty); + + var captainMind = _mindSystem.GetOrCreateMind(captainPlayer.UserId); + _mindSystem.TransferTo(captainMind, captainEntity); + + var playersToSpawn = Math.Min(pool.Count, _maxMogSpawnCount); + + + for (var i = 0; i < playersToSpawn; i++) + { + var player = _random.PickAndTake(pool); + + SpawnHumanoid(rule, player, ScpSlHumanoidType.Mog); + } + } + + private List GetEligiblePlayersForWave(List sessions) + { + var eligiblePlayers = new List(); + + foreach (var session in sessions) + { + var playerEntity = session.AttachedEntity; + + if (!playerEntity.HasValue || !HasComp(playerEntity)) + { + continue; + } + + eligiblePlayers.Add(session); + } + + return eligiblePlayers; + } + + private ProtoId SelectHumanoidPreset(Entity rule, ScpSlHumanoidType humanoidType) + { + var outfits = rule.Comp.HumanoidPresets[humanoidType]; + return _random.Pick(outfits); + } + + private EntityCoordinates SelectRandomSpawnPosition(ScpSlHumanoidType humanoidType) + { + var spawners = GetHumanoidSpawnPoints(humanoidType); + return _random.Pick(spawners).Comp1.Coordinates; + } + + private List> GetHumanoidSpawnPoints(ScpSlHumanoidType spawnPointType) + { + var spawnPointsQuery = EntityQueryEnumerator(); + + List> spawnPoints = []; + + while (spawnPointsQuery.MoveNext(out var entityUid, out var transformComponent, out var spawnPointComponent)) + { + var entity = new Entity(entityUid, transformComponent, spawnPointComponent); + spawnPoints.Add(entity); + } + + return spawnPoints; + } + + private List> GetScpSpawns() + { + var spawnPointsQuery = EntityQueryEnumerator(); + + List> spawnPoints = []; + + while (spawnPointsQuery.MoveNext(out var entityUid, out var transformComponent, out var spawnPointComponent)) + { + var entity = new Entity(entityUid, transformComponent, spawnPointComponent); + spawnPoints.Add(entity); + } + + return spawnPoints; + } +} diff --git a/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Tracker.cs b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Tracker.cs new file mode 100644 index 00000000000..538305e512b --- /dev/null +++ b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.Tracker.cs @@ -0,0 +1,53 @@ +using Content.Shared._Scp.GameRule.Sl; +using Content.Shared.Mobs; + +namespace Content.Server._Scp.GameRules.ScpSl; + +public partial class ScpSlGameRuleSystem +{ + private void InitializeTracker() + { + SubscribeLocalEvent(OnHumanoidMobStateChanged); + SubscribeLocalEvent(OnHumanoidRemoved); + + SubscribeLocalEvent(OnScpMobStateChanged); + SubscribeLocalEvent(OnScpRemoved); + } + + private void OnScpMobStateChanged(Entity ent, ref MobStateChangedEvent args) + { + if (!TryGetActiveRule(out var rule)) + { + return; + } + + if (args.NewMobState != MobState.Alive) + { + ent.Comp.Contained = true; + + _ghostSystem.SpawnGhost(ent.Owner, canReturn: false); + + TryEndRound(); + } + } + + private void OnScpRemoved(Entity ent, ref ComponentRemove args) + { + TryEndRound(); + } + + + private void OnHumanoidMobStateChanged(Entity ent, ref MobStateChangedEvent args) + { + if (args.NewMobState == MobState.Dead) + { + TryEndRound(); + } + } + + private void OnHumanoidRemoved(Entity ent, ref ComponentRemove args) + { + TryEndRound(); + } +} + diff --git a/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.cs b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.cs new file mode 100644 index 00000000000..7191be8fa83 --- /dev/null +++ b/Content.Server/_Scp/GameRules/ScpSl/ScpSlGameRuleSystem.cs @@ -0,0 +1,264 @@ +using System.Diagnostics.CodeAnalysis; +using Content.Server.Cuffs; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Rules; +using Content.Server.Ghost; +using Content.Server.Humanoid.Systems; +using Content.Server.Mind; +using Content.Server.Nuke; +using Content.Shared._Scp.GameRule.Sl; +using Content.Shared.GameTicking.Components; +using Content.Shared.Mobs; +using Content.Shared.Mobs.Components; +using Content.Shared.Mobs.Systems; +using Content.Shared.NPC.Systems; +using Robust.Server.Player; +using Robust.Shared.Configuration; +using Robust.Shared.Map.Components; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Server._Scp.GameRules.ScpSl; + +public sealed partial class ScpSlGameRuleSystem : GameRuleSystem +{ + [Dependency] private readonly NpcFactionSystem _npcFactionSystem = default!; + [Dependency] private readonly CuffableSystem _cuffableSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly MindSystem _mindSystem = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly RandomHumanoidSystem _randomHumanoidSystem = default!; + [Dependency] private readonly IPlayerManager _playerManager = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly MobStateSystem _mobStateSystem = default!; + [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly GhostSystem _ghostSystem = default!; + + protected override string SawmillName => "ScpSl"; + + private TimeSpan _nextRoundEndCheckTime; + private EntProtoId _ashPrototype = "Ash"; + + public override void Initialize() + { + base.Initialize(); + + UpdatesAfter.Add(typeof(LoadMapRuleSystem)); + + InitializeEscape(); + InitializeSpawn(); + InitializeSubs(); + InitializeTracker(); + + SubscribeLocalEvent(OnNukeExploded); + } + + private void OnNukeExploded(NukeExplodedEvent ev) + { + if (!TryGetActiveRule(out var gameRule) + || !ev.OwningStation.HasValue) + { + return; + } + + var stationUid = ev.OwningStation.Value; + var zones = gameRule.Value.Comp1.Zones.Values; + + if (zones.FirstOrNull(x => x.Owner == stationUid) == null) + { + return; + } + + foreach (var zone in zones) + { + DustifyAllMobsOnGrid(zone); + } + } + + private List> GetAllMobstatesOnGrid(Entity gridEntity) + { + var query = EntityQueryEnumerator(); + var mobs = new List>(); + + while (query.MoveNext(out var uid, out var xform, out var mobStateComponent)) + { + if (xform.GridUid != gridEntity) + continue; + + var mobEntity = new Entity(uid, mobStateComponent); + mobs.Add(mobEntity); + } + + return mobs; + } + + private void DustifyAllMobsOnGrid(Entity gridEntity) + { + var mobs = GetAllMobstatesOnGrid(gridEntity); + + foreach (var mob in mobs) + { + var mobXform = Transform(mob); + _mobStateSystem.ChangeMobState(mob, MobState.Dead); + + Spawn(_ashPrototype, mobXform.Coordinates); + + Del(mob); + } + } + + protected override void ActiveTick(EntityUid uid, ScpSlGameRuleComponent component, GameRuleComponent gameRule, float frameTime) + { + base.ActiveTick(uid, component, gameRule, frameTime); + + var ruleEntity = new Entity(uid, component); + SpawnUpdate(ruleEntity); + + if (_nextRoundEndCheckTime < _gameTiming.CurTime) + { + _nextRoundEndCheckTime = _gameTiming.CurTime + TimeSpan.FromSeconds(5); + + TryEndRound(); + } + } + + protected override void Started(EntityUid uid, ScpSlGameRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args) + { + base.Started(uid, component, gameRule, args); + + component.NextWaveSpawnTime = _gameTiming.CurTime + component.WaveSpawnCooldown; + _nextRoundEndCheckTime = _gameTiming.CurTime + TimeSpan.FromMinutes(1); + } + + protected override void AppendRoundEndText(EntityUid uid, + ScpSlGameRuleComponent component, + GameRuleComponent gameRule, + ref RoundEndTextAppendEvent args) + { + base.AppendRoundEndText(uid, component, gameRule, ref args); + } + + private void TryEndRound() + { + if (!TryGetActiveRule(out var rule)) + { + return; + } + + var slGameRuleComponent = rule.Value.Comp1; + + var dAlive = GetAliveHumanoidsCount(ScpSlHumanoidType.ClassD); + var mogAlive = GetAliveHumanoidsCount(ScpSlHumanoidType.Mog) + GetAliveHumanoidsCount(ScpSlHumanoidType.Scientist); + var chaosAlive = GetAliveHumanoidsCount(ScpSlHumanoidType.Chaos); + var scpAlive = GetAliveScpCount(); + + + var shouldRoundEnd = false; + + if (mogAlive > 0 && dAlive == 0 && scpAlive == 0) + { + shouldRoundEnd = true; + + if (slGameRuleComponent.EscapedScientists > slGameRuleComponent.EscapedDClass) + { + slGameRuleComponent.WinType = SlWinType.FondWin; + } + else + { + slGameRuleComponent.WinType = SlWinType.Tie; + } + } + + if (scpAlive > 0 && dAlive == 0 && mogAlive == 0) + { + shouldRoundEnd = true; + + if (scpAlive > slGameRuleComponent.EscapedDClass + slGameRuleComponent.EscapedScientists) + { + slGameRuleComponent.WinType = SlWinType.ScpWin; + } + else if (slGameRuleComponent.EscapedDClass > slGameRuleComponent.EscapedScientists + scpAlive) + { + slGameRuleComponent.WinType = SlWinType.ChaosWin; + } + else + { + slGameRuleComponent.WinType = SlWinType.Tie; + } + } + + if ((dAlive > 0 || chaosAlive > 0) && scpAlive == 0 && mogAlive == 0) + { + shouldRoundEnd = true; + + if (slGameRuleComponent.EscapedScientists < slGameRuleComponent.EscapedDClass) + { + slGameRuleComponent.WinType = SlWinType.ChaosWin; + } + else + { + slGameRuleComponent.WinType = SlWinType.Tie; + } + } + + if (dAlive == 0 && mogAlive == 0 && scpAlive == 0) + { + shouldRoundEnd = true; + slGameRuleComponent.WinType = SlWinType.Tie; + } + + if (shouldRoundEnd) + { + _gameTicker.EndRound(); + } + } + + private bool TryGetActiveRule([NotNullWhen(true)] out Entity? gameRule) + { + gameRule = null; + + var query = QueryActiveRules(); + + while (query.MoveNext(out var _, out var scpRuleComponent, out var ruleComponent)) + { + gameRule = new Entity(scpRuleComponent.Owner, scpRuleComponent, ruleComponent); + } + + return gameRule.HasValue; + } + + private int GetAliveHumanoidsCount(ScpSlHumanoidType humanoidType) + { + var query = EntityQueryEnumerator(); + + var alive = 0; + while (query.MoveNext(out var _, out var markerComponent, out var mobStateComponent)) + { + if (mobStateComponent.CurrentState is MobState.Alive or MobState.Critical + && markerComponent.HumanoidType == humanoidType) + { + alive++; + } + } + + return alive; + } + + private int GetAliveScpCount() + { + var query = EntityQueryEnumerator(); + var alive = 0; + + while (query.MoveNext(out var _, out var marker, out var _)) + { + if (!marker.Contained) + { + alive++; + } + } + + return alive; + } +} diff --git a/Content.Server/_Scp/GameRules/ScpSl/SlCvars.cs b/Content.Server/_Scp/GameRules/ScpSl/SlCvars.cs new file mode 100644 index 00000000000..646cdff65d7 --- /dev/null +++ b/Content.Server/_Scp/GameRules/ScpSl/SlCvars.cs @@ -0,0 +1,16 @@ +using Robust.Shared.Configuration; + +namespace Content.Server._Scp.GameRules.ScpSl; + +[CVarDefs] +public sealed class SlCvars +{ + public static readonly CVarDef MaxMogSpawnCount = + CVarDef.Create("sl.max_mog_spawn_count", 20, CVar.SERVERONLY | CVar.ARCHIVE); + + public static readonly CVarDef MaxChaosSpawnCount = + CVarDef.Create("sl.max_chaos_spawn_count", 20, CVar.SERVERONLY | CVar.ARCHIVE); + + public static readonly CVarDef ChaosSpawnChance = + CVarDef.Create("sl.chaos_spawn_chance", 0.2f, CVar.SERVERONLY | CVar.ARCHIVE); +} diff --git a/Content.Server/_Scp/Scp914/Scp914System.cs b/Content.Server/_Scp/Scp914/Scp914System.cs new file mode 100644 index 00000000000..30374e3fb56 --- /dev/null +++ b/Content.Server/_Scp/Scp914/Scp914System.cs @@ -0,0 +1,242 @@ +using System.Linq; +using Content.Server.Popups; +using Content.Server.Storage.Components; +using Content.Server.Storage.EntitySystems; +using Content.Shared._Scp.Scp914; +using Content.Shared.Interaction; +using Content.Shared.Popups; +using Robust.Server.Audio; +using Robust.Shared.Audio; +using Robust.Shared.Containers; +using Robust.Shared.Random; +using Robust.Shared.Timing; + +namespace Content.Server._Scp.Scp914; + +public sealed class Scp914System : SharedScp914System +{ + [Dependency] private readonly SharedTransformSystem _transformSystem = default!; + [Dependency] private readonly EntityStorageSystem _entityStorageSystem = default!; + [Dependency] private readonly SharedContainerSystem _containerSystem = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly AudioSystem _audioSystem = default!; + [Dependency] private readonly EntityStorageSystem _storageSystem = default!; + [Dependency] private readonly SharedUserInterfaceSystem _userInterfaceSystem = default!; + [Dependency] private readonly PopupSystem _popupSystem = default!; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnInteractHand); + + Subs.BuiEvents(Scp914UiKey.Key, + subscriber => + { + subscriber.Event(OnChangeModeRequest); + subscriber.Event(OnStartCycleRequest); + }); + } + + private void UpdateState(Entity entity) + { + var state = new Scp914BuiState(entity.Comp.CurrentMode, entity.Comp.Active); + _userInterfaceSystem.SetUiState(entity.Owner, Scp914UiKey.Key, state); + } + + private void OnInteractHand(Entity ent, ref InteractHandEvent args) + { + UpdateState(ent); + _userInterfaceSystem.TryOpenUi(ent.Owner, Scp914UiKey.Key, args.User); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + var query = EntityQueryEnumerator(); + + while (query.MoveNext(out var entityUid, out var scp914Component)) + { + if (!scp914Component.Active || _gameTiming.CurTime <= scp914Component.UpgradeTimeEnd) + continue; + + var scpEntity = new Entity(entityUid, scp914Component); + + scp914Component.Active = false; + ToggleLock(scpEntity, false); + + scp914Component.NextUpgradeTime = _gameTiming.CurTime + scp914Component.UpgradeCooldown; + } + } + + private void OnMapInit(Entity machineEntity, ref MapInitEvent args) + { + var query = EntityQueryEnumerator(); + var machineXform = Transform(machineEntity); + + while (query.MoveNext(out var containerEntity, out var containerXform, out var containerComponent)) + { + if (machineXform.MapID != containerXform.MapID || + !_transformSystem.InRange(machineEntity.Owner, containerEntity, 8f)) + { + continue; + } + + if (containerComponent.ContainerType == Scp914ContainerType.Input) + { + machineEntity.Comp.InputContainer = containerEntity; + } + else + { + machineEntity.Comp.OutputContainer = containerEntity; + } + } + } + + private void OnChangeModeRequest(Entity ent, ref Scp914ChangeModeRequestMessage args) + { + if (ent.Comp.Active) + { + return; + } + + if (ent.Comp.NextChangeCycleTime > _gameTiming.CurTime) + { + return; + } + + var newMode = CycleMod(ent.Comp.CurrentMode, args.Direction); + + ent.Comp.CurrentMode = newMode; + ent.Comp.NextChangeCycleTime = _gameTiming.CurTime + ent.Comp.CycleCooldown; + + _audioSystem.PlayPvs(ent.Comp.ClackSound, ent); + + UpdateState(ent); + Dirty(ent); + } + + private void OnStartCycleRequest(Entity ent, ref Scp914StartCycleMessage args) + { + if (ent.Comp.Active) + { + return; + } + + if (ent.Comp.NextUpgradeTime > _gameTiming.CurTime) + { + var message = Loc.GetString("scp914-cycle-timeout"); + _popupSystem.PopupEntity(message, ent, PopupType.LargeCaution); + return; + } + + _audioSystem.PlayPvs(ent.Comp.ClackSound, ent, AudioParams.Default.WithPitchScale(-10f).WithVariation(0.1f)); + _audioSystem.PlayPvs(ent.Comp.RefineSound, ent); + + ent.Comp.Active = true; + ent.Comp.UpgradeTimeEnd = _gameTiming.CurTime + ent.Comp.UpgradeDuration; + + ToggleLock(ent, true); + ProcessUpgrades(ent); + UpdateState(ent); + + Dirty(ent); + } + + private void ToggleLock(Entity ent, bool lockState) + { + var inputContainer = ent.Comp.InputContainer; + var outputContainer = ent.Comp.OutputContainer; + + if (lockState) + { + _storageSystem.CloseStorage(inputContainer); + _storageSystem.CloseStorage(outputContainer); + } + else + { + _storageSystem.OpenStorage(inputContainer); + _storageSystem.OpenStorage(outputContainer); + } + } + + private void ProcessUpgrades(Entity machineEntity) + { + var inputContainer = GetContainer(machineEntity, Scp914ContainerType.Input); + var outputContainer = GetContainer(machineEntity, Scp914ContainerType.Output); + + var machineMode = machineEntity.Comp.CurrentMode; + + foreach (var containedEntity in inputContainer.ContainedEntities.ToList()) + { + EntityUid? upgradedEntity = containedEntity; + + if (TryComp(containedEntity, out var upgradableComponent)) + { + var upgradableEntity = new Entity(containedEntity, upgradableComponent); + + UpgradeItem(upgradableEntity, machineMode, ref upgradedEntity); + } + + if (upgradedEntity.HasValue) + { + _containerSystem.Insert(upgradedEntity.Value, outputContainer, force: true); + } + } + } + + private void UpgradeItem(Entity upgradableEntity, Scp914Mode machineMode, ref EntityUid? entity) + { + var options = upgradableEntity.Comp.UpgradeOptions; + + if (!options.TryGetValue(machineMode, out var upgradeOptions)) + { + return; + } + + var randomValue = _random.NextFloat(); + var cumulativeProbability = 0f; + + foreach (var option in upgradeOptions) + { + cumulativeProbability += option.Chance; + + if (randomValue > cumulativeProbability) + { + continue; + } + + if (!option.Item.HasValue) + { + Del(upgradableEntity); + entity = null; + + break; + } + + Del(upgradableEntity); + + entity = Spawn(option.Item); + break; + } + } + + private BaseContainer GetContainer(Entity machineEntity, Scp914ContainerType containerType) + { + BaseContainer container; + + if (containerType == Scp914ContainerType.Input) + { + container = Comp(machineEntity.Comp.InputContainer).Contents; + } + else + { + container = Comp(machineEntity.Comp.OutputContainer).Contents; + } + + return container; + } +} diff --git a/Content.Shared/_Scp/GameRule/Sl/Markers.cs b/Content.Shared/_Scp/GameRule/Sl/Markers.cs new file mode 100644 index 00000000000..51a368786d3 --- /dev/null +++ b/Content.Shared/_Scp/GameRule/Sl/Markers.cs @@ -0,0 +1,25 @@ +namespace Content.Shared._Scp.GameRule.Sl; + +//Component for marking SCP in SCP:SL gamemode +[RegisterComponent] +public sealed partial class ScpSlScpMarkerComponent : Component +{ + public bool Contained { get; set; } +} + +[RegisterComponent] +public sealed partial class ScpSlHumanoidMarkerComponent : Component +{ + [DataField(required: true)] + public ScpSlHumanoidType HumanoidType { get; set; } +} + +[Serializable] +public enum ScpSlHumanoidType : byte +{ + Mog = 0, + Chaos = 1, + Scientist = 2, + ClassD = 4, + Security = 8 +} diff --git a/Content.Shared/_Scp/Scp914/Scp914UiData.cs b/Content.Shared/_Scp/Scp914/Scp914UiData.cs new file mode 100644 index 00000000000..3c062925d1b --- /dev/null +++ b/Content.Shared/_Scp/Scp914/Scp914UiData.cs @@ -0,0 +1,48 @@ +using Robust.Shared.Serialization; + +namespace Content.Shared._Scp.Scp914; + + +[Serializable, NetSerializable] +public sealed partial class Scp914BuiState : BoundUserInterfaceState +{ + public Scp914Mode NewMode { get; set; } + public bool Active { get; set; } + + public Scp914BuiState(Scp914Mode newMode, bool active) + { + NewMode = newMode; + Active = active; + } +} + +[Serializable, NetSerializable] +public sealed partial class Scp914ChangeModeRequestMessage : BoundUserInterfaceMessage +{ + public Scp914CycleDirection Direction { get; set; } + + public Scp914ChangeModeRequestMessage(Scp914CycleDirection direction) + { + Direction = direction; + } +} + +[Serializable, NetSerializable] +public sealed partial class Scp914StartCycleMessage : BoundUserInterfaceMessage +{ + +} + +[Serializable, NetSerializable] +public enum Scp914CycleDirection : byte +{ + Left = 0, + Right = 1, +} + +[Serializable, NetSerializable] +public enum Scp914UiKey +{ + Key, +} + diff --git a/Content.Shared/_Scp/Scp914/SharedScp914Data.cs b/Content.Shared/_Scp/Scp914/SharedScp914Data.cs new file mode 100644 index 00000000000..ead9a94f94d --- /dev/null +++ b/Content.Shared/_Scp/Scp914/SharedScp914Data.cs @@ -0,0 +1,86 @@ +using Content.Shared.Storage.Components; +using Robust.Shared.Audio; +using Robust.Shared.GameStates; +using Robust.Shared.Prototypes; +using Robust.Shared.Serialization; + +namespace Content.Shared._Scp.Scp914; + + +[RegisterComponent, NetworkedComponent] +public sealed partial class Scp914Component : Component +{ + public Scp914Mode CurrentMode { get; set; } = Scp914Mode.Rough; + + [ViewVariables(VVAccess.ReadWrite)] + public bool Active { get; set; } + + [DataField] + public TimeSpan UpgradeDuration { get; private set; } = TimeSpan.FromSeconds(15); + + [ViewVariables(VVAccess.ReadOnly)] + public TimeSpan UpgradeTimeEnd { get; set; } = TimeSpan.MinValue; + + [DataField] + public TimeSpan UpgradeCooldown { get; set; } = TimeSpan.FromSeconds(5); + + public TimeSpan NextUpgradeTime { get; set; } + + [DataField] + public TimeSpan CycleCooldown = TimeSpan.FromSeconds(1); + + public TimeSpan NextChangeCycleTime { get; set; } = TimeSpan.MinValue; + + [ViewVariables] + public EntityUid InputContainer { get; set; } + + [ViewVariables] + public EntityUid OutputContainer { get; set; } + + [DataField] + public SoundSpecifier RefineSound { get; private set; } = new SoundPathSpecifier("/Audio/_Scp/Scp914/scp914_refine.ogg"); + + [DataField] + public SoundSpecifier ClackSound { get; private set; } = new SoundPathSpecifier("/Audio/_Scp/Scp914/scp914_clack.ogg"); +} + +[RegisterComponent] +public sealed partial class Scp914ContainerComponent : Component +{ + [DataField(required: true)] + public Scp914ContainerType ContainerType { get; set; } +} + +[RegisterComponent] +public sealed partial class Scp914UpgradableComponent : Component +{ + [DataField(required: true)] + public Dictionary> UpgradeOptions { get; private set; } = new(); +} + +[Serializable, NetSerializable] +public enum Scp914Mode : byte +{ + Rough = 0, + Coarse = 1, + OneToOne = 2, + Fine = 4, + VeryFine = 8, +} + +[Serializable] +public enum Scp914ContainerType : byte +{ + Input = 0, + Output = 1 +} + +[DataDefinition] +public sealed partial class UpgradeOption +{ + [DataField(required: true)] + public EntProtoId? Item { get; private set; } + + [DataField] + public float Chance { get; private set; } = 1.0f; +} diff --git a/Content.Shared/_Scp/Scp914/SharedScp914System.cs b/Content.Shared/_Scp/Scp914/SharedScp914System.cs new file mode 100644 index 00000000000..ab98e23e90b --- /dev/null +++ b/Content.Shared/_Scp/Scp914/SharedScp914System.cs @@ -0,0 +1,48 @@ +using Content.Shared.Storage; +using Content.Shared.Storage.Components; + +namespace Content.Shared._Scp.Scp914; + +public abstract class SharedScp914System : EntitySystem +{ + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(OnOpenAttempt); + SubscribeLocalEvent(OnCloseAttempt); + SubscribeLocalEvent(OnInteractAttempt); + } + + private void OnCloseAttempt(Entity ent, ref StorageCloseAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnInteractAttempt(Entity ent, ref StorageInteractAttemptEvent args) + { + args.Cancelled = true; + } + + private void OnOpenAttempt(Entity ent, ref StorageOpenAttemptEvent args) + { + args.Cancelled = true; + } + + protected Scp914Mode CycleMod(Scp914Mode value, Scp914CycleDirection direction) + { + var values = (Scp914Mode[])Enum.GetValues(typeof(Scp914Mode)); + + + var currentIndex = Array.IndexOf(values, value); + + var shift = direction == Scp914CycleDirection.Left ? -1 : 1; + var newIndex = (currentIndex + shift) % values.Length; + + if (newIndex < 0) + { + newIndex += values.Length; + } + + return values[newIndex]; + } +} diff --git a/Resources/Audio/_Scp/Scp914/scp914_clack.ogg b/Resources/Audio/_Scp/Scp914/scp914_clack.ogg new file mode 100644 index 00000000000..9a18b7f31a6 Binary files /dev/null and b/Resources/Audio/_Scp/Scp914/scp914_clack.ogg differ diff --git a/Resources/Audio/_Scp/Scp914/scp914_refine.ogg b/Resources/Audio/_Scp/Scp914/scp914_refine.ogg new file mode 100644 index 00000000000..9215e01c8a2 Binary files /dev/null and b/Resources/Audio/_Scp/Scp914/scp914_refine.ogg differ diff --git a/Resources/Locale/ru-RU/_fire/scp/914.ftl b/Resources/Locale/ru-RU/_fire/scp/914.ftl new file mode 100644 index 00000000000..426c8e3bab1 --- /dev/null +++ b/Resources/Locale/ru-RU/_fire/scp/914.ftl @@ -0,0 +1,12 @@ +scp914-cycle-left-button = < +scp914-cycle-right-button = > +scp914-start-cycle = Запустить + +scp914-rough-mode = Очень грубо +scp914-coarse-mode = Грубо +scp914-onetoone-mode = 1:1 +scp914-fine-mode = Тонко +scp914-veryfine-mode = Очень тонко + + +scp914-cycle-timeout = Механизм не запускается... diff --git a/Resources/Locale/ru-RU/_fire/scp/game-rule.ftl b/Resources/Locale/ru-RU/_fire/scp/game-rule.ftl new file mode 100644 index 00000000000..299a1d1c66a --- /dev/null +++ b/Resources/Locale/ru-RU/_fire/scp/game-rule.ftl @@ -0,0 +1,2 @@ +scpsl-title = Побег +slpsl-description = Нужно просто сбежать из комплекса... diff --git a/Resources/Maps/_Fire/sl_scp_test.yml b/Resources/Maps/_Fire/sl_scp_test.yml new file mode 100644 index 00000000000..b77166dbd7b --- /dev/null +++ b/Resources/Maps/_Fire/sl_scp_test.yml @@ -0,0 +1,366 @@ +meta: + format: 6 + postmapinit: false +tilemap: + 0: Space + 86: FloorReinforced +entities: +- proto: "" + entities: + - uid: 1 + components: + - type: MetaData + name: Map Entity + - type: Transform + - type: Map + - type: PhysicsMap + - type: GridTree + - type: MovedGrids + - type: Broadphase + - type: OccluderTree + - uid: 2 + components: + - type: MetaData + name: grid + - type: Transform + pos: -2.237307,1.399869 + parent: 1 + - type: MapGrid + chunks: + 0,0: + ind: 0,0 + tiles: VgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + 0,-1: + ind: 0,-1 + tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAVgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + version: 6 + - type: Broadphase + - type: Physics + bodyStatus: InAir + angularDamping: 0.05 + linearDamping: 0.05 + fixedRotation: False + bodyType: Dynamic + - type: Fixtures + fixtures: {} + - type: OccluderTree + - type: SpreaderGrid + - type: Shuttle + - type: GridPathfinding + - type: Gravity + gravityShakeSound: !type:SoundPathSpecifier + path: /Audio/Effects/alert.ogg + - type: DecalGrid + chunkCollection: + version: 2 + nodes: [] + - type: GridAtmosphere + version: 2 + data: + chunkSize: 4 + - type: GasTileOverlay + - type: RadiationGridResistance +- proto: SlEscapeZone + entities: + - uid: 47 + components: + - type: Transform + pos: 1.5,1.5 + parent: 2 +- proto: Sl173SpawnPoints + entities: + - uid: 55 + components: + - type: Transform + pos: 11.5,1.5 + parent: 2 +- proto: Sl939ASpawnPoint + entities: + - uid: 56 + components: + - type: Transform + pos: 10.5,1.5 + parent: 2 +- proto: SlChaosSpawnPoint + entities: + - uid: 48 + components: + - type: Transform + pos: 1.5,-6.5 + parent: 2 +- proto: SlClassDSpawnPoint + entities: + - uid: 49 + components: + - type: Transform + pos: 2.5,-6.5 + parent: 2 +- proto: SlMogSpawnPoint + entities: + - uid: 50 + components: + - type: Transform + pos: 3.5,-6.5 + parent: 2 +- proto: SlScientistSpawnPoint + entities: + - uid: 51 + components: + - type: Transform + pos: 4.5,-6.5 + parent: 2 +- proto: SlSecuritySpawnPoint + entities: + - uid: 52 + components: + - type: Transform + pos: 5.5,-6.5 + parent: 2 +- proto: WallBrick106 + entities: + - uid: 3 + components: + - type: Transform + pos: 12.5,2.5 + parent: 2 + - uid: 4 + components: + - type: Transform + pos: 12.5,1.5 + parent: 2 + - uid: 5 + components: + - type: Transform + pos: 12.5,0.5 + parent: 2 + - uid: 6 + components: + - type: Transform + pos: 12.5,-0.5 + parent: 2 + - uid: 7 + components: + - type: Transform + pos: 12.5,-1.5 + parent: 2 + - uid: 8 + components: + - type: Transform + pos: 12.5,-2.5 + parent: 2 + - uid: 9 + components: + - type: Transform + pos: 12.5,-3.5 + parent: 2 + - uid: 10 + components: + - type: Transform + pos: 12.5,-4.5 + parent: 2 + - uid: 11 + components: + - type: Transform + pos: 12.5,-5.5 + parent: 2 + - uid: 12 + components: + - type: Transform + pos: 12.5,-6.5 + parent: 2 + - uid: 13 + components: + - type: Transform + pos: 12.5,-7.5 + parent: 2 + - uid: 14 + components: + - type: Transform + pos: 11.5,-7.5 + parent: 2 + - uid: 15 + components: + - type: Transform + pos: 10.5,-7.5 + parent: 2 + - uid: 16 + components: + - type: Transform + pos: 9.5,-7.5 + parent: 2 + - uid: 17 + components: + - type: Transform + pos: 8.5,-7.5 + parent: 2 + - uid: 18 + components: + - type: Transform + pos: 6.5,-7.5 + parent: 2 + - uid: 19 + components: + - type: Transform + pos: 5.5,-7.5 + parent: 2 + - uid: 20 + components: + - type: Transform + pos: 4.5,-7.5 + parent: 2 + - uid: 21 + components: + - type: Transform + pos: 0.5,-7.5 + parent: 2 + - uid: 22 + components: + - type: Transform + pos: 1.5,-7.5 + parent: 2 + - uid: 23 + components: + - type: Transform + pos: 7.5,-7.5 + parent: 2 + - uid: 24 + components: + - type: Transform + pos: 3.5,-7.5 + parent: 2 + - uid: 25 + components: + - type: Transform + pos: 2.5,-7.5 + parent: 2 + - uid: 26 + components: + - type: Transform + pos: 0.5,-6.5 + parent: 2 + - uid: 27 + components: + - type: Transform + pos: 0.5,-5.5 + parent: 2 + - uid: 28 + components: + - type: Transform + pos: 0.5,-4.5 + parent: 2 + - uid: 29 + components: + - type: Transform + pos: 0.5,-3.5 + parent: 2 + - uid: 30 + components: + - type: Transform + pos: 0.5,-2.5 + parent: 2 + - uid: 31 + components: + - type: Transform + pos: 0.5,-1.5 + parent: 2 + - uid: 32 + components: + - type: Transform + pos: 0.5,-0.5 + parent: 2 + - uid: 33 + components: + - type: Transform + pos: 0.5,1.5 + parent: 2 + - uid: 34 + components: + - type: Transform + pos: 0.5,2.5 + parent: 2 + - uid: 35 + components: + - type: Transform + pos: 0.5,0.5 + parent: 2 + - uid: 36 + components: + - type: Transform + pos: 2.5,2.5 + parent: 2 + - uid: 37 + components: + - type: Transform + pos: 1.5,2.5 + parent: 2 + - uid: 38 + components: + - type: Transform + pos: 3.5,2.5 + parent: 2 + - uid: 39 + components: + - type: Transform + pos: 4.5,2.5 + parent: 2 + - uid: 40 + components: + - type: Transform + pos: 5.5,2.5 + parent: 2 + - uid: 41 + components: + - type: Transform + pos: 6.5,2.5 + parent: 2 + - uid: 42 + components: + - type: Transform + pos: 7.5,2.5 + parent: 2 + - uid: 43 + components: + - type: Transform + pos: 8.5,2.5 + parent: 2 + - uid: 44 + components: + - type: Transform + pos: 9.5,2.5 + parent: 2 + - uid: 45 + components: + - type: Transform + pos: 10.5,2.5 + parent: 2 + - uid: 46 + components: + - type: Transform + pos: 11.5,2.5 + parent: 2 +- proto: WallDiamond + entities: + - uid: 53 + components: + - type: Transform + pos: 2.5,1.5 + parent: 2 + - uid: 54 + components: + - type: Transform + pos: 2.5,0.5 + parent: 2 +- proto: WallMeat + entities: + - uid: 57 + components: + - type: Transform + pos: 9.5,1.5 + parent: 2 + - uid: 58 + components: + - type: Transform + pos: 9.5,0.5 + parent: 2 +... diff --git a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml index fe5ec44f257..9554207021c 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/identification_cards.yml @@ -289,6 +289,19 @@ - state: idjanitor - type: PresetIdCard job: Janitor + - type: Scp914Upgradable + upgradeOptions: + Rough: + - item: null + Coarse: + - item: null + Fine: + - item: ResearchIDCard + VeryFine: + - item: ResearchIDCard + chance: 0.5 + - item: RDIDCard + chance: 0.5 - type: entity parent: IDCardStandard diff --git a/Resources/Prototypes/_Scp/Entities/Objects/scp914.yml b/Resources/Prototypes/_Scp/Entities/Objects/scp914.yml new file mode 100644 index 00000000000..8f6ce12b746 --- /dev/null +++ b/Resources/Prototypes/_Scp/Entities/Objects/scp914.yml @@ -0,0 +1,185 @@ +- type: entity + id: Scp914Main + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _Scp/scp-914/64x64.rsi + state: center + offset: 0, 0.5 + - type: InteractionOutline + - type: Clickable + - type: Scp914 + - type: UserInterface + interfaces: + enum.Scp914UiKey.Key: + type: Scp914BoundUserInterface + +- type: entity + id: Scp914ContainerBase + categories: [ HideSpawnMenu ] + placement: + mode: SnapgridCenter + components: + - type: Sprite + offset: 0, 0.5 + - type: ItemSlots + - type: Clickable + - type: InteractionOutline + - type: EntityStorage + open: true + itemCanStoreMobs: false + - type: ContainerContainer + containers: + entity_storage: !type:Container + - type: Appearance + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.8" + density: 75 + mask: + - MachineMask + layer: + - MachineLayer + +- type: entity + id: Scp914Input + parent: Scp914ContainerBase + placement: + mode: SnapgridCenter + components: + - type: Scp914Container + containerType: Input + - type: EntityStorageVisuals + stateBaseClosed: left-door + stateDoorOpen: left-door + stateDoorClosed: door + - type: Sprite + noRot: true + sprite: _Scp/scp-914/32x32.rsi + offset: 0, 0.5 + layers: + - state: left-door + map: [ "enum.StorageVisualLayers.Base" ] + - state: door + map: [ "enum.StorageVisualLayers.Door" ] + +- type: entity + id: Scp914Output + parent: Scp914ContainerBase + placement: + mode: SnapgridCenter + components: + - type: Scp914Container + containerType: Output + - type: EntityStorageVisuals + stateBaseClosed: right-door + stateDoorOpen: right-door + stateDoorClosed: door + - type: Sprite + noRot: true + sprite: _Scp/scp-914/32x32.rsi + offset: 0, 0.5 + layers: + - state: right-door + map: [ "enum.StorageVisualLayers.Base" ] + - state: door + map: [ "enum.StorageVisualLayers.Door" ] + +- type: entity + id: Scp914InputPipe + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _Scp/scp-914/32x32.rsi + state: input + drawDepth: 10 + offset: 0, 0.5 + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.48,0.25,0.48" + density: 75 + mask: + - MachineMask + layer: + - MachineLayer + +- type: entity + id: Scp914OutputPipe + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _Scp/scp-914/32x32.rsi + state: output + offset: 0, 0.5 + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.25,-0.48,0.25,0.48" + density: 75 + mask: + - MachineMask + layer: + - MachineLayer + +- type: entity + id: Scp914Left + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _Scp/scp-914/32x32.rsi + state: left + offset: 0, 0.5 + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.8" + density: 75 + mask: + - MachineMask + layer: + - MachineLayer + +- type: entity + id: Scp914Right + placement: + mode: SnapgridCenter + components: + - type: Sprite + sprite: _Scp/scp-914/32x32.rsi + state: right + offset: 0, 0.5 + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.8" + density: 75 + mask: + - MachineMask + layer: + - MachineLayer diff --git a/Resources/Prototypes/_Scp/ScpEscapeMode/spawn_points.yml b/Resources/Prototypes/_Scp/ScpEscapeMode/spawn_points.yml new file mode 100644 index 00000000000..55c88a0df41 --- /dev/null +++ b/Resources/Prototypes/_Scp/ScpEscapeMode/spawn_points.yml @@ -0,0 +1,123 @@ +- type: entity + id: BaseSlSpawnPoints + abstract: true + noSpawn: true + parent: MarkerBase + components: + - type: Marker + - type: Sprite + layers: + - state: blue + +# Humanoids +- type: entity + id: SlMogSpawnPoint + parent: BaseSlSpawnPoints + components: + - type: Sprite + layers: + - sprite: _Scp/ScpSl/spawn-points.rsi + state: m + - type: SlHumanoidSpawnPoint + spawnPointType: Mog + +- type: entity + id: SlChaosSpawnPoint + parent: BaseSlSpawnPoints + components: + - type: Sprite + layers: + - sprite: _Scp/ScpSl/spawn-points.rsi + state: c + - type: SlHumanoidSpawnPoint + spawnPointType: Chaos + +- type: entity + id: SlClassDSpawnPoint + parent: BaseSlSpawnPoints + components: + - type: Sprite + layers: + - sprite: _Scp/ScpSl/spawn-points.rsi + state: d + - type: SlHumanoidSpawnPoint + spawnPointType: ClassD + +- type: entity + id: SlScientistSpawnPoint + parent: BaseSlSpawnPoints + components: + - type: Sprite + layers: + - sprite: _Scp/ScpSl/spawn-points.rsi + state: e + - type: SlHumanoidSpawnPoint + spawnPointType: Scientist + +- type: entity + id: SlSecuritySpawnPoint + parent: BaseSlSpawnPoints + components: + - type: Sprite + layers: + - sprite: _Scp/ScpSl/spawn-points.rsi + state: s + - type: SlHumanoidSpawnPoint + spawnPointType: Security + +# SCP +- type: entity + id: Sl096SpawnPoint + parent: BaseSlSpawnPoints + components: + - type: SlScpSpawnPoint + scpProtoId: Scp096 + playable: true + +- type: entity + id: Sl939ASpawnPoint + parent: BaseSlSpawnPoints + components: + - type: SlScpSpawnPoint + scpProtoId: Scp939 + playable: true + +- type: entity + id: Sl939BSpawnPoint + parent: BaseSlSpawnPoints + components: + - type: SlScpSpawnPoint + scpProtoId: Scp939 + playable: true + +- type: entity + id: Sl106SpawnPoints + parent: BaseSlSpawnPoints + components: + - type: SlScpSpawnPoint + scpProtoId: Scp106 + playable: true + +- type: entity + id: Sl173SpawnPoints + parent: BaseSlSpawnPoints + components: + - type: SlScpSpawnPoint + scpProtoId: Scp173 + playable: true + +- type: entity + id: Sl035SpawnPoints + parent: BaseSlSpawnPoints + components: + - type: SlScpSpawnPoint + scpProtoId: ClothingMaskScp035 + playable: false + +- type: entity + id: Sl500SpawnPoints + parent: BaseSlSpawnPoints + components: + - type: SlScpSpawnPoint + scpProtoId: PillCanisterScp500 + playable: false diff --git a/Resources/Prototypes/_Scp/ScpEscapeMode/triggers.yml b/Resources/Prototypes/_Scp/ScpEscapeMode/triggers.yml new file mode 100644 index 00000000000..97d8cdc3a9d --- /dev/null +++ b/Resources/Prototypes/_Scp/ScpEscapeMode/triggers.yml @@ -0,0 +1,34 @@ +- type: entity + id: BaseEscapeZone + name: EscapeZone + abstract: true + noSpawn: true + parent: MarkerBase + components: + - type: Sprite + - type: Marker + - type: Physics + bodyType: Static + - type: Fixtures + fixtures: + fix1: + shape: + !type:PhysShapeAabb + bounds: "-0.5,-0.5,0.5,0.5" + mask: + - MobMask + layer: + - MobMask + hard: false + - type: ScpSlEscapeZone + zoneType: Mog + +- type: entity + parent: BaseEscapeZone + id: SlEscapeZone + components: + - type: ScpSlEscapeZone + zoneType: Mog + - type: Sprite + layers: + - state: blue diff --git a/Resources/Prototypes/_Scp/SlGamemode/RandomHumanoidProfiles/sl_humanoid_profiles.yml b/Resources/Prototypes/_Scp/SlGamemode/RandomHumanoidProfiles/sl_humanoid_profiles.yml new file mode 100644 index 00000000000..37be0e1e329 --- /dev/null +++ b/Resources/Prototypes/_Scp/SlGamemode/RandomHumanoidProfiles/sl_humanoid_profiles.yml @@ -0,0 +1,53 @@ +- type: randomHumanoidSettings + id: ClassDHumanoidSettings + randomizeName: true + components: + - type: Loadout + prototypes: [ SlClassDGear] + - type: ScpSlHumanoidMarker + humanoidType: ClassD + +- type: randomHumanoidSettings + id: ScientistHumanoidSettings + randomizeName: true + components: + - type: Loadout + prototypes: [ SlScientistGear ] + - type: ScpSlHumanoidMarker + humanoidType: Scientist + +- type: randomHumanoidSettings + id: SecurityHumanoidSettings + randomizeName: true + components: + - type: Loadout + prototypes: [ SlSecurityGear ] + - type: ScpSlHumanoidMarker + humanoidType: Security + +- type: randomHumanoidSettings + id: MogHumanoidSettings + randomizeName: true + components: + - type: Loadout + prototypes: [ SlMogGear ] + - type: ScpSlHumanoidMarker + humanoidType: Mog + +- type: randomHumanoidSettings + id: MogCaptainSettings + randomizeName: true + components: + - type: Loadout + prototypes: [ SlMogCaptainGear ] + - type: ScpSlHumanoidMarker + humanoidType: Mog + +- type: randomHumanoidSettings + id: ChaosHumanoidSettings + randomizeName: true + components: + - type: Loadout + prototypes: [ SlChaosGear ] + - type: ScpSlHumanoidMarker + humanoidType: Chaos diff --git a/Resources/Prototypes/_Scp/SlGamemode/RandomHumanoidProfiles/sl_starting_gear.yml b/Resources/Prototypes/_Scp/SlGamemode/RandomHumanoidProfiles/sl_starting_gear.yml new file mode 100644 index 00000000000..35a5133f634 --- /dev/null +++ b/Resources/Prototypes/_Scp/SlGamemode/RandomHumanoidProfiles/sl_starting_gear.yml @@ -0,0 +1,85 @@ +- type: startingGear + id: SlClassDGear + equipment: + jumpsuit: ClothingUniformJumpsuitPrisoner + +- type: startingGear + id: SlScientistGear + equipment: + jumpsuit: ClothingUniformJumpsuitCapFormal + shoes: ClothingShoesBootsLaceup + eyes: ClothingEyesGlassesSunglasses + gloves: ClothingHandsGlovesCaptain + head: ClothingHeadHatCapcap + neck: ClothingNeckMantleCap + id: VisitorPDA + belt: WeaponDisabler + back: ClothingBackpackSatchelCaptain + ears: ClothingHeadsetAltCommand + outerClothing: ClothingOuterArmorCaptainCarapace + pocket1: WeaponDisabler + +- type: startingGear + id: SlSecurityGear + equipment: + jumpsuit: ClothingUniformJumpsuitCapFormal + shoes: ClothingShoesBootsLaceup + eyes: ClothingEyesGlassesSunglasses + gloves: ClothingHandsGlovesCaptain + head: ClothingHeadHatCapcap + neck: ClothingNeckMantleCap + id: VisitorPDA + belt: WeaponDisabler + back: ClothingBackpackSatchelCaptain + ears: ClothingHeadsetAltCommand + outerClothing: ClothingOuterArmorCaptainCarapace + pocket1: WeaponDisabler + +- type: startingGear + id: SlMogGear + equipment: + jumpsuit: ClothingUniformJumpsuitCapFormal + shoes: ClothingShoesBootsLaceup + eyes: ClothingEyesGlassesSunglasses + gloves: ClothingHandsGlovesCaptain + head: ClothingHeadHatCapcap + neck: ClothingNeckMantleCap + id: VisitorPDA + belt: WeaponDisabler + back: ClothingBackpackSatchelCaptain + ears: ClothingHeadsetAltCommand + outerClothing: ClothingOuterArmorCaptainCarapace + pocket1: WeaponDisabler + +- type: startingGear + id: SlChaosGear + equipment: + jumpsuit: ClothingUniformJumpsuitCapFormal + shoes: ClothingShoesBootsLaceup + eyes: ClothingEyesGlassesSunglasses + gloves: ClothingHandsGlovesCaptain + head: ClothingHeadHatCapcap + neck: ClothingNeckMantleCap + id: VisitorPDA + belt: WeaponDisabler + back: ClothingBackpackSatchelCaptain + ears: ClothingHeadsetAltCommand + outerClothing: ClothingOuterArmorCaptainCarapace + pocket1: WeaponDisabler + +- type: startingGear + id: SlMogCaptainGear + equipment: + jumpsuit: ClothingUniformJumpsuitCapFormal + shoes: ClothingShoesBootsLaceup + eyes: ClothingEyesGlassesSunglasses + gloves: ClothingHandsGlovesCaptain + head: ClothingHeadHatCapcap + neck: ClothingNeckMantleCap + id: VisitorPDA + belt: WeaponDisabler + back: ClothingBackpackSatchelCaptain + ears: ClothingHeadsetAltCommand + outerClothing: ClothingOuterArmorCaptainCarapace + pocket1: WeaponDisabler + diff --git a/Resources/Prototypes/_Scp/SlGamemode/gameRule.yml b/Resources/Prototypes/_Scp/SlGamemode/gameRule.yml new file mode 100644 index 00000000000..3ef7b5a840b --- /dev/null +++ b/Resources/Prototypes/_Scp/SlGamemode/gameRule.yml @@ -0,0 +1,34 @@ +- type: entity + parent: BaseGameRule + id: ScpSlGameRule + components: + - type: GameRule + minPlayers: 2 + - type: LoadMapRule + mapPath: /Maps/_Fire/sl_scp_test.yml + - type: ScpSlGameRule + humanoidPresets: + ClassD: + - ClassDHumanoidSettings + Scientist: + - ScientistHumanoidSettings + Security: + - SecurityHumanoidSettings + Mog: + - MogHumanoidSettings + Chaos: + - ChaosHumanoidSettings + mogCaptainPrototype: MogCaptainSettings + waveSpawnCooldown: 300 + +- type: gamePreset + id: ScpSl + alias: + - ScpSl + - scpsl + name: scpsl-title + description: scpsl-description + showInVote: false + hide: false + rules: + - ScpSlGameRule diff --git a/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/c.png b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/c.png new file mode 100644 index 00000000000..39855955cb3 Binary files /dev/null and b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/c.png differ diff --git a/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/d.png b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/d.png new file mode 100644 index 00000000000..29c424dabec Binary files /dev/null and b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/d.png differ diff --git a/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/e.png b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/e.png new file mode 100644 index 00000000000..46ed3e14769 Binary files /dev/null and b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/e.png differ diff --git a/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/m.png b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/m.png new file mode 100644 index 00000000000..6d48f64799b Binary files /dev/null and b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/m.png differ diff --git a/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/meta.json b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/meta.json new file mode 100644 index 00000000000..d3ffbd7dc40 --- /dev/null +++ b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/meta.json @@ -0,0 +1,26 @@ +{ + "version": 1, + "license": "CLA", + "copyright": "SUNRISE", + "size": { + "x": 32, + "y": 32 + }, + "states": [ + { + "name": "c" + }, + { + "name": "d" + }, + { + "name": "e" + }, + { + "name": "m" + }, + { + "name": "s" + } + ] +} diff --git a/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/s.png b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/s.png new file mode 100644 index 00000000000..26ebbf23813 Binary files /dev/null and b/Resources/Textures/_Scp/ScpSl/spawn-points.rsi/s.png differ diff --git a/Resources/Textures/_Scp/scp-914/32x32.rsi/door.png b/Resources/Textures/_Scp/scp-914/32x32.rsi/door.png new file mode 100644 index 00000000000..c6e6819c091 Binary files /dev/null and b/Resources/Textures/_Scp/scp-914/32x32.rsi/door.png differ diff --git a/Resources/Textures/_Scp/scp-914/32x32.rsi/input.png b/Resources/Textures/_Scp/scp-914/32x32.rsi/input.png new file mode 100644 index 00000000000..f252e23be01 Binary files /dev/null and b/Resources/Textures/_Scp/scp-914/32x32.rsi/input.png differ diff --git a/Resources/Textures/_Scp/scp-914/32x32.rsi/left-door.png b/Resources/Textures/_Scp/scp-914/32x32.rsi/left-door.png new file mode 100644 index 00000000000..a220c68e4d3 Binary files /dev/null and b/Resources/Textures/_Scp/scp-914/32x32.rsi/left-door.png differ diff --git a/Resources/Textures/_Scp/scp-914/32x32.rsi/left.png b/Resources/Textures/_Scp/scp-914/32x32.rsi/left.png new file mode 100644 index 00000000000..1e80f203690 Binary files /dev/null and b/Resources/Textures/_Scp/scp-914/32x32.rsi/left.png differ diff --git a/Resources/Textures/_Scp/scp-914/32x32.rsi/meta.json b/Resources/Textures/_Scp/scp-914/32x32.rsi/meta.json new file mode 100644 index 00000000000..85bbddb4b90 --- /dev/null +++ b/Resources/Textures/_Scp/scp-914/32x32.rsi/meta.json @@ -0,0 +1,30 @@ +{ + "version": 1, + "size": { + "x": 32, + "y": 64 + }, + "states": [ + { + "name": "input" + }, + { + "name": "output" + }, + { + "name": "door" + }, + { + "name": "left" + }, + { + "name": "left-door" + }, + { + "name": "right-door" + }, + { + "name": "right" + } + ] +} \ No newline at end of file diff --git a/Resources/Textures/_Scp/scp-914/32x32.rsi/output.png b/Resources/Textures/_Scp/scp-914/32x32.rsi/output.png new file mode 100644 index 00000000000..2d2eafba12b Binary files /dev/null and b/Resources/Textures/_Scp/scp-914/32x32.rsi/output.png differ diff --git a/Resources/Textures/_Scp/scp-914/32x32.rsi/right-door.png b/Resources/Textures/_Scp/scp-914/32x32.rsi/right-door.png new file mode 100644 index 00000000000..30be5d232ed Binary files /dev/null and b/Resources/Textures/_Scp/scp-914/32x32.rsi/right-door.png differ diff --git a/Resources/Textures/_Scp/scp-914/32x32.rsi/right.png b/Resources/Textures/_Scp/scp-914/32x32.rsi/right.png new file mode 100644 index 00000000000..3568a713b7f Binary files /dev/null and b/Resources/Textures/_Scp/scp-914/32x32.rsi/right.png differ diff --git a/Resources/Textures/_Scp/scp-914/64x64.rsi/center.png b/Resources/Textures/_Scp/scp-914/64x64.rsi/center.png new file mode 100644 index 00000000000..9f31c994f69 Binary files /dev/null and b/Resources/Textures/_Scp/scp-914/64x64.rsi/center.png differ diff --git a/Resources/Textures/_Scp/scp-914/64x64.rsi/meta.json b/Resources/Textures/_Scp/scp-914/64x64.rsi/meta.json new file mode 100644 index 00000000000..9f7f6747ca1 --- /dev/null +++ b/Resources/Textures/_Scp/scp-914/64x64.rsi/meta.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "size": { + "x": 64, + "y": 64 + }, + "states": [ + { + "name": "center" + } + ] +} \ No newline at end of file