diff --git a/Content.Client/Points/PointSystem.cs b/Content.Client/Points/PointSystem.cs
new file mode 100644
index 0000000000..f41c4b09ab
--- /dev/null
+++ b/Content.Client/Points/PointSystem.cs
@@ -0,0 +1,58 @@
+using Content.Client.CharacterInfo;
+using Content.Client.Message;
+using Content.Shared.Points;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Shared.GameStates;
+
+namespace Content.Client.Points;
+
+///
+public sealed class PointSystem : SharedPointSystem
+{
+ [Dependency] private readonly CharacterInfoSystem _characterInfo = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnHandleState);
+ SubscribeLocalEvent(OnGetCharacterInfoControls);
+ }
+
+ private void OnHandleState(EntityUid uid, PointManagerComponent component, ref ComponentHandleState args)
+ {
+ if (args.Current is not PointManagerComponentState state)
+ return;
+
+ component.Points = new(state.Points);
+ component.Scoreboard = state.Scoreboard;
+ _characterInfo.RequestCharacterInfo();
+ }
+
+ private void OnGetCharacterInfoControls(ref CharacterInfoSystem.GetCharacterInfoControlsEvent ev)
+ {
+ foreach (var point in EntityQuery())
+ {
+ var box = new BoxContainer
+ {
+ Margin = new Thickness(5),
+ Orientation = BoxContainer.LayoutOrientation.Vertical
+ };
+
+ var title = new RichTextLabel
+ {
+ HorizontalAlignment = Control.HAlignment.Center
+ };
+ title.SetMarkup(Loc.GetString("point-scoreboard-header"));
+
+ var text = new RichTextLabel();
+ text.SetMessage(point.Scoreboard);
+
+ box.AddChild(title);
+ box.AddChild(text);
+ ev.Controls.Add(box);
+ }
+ }
+}
diff --git a/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs b/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs
index a09b458685..71a06d58ee 100644
--- a/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs
+++ b/Content.Client/UserInterface/Systems/Character/CharacterUIController.cs
@@ -109,6 +109,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered
-
+
diff --git a/Content.IntegrationTests/Tests/PostMapInitTest.cs b/Content.IntegrationTests/Tests/PostMapInitTest.cs
index 455426824f..6f3bdcc524 100644
--- a/Content.IntegrationTests/Tests/PostMapInitTest.cs
+++ b/Content.IntegrationTests/Tests/PostMapInitTest.cs
@@ -60,7 +60,8 @@ namespace Content.IntegrationTests.Tests
"Saltern",
"Core",
"Marathon",
- "Kettle"
+ "Kettle",
+ "MeteorArena"
};
///
diff --git a/Content.Server/Chat/SuicideSystem.cs b/Content.Server/Chat/SuicideSystem.cs
index 25819082ff..2f6ac51d72 100644
--- a/Content.Server/Chat/SuicideSystem.cs
+++ b/Content.Server/Chat/SuicideSystem.cs
@@ -80,7 +80,7 @@ namespace Content.Server.Chat
/// Returns true if there was a blocked attempt
private bool SuicideAttemptBlocked(EntityUid victim, SuicideEvent suicideEvent)
{
- RaiseLocalEvent(victim, suicideEvent, false);
+ RaiseLocalEvent(victim, suicideEvent, true);
if (suicideEvent.AttemptBlocked)
return true;
diff --git a/Content.Server/GameTicking/GameTicker.Player.cs b/Content.Server/GameTicking/GameTicker.Player.cs
index 6eb35be528..837410db42 100644
--- a/Content.Server/GameTicking/GameTicker.Player.cs
+++ b/Content.Server/GameTicking/GameTicker.Player.cs
@@ -144,9 +144,10 @@ namespace Content.Server.GameTicking
return (HumanoidCharacterProfile) _prefsManager.GetPreferences(p.UserId).SelectedCharacter;
}
- public void PlayerJoinGame(IPlayerSession session)
+ public void PlayerJoinGame(IPlayerSession session, bool silent = false)
{
- _chatManager.DispatchServerMessage(session, Loc.GetString("game-ticker-player-join-game-message"));
+ if (!silent)
+ _chatManager.DispatchServerMessage(session, Loc.GetString("game-ticker-player-join-game-message"));
_playerGameStatuses[session.UserId] = PlayerGameStatus.JoinedGame;
_db.AddRoundPlayers(RoundId, session.UserId);
diff --git a/Content.Server/GameTicking/GameTicker.RoundFlow.cs b/Content.Server/GameTicking/GameTicker.RoundFlow.cs
index 00515c614c..83df72d2a0 100644
--- a/Content.Server/GameTicking/GameTicker.RoundFlow.cs
+++ b/Content.Server/GameTicking/GameTicker.RoundFlow.cs
@@ -117,7 +117,7 @@ namespace Content.Server.GameTicking
if (CurrentPreset?.MapPool != null &&
_prototypeManager.TryIndex(CurrentPreset.MapPool, out var pool) &&
- pool.Maps.Contains(mainStationMap.ID))
+ !pool.Maps.Contains(mainStationMap.ID))
{
var msg = Loc.GetString("game-ticker-start-round-invalid-map",
("map", mainStationMap.MapName),
diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs
index 8dca5c20bc..db4d12ada9 100644
--- a/Content.Server/GameTicking/GameTicker.Spawning.cs
+++ b/Content.Server/GameTicking/GameTicker.Spawning.cs
@@ -104,7 +104,7 @@ namespace Content.Server.GameTicking
RaiseLocalEvent(new RulePlayerJobsAssignedEvent(assignedJobs.Keys.Select(x => _playerManager.GetSessionByUserId(x)).ToArray(), profiles, force));
}
- private void SpawnPlayer(IPlayerSession player, EntityUid station, string? jobId = null, bool lateJoin = true)
+ private void SpawnPlayer(IPlayerSession player, EntityUid station, string? jobId = null, bool lateJoin = true, bool silent = false)
{
var character = GetPlayerProfile(player);
@@ -114,10 +114,10 @@ namespace Content.Server.GameTicking
if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId))
return;
- SpawnPlayer(player, character, station, jobId, lateJoin);
+ SpawnPlayer(player, character, station, jobId, lateJoin, silent);
}
- private void SpawnPlayer(IPlayerSession player, HumanoidCharacterProfile character, EntityUid station, string? jobId = null, bool lateJoin = true)
+ private void SpawnPlayer(IPlayerSession player, HumanoidCharacterProfile character, EntityUid station, string? jobId = null, bool lateJoin = true, bool silent = false)
{
// Can't spawn players with a dummy ticker!
if (DummyTicker)
@@ -150,7 +150,7 @@ namespace Content.Server.GameTicking
// Do nothing, something else has handled spawning this player for us!
if (bev.Handled)
{
- PlayerJoinGame(player);
+ PlayerJoinGame(player, silent);
return;
}
@@ -177,7 +177,7 @@ namespace Content.Server.GameTicking
return;
}
- PlayerJoinGame(player);
+ PlayerJoinGame(player, silent);
var data = player.ContentData();
@@ -188,7 +188,7 @@ namespace Content.Server.GameTicking
var jobPrototype = _prototypeManager.Index(jobId);
var job = new JobComponent { PrototypeId = jobId };
- _roles.MindAddRole(newMind, job);
+ _roles.MindAddRole(newMind, job, silent: silent);
var jobName = _jobs.MindTryGetJobName(newMind);
_playTimeTrackings.PlayerRolesChanged(player);
@@ -199,7 +199,7 @@ namespace Content.Server.GameTicking
_mind.TransferTo(newMind, mob);
- if (lateJoin)
+ if (lateJoin && !silent)
{
_chatSystem.DispatchStationAnnouncement(station,
Loc.GetString(
@@ -230,7 +230,7 @@ namespace Content.Server.GameTicking
_chatManager.DispatchServerMessage(player, Loc.GetString("job-greet-crew-shortages"));
}
- if (TryComp(station, out MetaDataComponent? metaData))
+ if (!silent && TryComp(station, out MetaDataComponent? metaData))
{
_chatManager.DispatchServerMessage(player,
Loc.GetString("job-greet-station-name", ("stationName", metaData.EntityName)));
@@ -238,7 +238,7 @@ namespace Content.Server.GameTicking
// Arrivals is unable to do this during spawning as no actor is attached yet.
// We also want this message last.
- if (lateJoin && _arrivals.Enabled)
+ if (!silent && lateJoin && _arrivals.Enabled)
{
var arrival = _arrivals.NextShuttleArrival();
if (arrival == null)
@@ -269,7 +269,14 @@ namespace Content.Server.GameTicking
SpawnPlayer(player, EntityUid.Invalid);
}
- public void MakeJoinGame(IPlayerSession player, EntityUid station, string? jobId = null)
+ ///
+ /// Makes a player join into the game and spawn on a staiton.
+ ///
+ /// The player joining
+ /// The station they're spawning on
+ /// An optional job for them to spawn as
+ /// Whether or not the player should be greeted upon joining
+ public void MakeJoinGame(IPlayerSession player, EntityUid station, string? jobId = null, bool silent = false)
{
if (!_playerGameStatuses.ContainsKey(player.UserId))
return;
@@ -277,7 +284,7 @@ namespace Content.Server.GameTicking
if (!_userDb.IsLoadComplete(player))
return;
- SpawnPlayer(player, station, jobId);
+ SpawnPlayer(player, station, jobId, silent: silent);
}
///
diff --git a/Content.Server/GameTicking/Rules/Components/DeathMatchRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/DeathMatchRuleComponent.cs
index 17f46ba2f5..cb2c413870 100644
--- a/Content.Server/GameTicking/Rules/Components/DeathMatchRuleComponent.cs
+++ b/Content.Server/GameTicking/Rules/Components/DeathMatchRuleComponent.cs
@@ -1,33 +1,46 @@
-namespace Content.Server.GameTicking.Rules.Components;
+using Content.Shared.FixedPoint;
+using Content.Shared.Roles;
+using Content.Shared.Storage;
+using Robust.Shared.Network;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototype;
+
+namespace Content.Server.GameTicking.Rules.Components;
///
-/// Simple GameRule that will do a free-for-all death match.
-/// Kill everybody else to win.
+/// Gamerule that ends when a player gets a certain number of kills.
///
[RegisterComponent, Access(typeof(DeathMatchRuleSystem))]
public sealed partial class DeathMatchRuleComponent : Component
{
+ ///
+ /// The number of points a player has to get to win.
+ ///
+ [DataField("killCap"), ViewVariables(VVAccess.ReadWrite)]
+ public FixedPoint2 KillCap = 31;
+
///
/// How long until the round restarts
///
[DataField("restartDelay"), ViewVariables(VVAccess.ReadWrite)]
- public float RestartDelay = 10f;
+ public TimeSpan RestartDelay = TimeSpan.FromSeconds(10f);
///
- /// How long after a person dies will the restart be checked
+ /// The person who won.
+ /// We store this here in case of some assist shenanigans.
///
- [DataField("deadCheckDelay"), ViewVariables(VVAccess.ReadWrite)]
- public float DeadCheckDelay = 5f;
+ [DataField("victor")]
+ public NetUserId? Victor;
///
- /// A timer for checking after a death
+ /// An entity spawned after a player is killed.
///
- [DataField("deadCheckTimer"), ViewVariables(VVAccess.ReadWrite)]
- public float? DeadCheckTimer;
+ [DataField("rewardSpawns")]
+ public List RewardSpawns = new();
///
- /// A timer for the restart.
+ /// The gear all players spawn with.
///
- [DataField("restartTimer"), ViewVariables(VVAccess.ReadWrite)]
- public float? RestartTimer;
+ [DataField("gear", customTypeSerializer: typeof(PrototypeIdSerializer)), ViewVariables(VVAccess.ReadWrite)]
+ public string Gear = "DeathMatchGear";
}
diff --git a/Content.Server/GameTicking/Rules/Components/KillCalloutRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/KillCalloutRuleComponent.cs
new file mode 100644
index 0000000000..e82a952290
--- /dev/null
+++ b/Content.Server/GameTicking/Rules/Components/KillCalloutRuleComponent.cs
@@ -0,0 +1,32 @@
+namespace Content.Server.GameTicking.Rules.Components;
+
+///
+/// This is used for a rule that announces kills globally.
+///
+[RegisterComponent, Access(typeof(KillCalloutRuleSystem))]
+public sealed partial class KillCalloutRuleComponent : Component
+{
+ ///
+ /// Root used to generate kill callouts
+ ///
+ [DataField("killCalloutPrefix")]
+ public string KillCalloutPrefix = "death-match-kill-callout-";
+
+ ///
+ /// A value used to randomly select a kill callout
+ ///
+ [DataField("killCalloutAmount")]
+ public int KillCalloutAmount = 60;
+
+ ///
+ /// Root used to generate kill callouts when a player is killed by the environment
+ ///
+ [DataField("environmentKillCallouts")]
+ public string SelfKillCalloutPrefix = "death-match-kill-callout-env-";
+
+ ///
+ /// A value used to randomly select a kill callout when a player is killed by the environment
+ ///
+ [DataField("selfKillCalloutAmount")]
+ public int SelfKillCalloutAmount = 10;
+}
diff --git a/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs b/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs
new file mode 100644
index 0000000000..fafe811dd9
--- /dev/null
+++ b/Content.Server/GameTicking/Rules/Components/RespawnDeadRuleComponent.cs
@@ -0,0 +1,9 @@
+namespace Content.Server.GameTicking.Rules.Components;
+
+///
+/// This is used for gamemodes that automatically respawn players when they're no longer alive.
+///
+[RegisterComponent, Access(typeof(RespawnRuleSystem))]
+public sealed partial class RespawnDeadRuleComponent : Component
+{
+}
diff --git a/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs b/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs
new file mode 100644
index 0000000000..3d338c2d13
--- /dev/null
+++ b/Content.Server/GameTicking/Rules/Components/RespawnTrackerComponent.cs
@@ -0,0 +1,30 @@
+using Robust.Shared.Network;
+
+namespace Content.Server.GameTicking.Rules.Components;
+
+///
+/// This is used for globally tracking players that need to be respawned.
+/// Used on gamerule entities.
+///
+[RegisterComponent, Access(typeof(RespawnRuleSystem))]
+public sealed partial class RespawnTrackerComponent : Component
+{
+ ///
+ /// A list of the people that should be respawned.
+ /// Used to make sure that we don't respawn aghosts or observers.
+ ///
+ [DataField("players")]
+ public HashSet Players = new();
+
+ ///
+ /// The delay between dying and respawning.
+ ///
+ [DataField("respawnDelay")]
+ public TimeSpan RespawnDelay = TimeSpan.Zero;
+
+ ///
+ /// A dictionary of player netuserids and when they will respawn.
+ ///
+ [DataField("respawnQueue")]
+ public Dictionary RespawnQueue = new();
+}
diff --git a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs
index 79c3394d11..042455e75d 100644
--- a/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs
+++ b/Content.Server/GameTicking/Rules/DeathMatchRuleSystem.cs
@@ -1,12 +1,14 @@
-using Content.Server.Chat.Managers;
+using Content.Server.Administration.Commands;
using Content.Server.GameTicking.Rules.Components;
-using Content.Shared.CCVar;
-using Content.Shared.Damage;
-using Content.Shared.Mobs.Components;
-using Content.Shared.Mobs.Systems;
+using Content.Server.KillTracking;
+using Content.Server.Mind;
+using Content.Server.Points;
+using Content.Server.RoundEnd;
+using Content.Server.Station.Systems;
+using Content.Shared.Points;
+using Content.Shared.Storage;
using Robust.Server.Player;
-using Robust.Shared.Configuration;
-using Robust.Shared.Enums;
+using Robust.Shared.Utility;
namespace Content.Server.GameTicking.Rules;
@@ -15,116 +17,116 @@ namespace Content.Server.GameTicking.Rules;
///
public sealed class DeathMatchRuleSystem : GameRuleSystem
{
- [Dependency] private readonly IPlayerManager _playerManager = default!;
- [Dependency] private readonly IChatManager _chatManager = default!;
- [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
- [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly MindSystem _mind = default!;
+ [Dependency] private readonly PointSystem _point = default!;
+ [Dependency] private readonly RespawnRuleSystem _respawn = default!;
+ [Dependency] private readonly RoundEndSystem _roundEnd = default!;
+ [Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnHealthChanged);
- _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
+ SubscribeLocalEvent(OnBeforeSpawn);
+ SubscribeLocalEvent(OnSpawnComplete);
+ SubscribeLocalEvent(OnKillReported);
+ SubscribeLocalEvent(OnPointChanged);
+ SubscribeLocalEvent(OnRoundEndTextAppend);
}
- public override void Shutdown()
+ private void OnBeforeSpawn(PlayerBeforeSpawnEvent ev)
{
- base.Shutdown();
- _playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
- }
-
- protected override void Started(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
- {
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
-
- }
-
- protected override void Ended(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, GameRuleEndedEvent args)
- {
- base.Ended(uid, component, gameRule, args);
-
- component.DeadCheckTimer = null;
- component.RestartTimer = null;
-
- }
-
- private void OnHealthChanged(DamageChangedEvent _)
- {
- RunDelayedCheck();
- }
-
- private void OnPlayerStatusChanged(object? ojb, SessionStatusEventArgs e)
- {
- if (e.NewStatus == SessionStatus.Disconnected)
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var dm, out var tracker, out var point, out var rule))
{
- RunDelayedCheck();
- }
- }
-
- private void RunDelayedCheck()
- {
- var query = EntityQueryEnumerator();
- while (query.MoveNext(out var uid, out var deathMatch, out var gameRule))
- {
- if (!GameTicker.IsGameRuleActive(uid, gameRule) || deathMatch.DeadCheckTimer != null)
+ if (!GameTicker.IsGameRuleActive(uid, rule))
continue;
- deathMatch.DeadCheckTimer = deathMatch.DeadCheckDelay;
+ var newMind = _mind.CreateMind(ev.Player.UserId, ev.Profile.Name);
+ _mind.SetUserId(newMind, ev.Player.UserId);
+
+ var mobMaybe = _stationSpawning.SpawnPlayerCharacterOnStation(ev.Station, null, ev.Profile);
+ DebugTools.AssertNotNull(mobMaybe);
+ var mob = mobMaybe!.Value;
+
+ _mind.TransferTo(newMind, mob);
+ SetOutfitCommand.SetOutfit(mob, dm.Gear, EntityManager);
+ EnsureComp(mob);
+ _respawn.AddToTracker(ev.Player.UserId, uid, tracker);
+
+ _point.EnsurePlayer(ev.Player.UserId, uid, point);
+
+ ev.Handled = true;
+ break;
}
}
- protected override void ActiveTick(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, float frameTime)
+ private void OnSpawnComplete(PlayerSpawnCompleteEvent ev)
{
- base.ActiveTick(uid, component, gameRule, frameTime);
-
- // If the restart timer is active, that means the round is ending soon, no need to check for winners.
- // TODO: We probably want a sane, centralized round end thingie in GameTicker, RoundEndSystem is no good...
- if (component.RestartTimer != null)
+ EnsureComp(ev.Mob);
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out _, out var tracker, out var rule))
{
- component.RestartTimer -= frameTime;
-
- if (component.RestartTimer > 0f)
- return;
-
- GameTicker.EndRound();
- GameTicker.RestartRound();
- return;
+ if (!GameTicker.IsGameRuleActive(uid, rule))
+ continue;
+ _respawn.AddToTracker(ev.Mob, uid, tracker);
}
+ }
- if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin) || component.DeadCheckTimer == null)
- return;
-
- component.DeadCheckTimer -= frameTime;
-
- if (component.DeadCheckTimer > 0)
- return;
-
- component.DeadCheckTimer = null;
-
- IPlayerSession? winner = null;
- foreach (var playerSession in _playerManager.ServerSessions)
+ private void OnKillReported(ref KillReportedEvent ev)
+ {
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var dm, out var point, out var rule))
{
- if (playerSession.AttachedEntity is not { Valid: true } playerEntity
- || !TryComp(playerEntity, out MobStateComponent? state))
+ if (!GameTicker.IsGameRuleActive(uid, rule))
continue;
- if (!_mobStateSystem.IsAlive(playerEntity, state))
+ // YOU SUICIDED OR GOT THROWN INTO LAVA!
+ // WHAT A GIANT FUCKING NERD! LAUGH NOW!
+ if (ev.Primary is not KillPlayerSource player)
+ {
+ _point.AdjustPointValue(ev.Entity, -1, uid, point);
+ continue;
+ }
+
+ _point.AdjustPointValue(player.PlayerId, 1, uid, point);
+
+ if (ev.Assist is KillPlayerSource assist && dm.Victor == null)
+ _point.AdjustPointValue(assist.PlayerId, 1, uid, point);
+
+ var spawns = EntitySpawnCollection.GetSpawns(dm.RewardSpawns);
+ EntityManager.SpawnEntities(Transform(ev.Entity).MapPosition, spawns);
+ }
+ }
+
+ private void OnPointChanged(EntityUid uid, DeathMatchRuleComponent component, ref PlayerPointChangedEvent args)
+ {
+ if (component.Victor != null)
+ return;
+
+ if (args.Points < component.KillCap)
+ return;
+
+ component.Victor = args.Player;
+ _roundEnd.EndRound(component.RestartDelay);
+ }
+
+ private void OnRoundEndTextAppend(RoundEndTextAppendEvent ev)
+ {
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var dm, out var point, out var rule))
+ {
+ if (!GameTicker.IsGameRuleAdded(uid, rule))
continue;
- // Found a second person alive, nothing decided yet!
- if (winner != null)
- return;
-
- winner = playerSession;
+ 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());
}
-
- _chatManager.DispatchServerAnnouncement(winner == null
- ? Loc.GetString("rule-death-match-check-winner-stalemate")
- : Loc.GetString("rule-death-match-check-winner", ("winner", winner)));
-
- _chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",
- ("seconds", component.RestartDelay)));
- component.RestartTimer = component.RestartDelay;
}
}
diff --git a/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs
new file mode 100644
index 0000000000..94eeb5de56
--- /dev/null
+++ b/Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs
@@ -0,0 +1,99 @@
+using Content.Server.Chat.Managers;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.KillTracking;
+using Content.Shared.Chat;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+using Robust.Shared.Random;
+
+namespace Content.Server.GameTicking.Rules;
+
+///
+/// This handles calling out kills from
+///
+public sealed class KillCalloutRuleSystem : GameRuleSystem
+{
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnKillReported);
+ }
+
+ private void OnKillReported(ref KillReportedEvent ev)
+ {
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out var kill, out var rule))
+ {
+ if (!GameTicker.IsGameRuleActive(uid, rule))
+ continue;
+
+ var callout = GetCallout(kill, ev);
+ _chatManager.ChatMessageToAll(ChatChannel.Server, callout, callout, uid, false, true, Color.OrangeRed);
+ }
+ }
+
+ private string GetCallout(KillCalloutRuleComponent component, KillReportedEvent ev)
+ {
+ // Do the humiliation callouts if you kill yourself or die from bleeding out or something lame.
+ if (ev.Primary is KillEnvironmentSource || ev.Suicide)
+ {
+ var selfCallout = $"{component.SelfKillCalloutPrefix}{_random.Next(component.SelfKillCalloutAmount)}";
+ return Loc.GetString(selfCallout,
+ ("victim", GetCalloutName(ev.Entity)));
+ }
+
+ var primary = GetCalloutName(ev.Primary);
+ var killerString = primary;
+ if (ev.Assist != null)
+ {
+ var secondary = GetCalloutName(ev.Assist);
+ killerString = Loc.GetString("death-match-assist",
+ ("primary", primary), ("secondary", secondary));
+ }
+
+ var callout = $"{component.KillCalloutPrefix}{_random.Next(component.KillCalloutAmount)}";
+ return Loc.GetString(callout, ("killer", killerString),
+ ("victim", GetCalloutName(ev.Entity)));
+ }
+
+ private string GetCalloutName(KillSource source)
+ {
+ switch (source)
+ {
+ case KillPlayerSource player:
+ if (!_playerManager.TryGetSessionById(player.PlayerId, out var session))
+ break;
+ if (session.AttachedEntity == null)
+ break;
+
+ return Loc.GetString("death-match-name-player",
+ ("name", MetaData(session.AttachedEntity.Value).EntityName),
+ ("username", session.Name));
+
+ case KillNpcSource npc:
+ if (Deleted(npc.NpcEnt))
+ return string.Empty;
+ return Loc.GetString("death-match-name-npc", ("name", MetaData(npc.NpcEnt).EntityName));
+ }
+
+ return string.Empty;
+ }
+
+ private string GetCalloutName(EntityUid source)
+ {
+ if (TryComp(source, out var actorComp))
+ {
+ return Loc.GetString("death-match-name-player",
+ ("name", MetaData(source).EntityName),
+ ("username", actorComp.PlayerSession.Name));
+ }
+
+ return Loc.GetString("death-match-name-npc", ("name", MetaData(source).EntityName));
+ }
+}
diff --git a/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs
new file mode 100644
index 0000000000..5f36dcd7d1
--- /dev/null
+++ b/Content.Server/GameTicking/Rules/RespawnRuleSystem.cs
@@ -0,0 +1,145 @@
+using Content.Server.Chat.Managers;
+using Content.Server.GameTicking.Rules.Components;
+using Content.Server.Mind;
+using Content.Server.Mind.Components;
+using Content.Server.Mind.Toolshed;
+using Content.Server.Players;
+using Content.Server.Station.Systems;
+using Content.Shared.Chat;
+using Content.Shared.Interaction.Events;
+using Content.Shared.Mobs;
+using Robust.Server.GameObjects;
+using Robust.Server.Player;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Server.GameTicking.Rules;
+
+///
+/// This handles logic and interactions related to
+///
+public sealed class RespawnRuleSystem : GameRuleSystem
+{
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly StationSystem _station = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnSuicide);
+ SubscribeLocalEvent(OnMobStateChanged);
+ }
+
+ private void OnSuicide(SuicideEvent ev)
+ {
+ if (!TryComp(ev.Victim, out var actor))
+ return;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out _, out var respawn))
+ {
+ respawn.Players.Remove(actor.PlayerSession.UserId);
+ }
+ QueueDel(ev.Victim);
+ }
+
+ private void OnMobStateChanged(MobStateChangedEvent args)
+ {
+ if (args.NewMobState == MobState.Alive)
+ return;
+
+ if (!TryComp(args.Target, out var actor))
+ return;
+
+ var query = EntityQueryEnumerator();
+ while (query.MoveNext(out var uid, out _, out var rule))
+ {
+ if (!GameTicker.IsGameRuleActive(uid, rule))
+ continue;
+
+ if (RespawnPlayer(args.Target, uid, actor: actor))
+ break;
+ }
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ if (_station.GetStations().FirstOrNull() is not { } station)
+ return;
+
+ foreach (var tracker in EntityQuery())
+ {
+ var queue = new Dictionary(tracker.RespawnQueue);
+ foreach (var (player, time) in queue)
+ {
+ if (_timing.CurTime < time)
+ continue;
+
+ if (!_playerManager.TryGetSessionById(player, out var session))
+ continue;
+
+ if (session.GetMind() is { } mind && TryComp(mind, out var mindComp) && mindComp.OwnedEntity.HasValue)
+ QueueDel(mindComp.OwnedEntity.Value);
+ GameTicker.MakeJoinGame(session, station, silent: true);
+ tracker.RespawnQueue.Remove(player);
+ }
+ }
+ }
+
+ ///
+ /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die.
+ ///
+ public void AddToTracker(EntityUid player, EntityUid tracker, RespawnTrackerComponent? component = null, ActorComponent? actor = null)
+ {
+ if (!Resolve(tracker, ref component) || !Resolve(player, ref actor, false))
+ return;
+
+ AddToTracker(actor.PlayerSession.UserId, tracker, component);
+ }
+
+ ///
+ /// Adds a given player to the respawn tracker, ensuring that they are respawned if they die.
+ ///
+ public void AddToTracker(NetUserId id, EntityUid tracker, RespawnTrackerComponent? component = null)
+ {
+ if (!Resolve(tracker, ref component))
+ return;
+
+ component.Players.Add(id);
+ }
+
+ ///
+ /// Attempts to directly respawn a player, skipping the lobby screen.
+ ///
+ public bool RespawnPlayer(EntityUid player, EntityUid respawnTracker, RespawnTrackerComponent? component = null, ActorComponent? actor = null)
+ {
+ if (!Resolve(respawnTracker, ref component) || !Resolve(player, ref actor, false))
+ return false;
+
+ if (!component.Players.Contains(actor.PlayerSession.UserId) || component.RespawnQueue.ContainsKey(actor.PlayerSession.UserId))
+ return false;
+
+ if (component.RespawnDelay == TimeSpan.Zero)
+ {
+ if (_station.GetStations().FirstOrNull() is not { } station)
+ return false;
+
+ QueueDel(player);
+ GameTicker.MakeJoinGame(actor.PlayerSession, station, silent: true);
+ return false;
+ }
+
+ var msg = Loc.GetString("rule-respawn-in-seconds", ("second", component.RespawnDelay.TotalSeconds));
+ var wrappedMsg = Loc.GetString("chat-manager-server-wrap-message", ("message", msg));
+ _chatManager.ChatMessageToOne(ChatChannel.Server, msg, wrappedMsg, respawnTracker, false, actor.PlayerSession.ConnectedClient, Color.LimeGreen);
+ component.RespawnQueue[actor.PlayerSession.UserId] = _timing.CurTime + component.RespawnDelay;
+ return true;
+ }
+}
diff --git a/Content.Server/KillTracking/KillTrackerComponent.cs b/Content.Server/KillTracking/KillTrackerComponent.cs
new file mode 100644
index 0000000000..aad6671df9
--- /dev/null
+++ b/Content.Server/KillTracking/KillTrackerComponent.cs
@@ -0,0 +1,62 @@
+using Content.Shared.FixedPoint;
+using Content.Shared.Mobs;
+using Robust.Shared.Network;
+
+namespace Content.Server.KillTracking;
+
+///
+/// This is used for entities that track player damage sources and killers.
+///
+[RegisterComponent, Access(typeof(KillTrackingSystem))]
+public sealed partial class KillTrackerComponent : Component
+{
+ ///
+ /// The mobstate that registers as a "kill"
+ ///
+ [DataField("killState")]
+ public MobState KillState = MobState.Critical;
+
+ ///
+ /// A dictionary of sources and how much damage they've done to this entity over time.
+ ///
+ [DataField("lifetimeDamage")]
+ public Dictionary LifetimeDamage = new();
+}
+
+public abstract record KillSource;
+
+///
+/// A kill source for players
+///
+[DataDefinition, Serializable]
+public sealed partial record KillPlayerSource : KillSource
+{
+ [DataField("playerId")]
+ public NetUserId PlayerId;
+
+ public KillPlayerSource(NetUserId playerId)
+ {
+ PlayerId = playerId;
+ }
+}
+
+///
+/// A kill source for non-player controlled entities
+///
+[DataDefinition, Serializable]
+public sealed partial record KillNpcSource : KillSource
+{
+ [DataField("npcEnt")]
+ public EntityUid NpcEnt;
+
+ public KillNpcSource(EntityUid npcEnt)
+ {
+ NpcEnt = npcEnt;
+ }
+}
+
+///
+/// A kill source for kills with no damage origin
+///
+[DataDefinition, Serializable]
+public sealed partial record KillEnvironmentSource : KillSource;
diff --git a/Content.Server/KillTracking/KillTrackingSystem.cs b/Content.Server/KillTracking/KillTrackingSystem.cs
new file mode 100644
index 0000000000..177f28ddc8
--- /dev/null
+++ b/Content.Server/KillTracking/KillTrackingSystem.cs
@@ -0,0 +1,129 @@
+using Content.Server.NPC.HTN;
+using Content.Shared.Damage;
+using Content.Shared.FixedPoint;
+using Content.Shared.Mobs;
+using Robust.Server.GameObjects;
+
+namespace Content.Server.KillTracking;
+
+///
+/// This handles and recording who is damaging and killing entities.
+///
+public sealed class KillTrackingSystem : EntitySystem
+{
+ ///
+ public override void Initialize()
+ {
+ SubscribeLocalEvent(OnDamageChanged);
+ SubscribeLocalEvent(OnMobStateChanged);
+ }
+
+ private void OnDamageChanged(EntityUid uid, KillTrackerComponent component, DamageChangedEvent args)
+ {
+ if (args.DamageDelta == null)
+ return;
+
+ if (!args.DamageIncreased)
+ {
+ foreach (var key in component.LifetimeDamage.Keys)
+ {
+ component.LifetimeDamage[key] -= args.DamageDelta.Total;
+ }
+
+ return;
+ }
+
+ var source = GetKillSource(args.Origin);
+ var damage = component.LifetimeDamage.GetValueOrDefault(source);
+ component.LifetimeDamage[source] = damage + args.DamageDelta.Total;
+ }
+
+ private void OnMobStateChanged(EntityUid uid, KillTrackerComponent component, MobStateChangedEvent args)
+ {
+ if (args.NewMobState != component.KillState || args.OldMobState >= args.NewMobState)
+ return;
+
+ // impulse is the entity that did the finishing blow.
+ var killImpulse = GetKillSource(args.Origin);
+
+ // source is the kill tracker source with the most damage dealt.
+ var largestSource = GetLargestSource(component.LifetimeDamage);
+ largestSource ??= killImpulse;
+
+ KillSource? killSource;
+ KillSource? assistSource = null;
+
+ if (killImpulse is KillEnvironmentSource)
+ {
+ // if the kill was environmental, whatever did the most damage gets the kill.
+ killSource = largestSource;
+ }
+ else if (killImpulse == largestSource)
+ {
+ // if the impulse and the source are the same, there's no assist
+ killSource = killImpulse;
+ }
+ else
+ {
+ // the impulse gets the kill and the most damage gets the assist
+ killSource = killImpulse;
+
+ // no assist is given to environmental kills
+ if (largestSource is not KillEnvironmentSource)
+ {
+ // you have to do at least 50% of largest source's damage to get the assist.
+ if (component.LifetimeDamage[largestSource] >= component.LifetimeDamage[killSource] / 2)
+ {
+ assistSource = largestSource;
+ }
+ }
+ }
+
+ // it's a suicide if:
+ // - you caused your own death
+ // - the kill source was the entity that died
+ // - the entity that died had an assist on themselves
+ var suicide = args.Origin == uid ||
+ killSource is KillNpcSource npc && npc.NpcEnt == uid ||
+ killSource is KillPlayerSource player && player.PlayerId == CompOrNull(uid)?.PlayerSession.UserId ||
+ assistSource is KillNpcSource assistNpc && assistNpc.NpcEnt == uid ||
+ assistSource is KillPlayerSource assistPlayer && assistPlayer.PlayerId == CompOrNull(uid)?.PlayerSession.UserId;
+
+ var ev = new KillReportedEvent(uid, killSource, assistSource, suicide);
+ RaiseLocalEvent(uid, ref ev, true);
+ }
+
+ private KillSource GetKillSource(EntityUid? sourceEntity)
+ {
+ if (TryComp(sourceEntity, out var actor))
+ return new KillPlayerSource(actor.PlayerSession.UserId);
+ if (HasComp(sourceEntity))
+ return new KillNpcSource(sourceEntity.Value);
+ return new KillEnvironmentSource();
+ }
+
+ private KillSource? GetLargestSource(Dictionary lifetimeDamages)
+ {
+ KillSource? maxSource = null;
+ var maxDamage = FixedPoint2.Zero;
+ foreach (var (source, damage) in lifetimeDamages)
+ {
+ if (damage < maxDamage)
+ continue;
+ maxSource = source;
+ maxDamage = damage;
+ }
+
+ return maxSource;
+ }
+}
+
+///
+/// Event broadcasted and raised by-ref on an entity with when they are killed.
+///
+/// The entity that was killed
+/// The primary source of the kill
+/// A secondary source of the kill. Can be null.
+/// True if the entity that was killed caused their own death.
+[ByRefEvent]
+public readonly record struct KillReportedEvent(EntityUid Entity, KillSource Primary, KillSource? Assist, bool Suicide);
diff --git a/Content.Server/Maps/GameMapManager.cs b/Content.Server/Maps/GameMapManager.cs
index 2fb531e5d0..69f732151f 100644
--- a/Content.Server/Maps/GameMapManager.cs
+++ b/Content.Server/Maps/GameMapManager.cs
@@ -85,7 +85,7 @@ public sealed class GameMapManager : IGameMapManager
var poolPrototype = _entityManager.System().Preset?.MapPool ??
_configurationManager.GetCVar(CCVars.GameMapPool);
- if (_prototypeManager.TryIndex(_configurationManager.GetCVar(CCVars.GameMapPool), out var pool))
+ if (_prototypeManager.TryIndex(poolPrototype, out var pool))
{
foreach (var map in pool.Maps)
{
diff --git a/Content.Server/Medical/HealingSystem.cs b/Content.Server/Medical/HealingSystem.cs
index 9dbc404892..35d70209b5 100644
--- a/Content.Server/Medical/HealingSystem.cs
+++ b/Content.Server/Medical/HealingSystem.cs
@@ -84,10 +84,18 @@ public sealed class HealingSystem : EntitySystem
var total = healed?.Total ?? FixedPoint2.Zero;
// Re-verify that we can heal the damage.
- _stacks.Use(args.Used.Value, 1);
- if (_stacks.GetCount(args.Used.Value) <= 0)
- dontRepeat = true;
+ if (TryComp(args.Used.Value, out var stackComp))
+ {
+ _stacks.Use(args.Used.Value, 1, stackComp);
+
+ if (_stacks.GetCount(args.Used.Value, stackComp) <= 0)
+ dontRepeat = true;
+ }
+ else
+ {
+ QueueDel(args.Used.Value);
+ }
if (uid != args.User)
{
@@ -157,7 +165,7 @@ public sealed class HealingSystem : EntitySystem
if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
return false;
- if (!TryComp(uid, out var stack) || stack.Count < 1)
+ if (TryComp(uid, out var stack) && stack.Count < 1)
return false;
if (!TryComp(target, out var bloodstream))
diff --git a/Content.Server/Points/PointSystem.cs b/Content.Server/Points/PointSystem.cs
new file mode 100644
index 0000000000..c3265701c0
--- /dev/null
+++ b/Content.Server/Points/PointSystem.cs
@@ -0,0 +1,96 @@
+using System.Linq;
+using Content.Shared.FixedPoint;
+using Content.Shared.Points;
+using JetBrains.Annotations;
+using Robust.Server.GameObjects;
+using Robust.Server.GameStates;
+using Robust.Server.Player;
+using Robust.Shared.GameStates;
+using Robust.Shared.Utility;
+
+namespace Content.Server.Points;
+
+///
+public sealed class PointSystem : SharedPointSystem
+{
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
+
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnStartup);
+ SubscribeLocalEvent(OnGetState);
+ }
+
+ private void OnStartup(EntityUid uid, PointManagerComponent component, ComponentStartup args)
+ {
+ _pvsOverride.AddGlobalOverride(uid);
+ }
+
+ private void OnGetState(EntityUid uid, PointManagerComponent component, ref ComponentGetState args)
+ {
+ args.State = new PointManagerComponentState(component.Points, component.Scoreboard);
+ }
+
+ ///
+ /// Adds the specified point value to a player.
+ ///
+ [PublicAPI]
+ public void AdjustPointValue(EntityUid user, FixedPoint2 value, EntityUid uid, PointManagerComponent? component, ActorComponent? actor = null)
+ {
+ if (!Resolve(uid, ref component) || !Resolve(user, ref actor, false))
+ return;
+ AdjustPointValue(actor.PlayerSession.UserId, value, uid, component);
+ }
+
+ ///
+ /// Sets the amount of points for a player
+ ///
+ [PublicAPI]
+ public void SetPointValue(EntityUid user, FixedPoint2 value, EntityUid uid, PointManagerComponent? component, ActorComponent? actor = null)
+ {
+ if (!Resolve(uid, ref component) || !Resolve(user, ref actor, false))
+ return;
+ SetPointValue(actor.PlayerSession.UserId, value, uid, component);
+ }
+
+ ///
+ /// Gets the amount of points for a given player
+ ///
+ [PublicAPI]
+ public FixedPoint2 GetPointValue(EntityUid user, EntityUid uid, PointManagerComponent? component, ActorComponent? actor = null)
+ {
+ if (!Resolve(uid, ref component) || !Resolve(user, ref actor, false))
+ return FixedPoint2.Zero;
+ return GetPointValue(actor.PlayerSession.UserId, uid, component);
+ }
+
+ ///
+ public override FormattedMessage GetScoreboard(EntityUid uid, PointManagerComponent? component = null)
+ {
+ var msg = new FormattedMessage();
+
+ if (!Resolve(uid, ref component))
+ return msg;
+
+ var orderedPlayers = component.Points.OrderByDescending(p => p.Value).ToList();
+ var place = 1;
+ foreach (var (id, points) in orderedPlayers)
+ {
+ if (!_player.TryGetPlayerData(id, out var data))
+ continue;
+
+ msg.AddMarkup(Loc.GetString("point-scoreboard-list",
+ ("place", place),
+ ("name", data.UserName),
+ ("points", points.Int())));
+ msg.PushNewline();
+ place++;
+ }
+
+ return msg;
+ }
+}
diff --git a/Content.Server/Roles/Jobs/JobSystem.cs b/Content.Server/Roles/Jobs/JobSystem.cs
index dfb689b4f9..bdc54bd17c 100644
--- a/Content.Server/Roles/Jobs/JobSystem.cs
+++ b/Content.Server/Roles/Jobs/JobSystem.cs
@@ -26,6 +26,9 @@ public sealed class JobSystem : EntitySystem
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, ref MindRoleAddedEvent args)
{
+ if (args.Silent)
+ return;
+
if (!_mind.TryGetSession(mindId, out var session))
return;
diff --git a/Content.Server/Roles/MindRoleAddedEvent.cs b/Content.Server/Roles/MindRoleAddedEvent.cs
index 9d208a4eba..f523819893 100644
--- a/Content.Server/Roles/MindRoleAddedEvent.cs
+++ b/Content.Server/Roles/MindRoleAddedEvent.cs
@@ -5,4 +5,4 @@
/// for the one raised on player entities.
///
[ByRefEvent]
-public readonly record struct MindRoleAddedEvent;
+public readonly record struct MindRoleAddedEvent(bool Silent);
diff --git a/Content.Server/Roles/RoleAddedEvent.cs b/Content.Server/Roles/RoleAddedEvent.cs
index 84d72ece04..1e88e78fb8 100644
--- a/Content.Server/Roles/RoleAddedEvent.cs
+++ b/Content.Server/Roles/RoleAddedEvent.cs
@@ -9,4 +9,4 @@ namespace Content.Server.Roles;
/// The mind id associated with the player.
/// The mind component associated with the mind id.
/// Whether or not the role makes the player an antagonist.
-public sealed record RoleAddedEvent(EntityUid MindId, MindComponent Mind, bool Antagonist) : RoleEvent(MindId, Mind, Antagonist);
+public sealed record RoleAddedEvent(EntityUid MindId, MindComponent Mind, bool Antagonist, bool Silent = false) : RoleEvent(MindId, Mind, Antagonist);
diff --git a/Content.Server/Roles/RoleSystem.cs b/Content.Server/Roles/RoleSystem.cs
index d5fb032aac..a70a51e7e5 100644
--- a/Content.Server/Roles/RoleSystem.cs
+++ b/Content.Server/Roles/RoleSystem.cs
@@ -66,11 +66,12 @@ public sealed class RoleSystem : EntitySystem
/// The mind to add the role to.
/// The role instance to add.
/// The role type to add.
+ /// Whether or not the role should be added silently
/// The instance of the role.
///
/// Thrown if we already have a role with this type.
///
- public void MindAddRole(EntityUid mindId, T component, MindComponent? mind = null) where T : Component, new()
+ public void MindAddRole(EntityUid mindId, T component, MindComponent? mind = null, bool silent = false) where T : Component, new()
{
if (!Resolve(mindId, ref mind))
return;
@@ -86,7 +87,7 @@ public sealed class RoleSystem : EntitySystem
var mindEv = new MindRoleAddedEvent();
RaiseLocalEvent(mindId, ref mindEv);
- var message = new RoleAddedEvent(mindId, mind, antagonist);
+ var message = new RoleAddedEvent(mindId, mind, antagonist, silent);
if (mind.OwnedEntity != null)
{
RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
diff --git a/Content.Server/RoundEnd/RoundEndSystem.cs b/Content.Server/RoundEnd/RoundEndSystem.cs
index 5d180b8d17..4681acc227 100644
--- a/Content.Server/RoundEnd/RoundEndSystem.cs
+++ b/Content.Server/RoundEnd/RoundEndSystem.cs
@@ -201,7 +201,7 @@ namespace Content.Server.RoundEnd
RaiseLocalEvent(RoundEndSystemChangedEvent.Default);
}
- public void EndRound()
+ public void EndRound(TimeSpan? countdownTime = null)
{
if (_gameTicker.RunLevel != GameRunLevel.InRound) return;
LastCountdownStart = null;
@@ -211,17 +211,17 @@ namespace Content.Server.RoundEnd
_countdownTokenSource?.Cancel();
_countdownTokenSource = new();
- var countdownTime = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.RoundRestartTime));
+ countdownTime ??= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.RoundRestartTime));
int time;
string unitsLocString;
- if (countdownTime.TotalSeconds < 60)
+ if (countdownTime.Value.TotalSeconds < 60)
{
- time = countdownTime.Seconds;
+ time = countdownTime.Value.Seconds;
unitsLocString = "eta-units-seconds";
}
else
{
- time = countdownTime.Minutes;
+ time = countdownTime.Value.Minutes;
unitsLocString = "eta-units-minutes";
}
_chatManager.DispatchServerAnnouncement(
@@ -229,7 +229,7 @@ namespace Content.Server.RoundEnd
"round-end-system-round-restart-eta-announcement",
("time", time),
("units", Loc.GetString(unitsLocString))));
- Timer.Spawn(countdownTime, AfterEndRoundRestart, _countdownTokenSource.Token);
+ Timer.Spawn(countdownTime.Value, AfterEndRoundRestart, _countdownTokenSource.Token);
}
private void AfterEndRoundRestart()
diff --git a/Content.Shared/Damage/Systems/DamageableSystem.cs b/Content.Shared/Damage/Systems/DamageableSystem.cs
index 51d6e89d0f..a3cdd14ef7 100644
--- a/Content.Shared/Damage/Systems/DamageableSystem.cs
+++ b/Content.Shared/Damage/Systems/DamageableSystem.cs
@@ -123,12 +123,6 @@ namespace Content.Shared.Damage
return null;
}
- if (damage == null)
- {
- Log.Error("Null DamageSpecifier. Probably because a required yaml field was not given.");
- return null;
- }
-
if (damage.Empty)
{
return damage;
diff --git a/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs b/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs
index f3e637bca5..b07087cafa 100644
--- a/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs
+++ b/Content.Shared/Mobs/Systems/MobStateSystem.StateMachine.cs
@@ -32,7 +32,7 @@ public partial class MobStateSystem
var ev = new UpdateMobStateEvent {Target = entity, Component = component, Origin = origin};
RaiseLocalEvent(entity, ref ev);
- ChangeState(entity, component, ev.State);
+ ChangeState(entity, component, ev.State, origin: origin);
}
///
diff --git a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs
index bd357e4be8..93e89c32b8 100644
--- a/Content.Shared/Mobs/Systems/MobThresholdSystem.cs
+++ b/Content.Shared/Mobs/Systems/MobThresholdSystem.cs
@@ -297,14 +297,14 @@ public sealed class MobThresholdSystem : EntitySystem
#region Private Implementation
private void CheckThresholds(EntityUid target, MobStateComponent mobStateComponent,
- MobThresholdsComponent thresholdsComponent, DamageableComponent damageableComponent)
+ MobThresholdsComponent thresholdsComponent, DamageableComponent damageableComponent, EntityUid? origin = null)
{
foreach (var (threshold, mobState) in thresholdsComponent.Thresholds.Reverse())
{
if (damageableComponent.TotalDamage < threshold)
continue;
- TriggerThreshold(target, mobState, mobStateComponent, thresholdsComponent);
+ TriggerThreshold(target, mobState, mobStateComponent, thresholdsComponent, origin);
break;
}
}
@@ -313,7 +313,8 @@ public sealed class MobThresholdSystem : EntitySystem
EntityUid target,
MobState newState,
MobStateComponent? mobState = null,
- MobThresholdsComponent? thresholds = null)
+ MobThresholdsComponent? thresholds = null,
+ EntityUid? origin = null)
{
if (!Resolve(target, ref mobState, ref thresholds) ||
mobState.CurrentState == newState)
@@ -327,7 +328,7 @@ public sealed class MobThresholdSystem : EntitySystem
Dirty(target, thresholds);
}
- _mobStateSystem.UpdateMobState(target, mobState);
+ _mobStateSystem.UpdateMobState(target, mobState, origin);
}
private void UpdateAlerts(EntityUid target, MobState currentMobState, MobThresholdsComponent? threshold = null,
@@ -374,7 +375,7 @@ public sealed class MobThresholdSystem : EntitySystem
{
if (!TryComp(target, out var mobState))
return;
- CheckThresholds(target, mobState, thresholds, args.Damageable);
+ CheckThresholds(target, mobState, thresholds, args.Damageable, args.Origin);
var ev = new MobThresholdChecked(target, mobState, thresholds, args.Damageable);
RaiseLocalEvent(target, ref ev, true);
UpdateAlerts(target, mobState.CurrentState, thresholds, args.Damageable);
diff --git a/Content.Shared/Points/PointManagerComponent.cs b/Content.Shared/Points/PointManagerComponent.cs
new file mode 100644
index 0000000000..4250d44dc1
--- /dev/null
+++ b/Content.Shared/Points/PointManagerComponent.cs
@@ -0,0 +1,40 @@
+using Content.Shared.FixedPoint;
+using Robust.Shared.GameStates;
+using Robust.Shared.Network;
+using Robust.Shared.Serialization;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Points;
+
+///
+/// This is a component that generically stores points for all players.
+///
+[RegisterComponent, NetworkedComponent, Access(typeof(SharedPointSystem))]
+public sealed partial class PointManagerComponent : Component
+{
+ ///
+ /// A dictionary of a player's netuserID to the amount of points they have.
+ ///
+ [DataField("points")]
+ public Dictionary Points = new();
+
+ ///
+ /// A text-only version of the scoreboard used by the client.
+ ///
+ [DataField("scoreboard")]
+ public FormattedMessage Scoreboard = new();
+}
+
+[Serializable, NetSerializable]
+public sealed class PointManagerComponentState : ComponentState
+{
+ public Dictionary Points;
+
+ public FormattedMessage Scoreboard;
+
+ public PointManagerComponentState(Dictionary points, FormattedMessage scoreboard)
+ {
+ Points = points;
+ Scoreboard = scoreboard;
+ }
+}
diff --git a/Content.Shared/Points/SharedPointSystem.cs b/Content.Shared/Points/SharedPointSystem.cs
new file mode 100644
index 0000000000..978f4bca42
--- /dev/null
+++ b/Content.Shared/Points/SharedPointSystem.cs
@@ -0,0 +1,86 @@
+using Content.Shared.FixedPoint;
+using Robust.Shared.Network;
+using Robust.Shared.Utility;
+
+namespace Content.Shared.Points;
+
+///
+/// This handles modifying point counts for
+///
+public abstract class SharedPointSystem : EntitySystem
+{
+ ///
+ /// Adds the specified point value to a player.
+ ///
+ public void AdjustPointValue(NetUserId userId, FixedPoint2 value, EntityUid uid, PointManagerComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (!component.Points.TryGetValue(userId, out var current))
+ current = 0;
+
+ SetPointValue(userId, current + value, uid, component);
+ }
+
+ ///
+ /// Sets the amount of points for a player
+ ///
+ public void SetPointValue(NetUserId userId, FixedPoint2 value, EntityUid uid, PointManagerComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (component.Points.TryGetValue(userId, out var current) && current == value)
+ return;
+
+ component.Points[userId] = value;
+ component.Scoreboard = GetScoreboard(uid, component);
+ Dirty(uid, component);
+
+ var ev = new PlayerPointChangedEvent(userId, value);
+ RaiseLocalEvent(uid, ref ev, true);
+ }
+
+ ///
+ /// Gets the amount of points for a given player
+ ///
+ public FixedPoint2 GetPointValue(NetUserId userId, EntityUid uid, PointManagerComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return FixedPoint2.Zero;
+
+ return component.Points.TryGetValue(userId, out var value)
+ ? value
+ : FixedPoint2.Zero;
+ }
+
+ ///
+ /// Ensures that a player is being tracked by the PointManager, giving them a default score of 0.
+ ///
+ public void EnsurePlayer(NetUserId userId, EntityUid uid, PointManagerComponent? component = null)
+ {
+ if (!Resolve(uid, ref component))
+ return;
+
+ if (component.Points.ContainsKey(userId))
+ return;
+ SetPointValue(userId, FixedPoint2.Zero, uid, component);
+ }
+
+ ///
+ /// Returns a formatted message containing a ranking of all the currently online players and their scores.
+ ///
+ public virtual FormattedMessage GetScoreboard(EntityUid uid, PointManagerComponent? component = null)
+ {
+ return new FormattedMessage();
+ }
+}
+
+///
+/// Event raised on the point manager entity and broadcasted whenever a player's points change.
+///
+///
+///
+[ByRefEvent]
+public readonly record struct PlayerPointChangedEvent(NetUserId Player, FixedPoint2 Points);
diff --git a/Resources/Locale/en-US/game-ticking/game-presets/preset-deathmatch.ftl b/Resources/Locale/en-US/game-ticking/game-presets/preset-deathmatch.ftl
index ee41fcefea..c83d0167bf 100644
--- a/Resources/Locale/en-US/game-ticking/game-presets/preset-deathmatch.ftl
+++ b/Resources/Locale/en-US/game-ticking/game-presets/preset-deathmatch.ftl
@@ -1,2 +1,79 @@
death-match-title = DeathMatch
-death-match-description = Kill anything that moves!
+death-match-description = Kill anything that moves! Normal roleplay need not apply. The first to 31 points wins!
+
+death-match-name-player = [bold]{$name}[/bold] ([italic]{$username}[/italic])
+death-match-name-npc = [bold]{$name}[/bold]
+death-match-assist = {$primary}, assisted by {$secondary},
+
+death-match-kill-callout-0 = {CAPITALIZE($killer)} murdered {$victim}!
+death-match-kill-callout-1 = {CAPITALIZE($killer)} killed {$victim}!
+death-match-kill-callout-2 = {CAPITALIZE($killer)} fragged {$victim}!
+death-match-kill-callout-3 = {CAPITALIZE($killer)} demolished {$victim}!
+death-match-kill-callout-4 = {CAPITALIZE($killer)} turned {$victim} into lunch meat!
+death-match-kill-callout-5 = {CAPITALIZE($killer)} blitzed {$victim}!
+death-match-kill-callout-6 = {CAPITALIZE($killer)} flipped {$victim} upside down and spun 'em!
+death-match-kill-callout-7 = {CAPITALIZE($killer)} messed up {$victim}!
+death-match-kill-callout-8 = {CAPITALIZE($killer)} sent {$victim} to hell!
+death-match-kill-callout-9 = {CAPITALIZE($killer)} danced on {$victim}'s grave!
+death-match-kill-callout-10 = {CAPITALIZE($killer)} obliterated {$victim}!
+death-match-kill-callout-11 = {CAPITALIZE($killer)} robusted {$victim}!
+death-match-kill-callout-12 = {CAPITALIZE($killer)} combined toolbox and {$victim} to create corpse!
+death-match-kill-callout-13 = {CAPITALIZE($killer)} made {$victim} bite the dust!
+death-match-kill-callout-14 = {CAPITALIZE($killer)} posted a cringe compilation of {$victim}!
+death-match-kill-callout-15 = {CAPITALIZE($killer)} saw {$victim} post their full-body OC!
+death-match-kill-callout-16 = {CAPITALIZE($killer)} Doom (1993)'ed {$victim}!
+death-match-kill-callout-17 = {CAPITALIZE($killer)} humiliated {$victim}!
+death-match-kill-callout-18 = {CAPITALIZE($killer)} removed {$victim}'s flashdrive while it was being used!
+death-match-kill-callout-19 = {CAPITALIZE($killer)} deleted System32 from {$victim}'s PC!
+death-match-kill-callout-20 = {CAPITALIZE($killer)} bwoinked {$victim}!
+death-match-kill-callout-21 = {CAPITALIZE($killer)} voted {$victim} off the island!
+death-match-kill-callout-22 = {CAPITALIZE($killer)} voted {$victim} for acting sus!
+death-match-kill-callout-23 = {CAPITALIZE($killer)} forced {$victim} to code for SS14!
+death-match-kill-callout-24 = {CAPITALIZE($killer)} forced {$victim} to code for Open Dream!
+death-match-kill-callout-25 = {CAPITALIZE($killer)} forced {$victim} to code for Byond!
+death-match-kill-callout-26 = {CAPITALIZE($killer)} 1984'ed {$victim}!
+death-match-kill-callout-27 = {CAPITALIZE($killer)} express-shipped {$victim} to God!
+death-match-kill-callout-28 = {CAPITALIZE($killer)} taunt killed {$victim}!
+death-match-kill-callout-29 = {CAPITALIZE($killer)} said {$victim} had a nice cut, G!
+death-match-kill-callout-30 = {CAPITALIZE($killer)} shuffled on {$victim}'s grave!
+death-match-kill-callout-31 = {CAPITALIZE($killer)} pushed {$victim} down the stairs!
+death-match-kill-callout-32 = {CAPITALIZE($killer)} enacted the bite of '87 on {$victim}!
+death-match-kill-callout-33 = {CAPITALIZE($killer)} saw {$victim} post on reddit!
+death-match-kill-callout-34 = {CAPITALIZE($killer)} threw {$victim} to the admin team!
+death-match-kill-callout-35 = {CAPITALIZE($killer)} weh'ed {$victim}!
+death-match-kill-callout-36 = {CAPITALIZE($killer)} turned {$victim} into an SS13 remake!
+death-match-kill-callout-37 = {CAPITALIZE($killer)} forced {$victim} to play Xonotic!
+death-match-kill-callout-38 = {CAPITALIZE($killer)} sent {$victim} to Brazil!
+death-match-kill-callout-39 = {CAPITALIZE($killer)} epicly hacked {$victim}!
+death-match-kill-callout-40 = {CAPITALIZE($killer)} closed {$victim}'s PR!
+death-match-kill-callout-41 = {CAPITALIZE($killer)} saw {$victim} merge cringe on master!
+death-match-kill-callout-42 = {CAPITALIZE($killer)} witnessed {$victim} sergalpost on main!
+death-match-kill-callout-43 = {CAPITALIZE($killer)} did not gently the {$victim}!
+death-match-kill-callout-44 = {CAPITALIZE($killer)} schmoved on {$victim}!
+death-match-kill-callout-45 = {CAPITALIZE($killer)} cunked {$victim}!
+death-match-kill-callout-46 = {CAPITALIZE($killer)} shook {$victim} before drinking!
+death-match-kill-callout-47 = {CAPITALIZE($killer)} drove drunk and hit {$victim}!
+death-match-kill-callout-48 = {CAPITALIZE($killer)} turned {$victim} into a marketable plush!
+death-match-kill-callout-49 = {CAPITALIZE($killer)} gave {$victim} a reminder of their mortality!
+death-match-kill-callout-50 = {CAPITALIZE($killer)} ratio'd {$victim}!
+death-match-kill-callout-51 = {CAPITALIZE($killer)} ctrl-alt-delete'd {$victim}!
+death-match-kill-callout-52 = {CAPITALIZE($killer)} bonked {$victim}!
+death-match-kill-callout-53 = {CAPITALIZE($killer)} landed a random crit on {$victim}!
+death-match-kill-callout-54 = {CAPITALIZE($killer)} taught {$victim} a valuable lesson!
+death-match-kill-callout-55 = {CAPITALIZE($killer)} hit a home run on {$victim}!
+death-match-kill-callout-56 = {CAPITALIZE($killer)} dunked on {$victim}!
+death-match-kill-callout-57 = {CAPITALIZE($killer)} styled on {$victim}!
+death-match-kill-callout-58 = {CAPITALIZE($killer)} said a rude word to {$victim}!
+death-match-kill-callout-59 = {CAPITALIZE($killer)} sent hate-mail to {$victim}!
+death-match-kill-callout-60 = {CAPITALIZE($killer)} tripped {$victim} down the stairs!
+death-match-kill-callout-env-0 = {CAPITALIZE($victim)} lost a point!
+death-match-kill-callout-env-1 = {CAPITALIZE($victim)} got humiliated!
+death-match-kill-callout-env-2 = {CAPITALIZE($victim)} just looked like an idiot!
+death-match-kill-callout-env-3 = {CAPITALIZE($victim)} suffered a skill issue!
+death-match-kill-callout-env-4 = {CAPITALIZE($victim)} looked extremely dumb!
+death-match-kill-callout-env-5 = {CAPITALIZE($victim)} put themselves out of their misery!
+death-match-kill-callout-env-6 = {CAPITALIZE($victim)} got bored of life!
+death-match-kill-callout-env-7 = {CAPITALIZE($victim)} didn't try very hard!
+death-match-kill-callout-env-8 = {CAPITALIZE($victim)} took out the trash themselves!
+death-match-kill-callout-env-9 = {CAPITALIZE($victim)} looked extremely dumb!
+death-match-kill-callout-env-10 = {CAPITALIZE($victim)} clowned around!
diff --git a/Resources/Locale/en-US/game-ticking/game-rules/rules.ftl b/Resources/Locale/en-US/game-ticking/game-rules/rules.ftl
index fede404413..5a89d5e979 100644
--- a/Resources/Locale/en-US/game-ticking/game-rules/rules.ftl
+++ b/Resources/Locale/en-US/game-ticking/game-rules/rules.ftl
@@ -1,3 +1,6 @@
# General
rule-restarting-in-seconds = Restarting in {$seconds} seconds.
-rule-time-has-run-out = Time has run out!
\ No newline at end of file
+rule-time-has-run-out = Time has run out!
+
+# Respawning
+rule-respawn-in-seconds = Respawning in {$second} seconds...
diff --git a/Resources/Locale/en-US/points/points.ftl b/Resources/Locale/en-US/points/points.ftl
new file mode 100644
index 0000000000..7b0dfe3c01
--- /dev/null
+++ b/Resources/Locale/en-US/points/points.ftl
@@ -0,0 +1,6 @@
+point-scoreboard-winner = The winner was [color=lime]{$player}![/color]
+point-scoreboard-header = [bold]Scoreboard[/bold]
+point-scoreboard-list = {$place}. [bold][color=cyan]{$name}[/color][/bold] scored [color=yellow]{$points ->
+ [one] {$points} point
+ *[other] {$points} points
+}.[/color]
diff --git a/Resources/Maps/Nonstations/meteor-arena.yml b/Resources/Maps/Nonstations/meteor-arena.yml
new file mode 100644
index 0000000000..a5681eda96
--- /dev/null
+++ b/Resources/Maps/Nonstations/meteor-arena.yml
@@ -0,0 +1,1634 @@
+meta:
+ format: 5
+ postmapinit: false
+tilemap:
+ 0: Space
+ 16: FloorBasalt
+ 23: FloorCave
+ 26: FloorDark
+ 27: FloorDarkDiagonal
+ 29: FloorDarkHerringbone
+ 31: FloorDarkMono
+ 33: FloorDarkPavement
+ 34: FloorDarkPavementVertical
+ 57: FloorMetalDiamond
+ 75: FloorSteel
+ 104: Plating
+entities:
+- proto: ""
+ entities:
+ - uid: 1
+ components:
+ - type: MetaData
+ - type: Transform
+ - type: Map
+ - type: PhysicsMap
+ - type: GridTree
+ - type: MovedGrids
+ - type: Broadphase
+ - type: OccluderTree
+ - ambientLightColor: '#D8B059FF'
+ type: MapLight
+ - type: LoadedMap
+ - uid: 2
+ components:
+ - type: MetaData
+ - parent: 1
+ type: Transform
+ - chunks:
+ -1,-1:
+ ind: -1,-1
+ tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAGgAAABoAAAAaAAAAEsAAAFLAAABSwAAAksAAAFLAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAAAXAAAEFwAABjkAAAAQAAAAEAAAABAAAAAQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAGgAAABoAAAAFwAAAhcAAAVoAAAAHwAAAR8AAAEfAAABHwAAAh8AAAIAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAAAXAAADFwAAAhcAAAUXAAAGaAAAABoAAAMaAAACaAAAABsAAAEbAAADAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAFwAAAxcAAAIXAAADaAAAAGgAAAAaAAABGgAAAR8AAAAfAAAAHwAAAAAAAAAAAAAAAAAAAAAAAABoAAAAaAAAADkAAABoAAAAaAAAAGgAAAAaAAAAGgAAAhoAAAMaAAACGgAAAxoAAAIAAAAAAAAAAAAAAAAAAAAAaAAAAEsAAAAQAAAAHwAAARoAAAEaAAAAGgAAAx0AAAAdAAABIQAAASEAAAMhAAACAAAAAAAAAAAAAAAAAAAAAGgAAABLAAACEAAAAB8AAAEaAAACGgAAABoAAAMdAAAAaAAAAEsAAAMQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAABoAAAASwAAAxAAAAAfAAACaAAAAB8AAAAaAAACIgAAAksAAAJLAAADEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAEsAAAIQAAAAHwAAAxsAAAIfAAAAGgAAAyIAAAAQAAAAEAAAAB8AAAMfAAACAAAAAAAAAAAAAAAAAAAAAGgAAABLAAAAEAAAAB8AAAAbAAACHwAAABoAAAEiAAACHwAAAB8AAAIfAAABHwAAAg==
+ -1,0:
+ ind: -1,0
+ tiles: AAAAAAAAAAAAAAAAAAAAAGgAAABLAAAAEAAAAB8AAAMbAAADHwAAABoAAAIiAAABEAAAABAAAAAfAAACHwAAAgAAAAAAAAAAAAAAAAAAAABoAAAASwAAABAAAAAfAAACaAAAAB8AAAAaAAABIgAAA0sAAANLAAADEAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAEsAAAEQAAAAHwAAABoAAAIaAAABGgAAAB0AAANoAAAASwAAAhAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAGgAAABLAAAAEAAAAB8AAAMaAAADGgAAARoAAAEdAAADHQAAASEAAAEhAAADIQAAAQAAAAAAAAAAAAAAAAAAAABoAAAAaAAAADkAAABoAAAAaAAAAGgAAAAaAAACGgAAAxoAAAAaAAABGgAAARoAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAAAXAAACFwAAABcAAANoAAAAaAAAABoAAAIaAAAAHwAAAB8AAAAfAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAFwAAARcAAAYXAAABFwAAAmgAAAAaAAADGgAAAWgAAAAbAAACGwAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAAGgAAABoAAAAFwAAABcAAAFoAAAAHwAAAR8AAAIfAAAAHwAAAR8AAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAaAAAABcAAAUXAAADOQAAABAAAAAQAAAAEAAAABAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAABoAAAAaAAAAGgAAABLAAABSwAAAEsAAABLAAADSwAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAAaAAAAGgAAABoAAAAaAAAAGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+ 0,0:
+ ind: 0,0
+ tiles: HwAAAxAAAAAQAAAAIgAAABoAAAAfAAAAGwAAAh8AAAIQAAAASwAAAWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAABLAAAASwAAAiIAAAIaAAABHwAAAGgAAAAfAAADEAAAAEsAAAFoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAASwAAAWgAAAAdAAAAGgAAAxoAAAEaAAAAHwAAARAAAABLAAACaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIQAAASEAAAIdAAABHQAAAxoAAAIaAAABGgAAAB8AAAEQAAAASwAAAWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAIaAAAAGgAAARoAAAEaAAABaAAAAGgAAABoAAAAOQAAAGgAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAAAHwAAABoAAAAaAAAAaAAAAGgAAAAXAAAEFwAABhcAAAJoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGwAAAGgAAAAaAAACGgAAAmgAAAAXAAAFFwAAAxcAAAEXAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAAfAAABHwAAAR8AAABoAAAAFwAAABcAAANoAAAAaAAAAGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAEAAAABAAAAAQAAAAOQAAABcAAAYXAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASwAAAksAAABLAAAASwAAAGgAAABoAAAAaAAAAGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAABoAAAAaAAAAGgAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+ 0,-1:
+ ind: 0,-1
+ tiles: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGgAAABoAAAAaAAAAGgAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABLAAABSwAAAksAAAJLAAAAaAAAAGgAAABoAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAABAAAAAQAAAAEAAAADkAAAAXAAACFwAAAWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB8AAAEfAAAAHwAAAh8AAABoAAAAFwAABRcAAANoAAAAaAAAAGgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbAAACaAAAABoAAAMaAAABaAAAABcAAAYXAAAFFwAABRcAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAB8AAAAaAAABGgAAA2gAAABoAAAAFwAABRcAAAQXAAAAaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoAAAIaAAABGgAAAhoAAAIaAAABaAAAAGgAAABoAAAAOQAAAGgAAABoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAAADIQAAAR0AAAMdAAAAGgAAARoAAAIaAAADHwAAAhAAAABLAAABaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAEsAAAFoAAAAHQAAAxoAAAAaAAAAGgAAAB8AAAIQAAAASwAAAmgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAABLAAABSwAAASIAAAMaAAACHwAAAGgAAAAfAAAAEAAAAEsAAANoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAfAAADEAAAABAAAAAiAAACGgAAAh8AAAAbAAABHwAAAhAAAABLAAACaAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHwAAAx8AAAMfAAAAIgAAAxoAAAMfAAAAGwAAAh8AAAIQAAAASwAAAWgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==
+ type: MapGrid
+ - type: Broadphase
+ - bodyStatus: InAir
+ angularDamping: 0.05
+ linearDamping: 0.05
+ fixedRotation: False
+ bodyType: Dynamic
+ type: Physics
+ - fixtures: {}
+ type: Fixtures
+ - id: Arena
+ type: BecomesStation
+ - gravityShakeSound: !type:SoundPathSpecifier
+ path: /Audio/Effects/alert.ogg
+ inherent: True
+ enabled: True
+ type: Gravity
+ - chunkCollection:
+ version: 2
+ nodes:
+ - node:
+ color: '#DE3A3A96'
+ id: HalfTileOverlayGreyscale
+ decals:
+ 31: -4,6
+ 32: -7,3
+ 33: 2,6
+ 34: 5,3
+ - node:
+ color: '#DE3A3A96'
+ id: HalfTileOverlayGreyscale180
+ decals:
+ 19: -7,-5
+ 20: -4,-8
+ 21: 2,-8
+ 22: 5,-5
+ - node:
+ color: '#DE3A3A96'
+ id: HalfTileOverlayGreyscale270
+ decals:
+ 23: -8,-4
+ 24: -8,2
+ 25: -5,5
+ 26: -5,-7
+ - node:
+ color: '#DE3A3A96'
+ id: HalfTileOverlayGreyscale90
+ decals:
+ 27: 3,-7
+ 28: 6,-4
+ 29: 6,2
+ 30: 3,5
+ - node:
+ color: '#DE3A3A96'
+ id: QuarterTileOverlayGreyscale
+ decals:
+ 41: -6,3
+ 42: -5,4
+ - node:
+ color: '#DE3A3A96'
+ id: QuarterTileOverlayGreyscale180
+ decals:
+ 37: 4,-5
+ 38: 3,-6
+ - node:
+ color: '#DE3A3A96'
+ id: QuarterTileOverlayGreyscale270
+ decals:
+ 39: -5,-6
+ 40: -6,-5
+ - node:
+ color: '#DE3A3A96'
+ id: QuarterTileOverlayGreyscale90
+ decals:
+ 35: 3,4
+ 36: 4,3
+ - node:
+ color: '#FFFFFFFF'
+ id: SpaceStationSign1
+ decals:
+ 0: -4,-1
+ - node:
+ color: '#FFFFFFFF'
+ id: SpaceStationSign2
+ decals:
+ 1: -3,-1
+ - node:
+ color: '#FFFFFFFF'
+ id: SpaceStationSign3
+ decals:
+ 4: -2,-1
+ - node:
+ color: '#FFFFFFFF'
+ id: SpaceStationSign4
+ decals:
+ 5: -1,-1
+ - node:
+ color: '#FFFFFFFF'
+ id: SpaceStationSign5
+ decals:
+ 6: 0,-1
+ - node:
+ color: '#FFFFFFFF'
+ id: SpaceStationSign6
+ decals:
+ 2: 1,-1
+ - node:
+ color: '#FFFFFFFF'
+ id: SpaceStationSign7
+ decals:
+ 3: 2,-1
+ - node:
+ color: '#DE3A3A96'
+ id: ThreeQuarterTileOverlayGreyscale
+ decals:
+ 7: -6,4
+ 8: -8,3
+ 9: -5,6
+ - node:
+ color: '#DE3A3A96'
+ id: ThreeQuarterTileOverlayGreyscale180
+ decals:
+ 10: 3,-8
+ 11: 4,-6
+ 12: 6,-5
+ - node:
+ color: '#DE3A3A96'
+ id: ThreeQuarterTileOverlayGreyscale270
+ decals:
+ 13: -8,-5
+ 14: -6,-6
+ 15: -5,-8
+ - node:
+ color: '#DE3A3A96'
+ id: ThreeQuarterTileOverlayGreyscale90
+ decals:
+ 16: 3,6
+ 17: 4,4
+ 18: 6,3
+ - node:
+ color: '#FFFFFFFF'
+ id: WarnLineE
+ decals:
+ 52: 7,-5
+ 53: 7,-4
+ 54: 7,-3
+ 55: 7,-2
+ 56: 7,-1
+ 57: 7,0
+ 58: 7,1
+ 59: 7,2
+ 60: 7,3
+ - node:
+ color: '#FFFFFFFF'
+ id: WarnLineN
+ decals:
+ 43: -5,-9
+ 44: -4,-9
+ 45: -3,-9
+ 46: -2,-9
+ 47: -1,-9
+ 48: 0,-9
+ 49: 1,-9
+ 50: 2,-9
+ 51: 3,-9
+ - node:
+ color: '#FFFFFFFF'
+ id: WarnLineS
+ decals:
+ 70: -9,3
+ 71: -9,2
+ 72: -9,1
+ 73: -9,0
+ 74: -9,-1
+ 75: -9,-2
+ 76: -9,-3
+ 77: -9,-4
+ 78: -9,-5
+ - node:
+ color: '#FFFFFFFF'
+ id: WarnLineW
+ decals:
+ 61: -5,7
+ 62: -4,7
+ 63: -3,7
+ 64: -2,7
+ 65: -1,7
+ 66: 0,7
+ 67: 1,7
+ 68: 2,7
+ 69: 3,7
+ type: DecalGrid
+ - type: OccluderTree
+ - type: Shuttle
+ - type: GridPathfinding
+ - type: SpreaderGrid
+ - version: 2
+ data:
+ tiles:
+ -1,-1:
+ 0: 65535
+ -1,0:
+ 0: 65535
+ 0,0:
+ 0: 65535
+ 0,-1:
+ 0: 65535
+ -3,-2:
+ 0: 65518
+ -3,-1:
+ 0: 65535
+ -3,-3:
+ 0: 59520
+ -2,-3:
+ 0: 65532
+ -2,-2:
+ 0: 65535
+ -2,-1:
+ 0: 65535
+ -1,-3:
+ 0: 65535
+ -1,-2:
+ 0: 65535
+ -3,0:
+ 0: 65535
+ -3,1:
+ 0: 61167
+ -3,2:
+ 0: 136
+ -2,0:
+ 0: 65535
+ -2,1:
+ 0: 65535
+ -2,2:
+ 0: 3327
+ -1,1:
+ 0: 65535
+ -1,2:
+ 0: 4095
+ 0,1:
+ 0: 65535
+ 0,2:
+ 0: 4095
+ 1,0:
+ 0: 65535
+ 1,1:
+ 0: 65535
+ 1,2:
+ 0: 511
+ 2,0:
+ 0: 30583
+ 2,1:
+ 0: 13111
+ 0,-3:
+ 0: 65535
+ 0,-2:
+ 0: 65535
+ 1,-3:
+ 0: 65521
+ 1,-2:
+ 0: 65535
+ 1,-1:
+ 0: 65535
+ 2,-3:
+ 0: 12288
+ 2,-2:
+ 0: 30515
+ 2,-1:
+ 0: 30583
+ uniqueMixes:
+ - volume: 2500
+ temperature: 293.15
+ moles:
+ - 21.824879
+ - 82.10312
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ - 0
+ chunkSize: 4
+ type: GridAtmosphere
+ - type: GasTileOverlay
+ - type: RadiationGridResistance
+- proto: FenceMetalBroken
+ entities:
+ - uid: 217
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -9.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 218
+ components:
+ - rot: 3.141592653589793 rad
+ pos: -5.5,8.5
+ parent: 2
+ type: Transform
+ - uid: 219
+ components:
+ - rot: 3.141592653589793 rad
+ pos: 4.5,8.5
+ parent: 2
+ type: Transform
+ - uid: 220
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 8.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 221
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 8.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 222
+ components:
+ - rot: 3.141592653589793 rad
+ pos: 4.5,-9.5
+ parent: 2
+ type: Transform
+ - uid: 223
+ components:
+ - rot: 3.141592653589793 rad
+ pos: -5.5,-9.5
+ parent: 2
+ type: Transform
+ - uid: 224
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -9.5,-5.5
+ parent: 2
+ type: Transform
+- proto: FenceMetalCorner
+ entities:
+ - uid: 73
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 1.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 74
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -2.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 75
+ components:
+ - pos: -6.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 76
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -6.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 77
+ components:
+ - rot: 3.141592653589793 rad
+ pos: 5.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 78
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 5.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 79
+ components:
+ - pos: 1.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 80
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -2.5,5.5
+ parent: 2
+ type: Transform
+- proto: FenceMetalStraight
+ entities:
+ - uid: 61
+ components:
+ - pos: -6.5,0.5
+ parent: 2
+ type: Transform
+ - uid: 62
+ components:
+ - pos: -6.5,-0.5
+ parent: 2
+ type: Transform
+ - uid: 63
+ components:
+ - pos: -6.5,-1.5
+ parent: 2
+ type: Transform
+ - uid: 64
+ components:
+ - pos: 5.5,0.5
+ parent: 2
+ type: Transform
+ - uid: 65
+ components:
+ - pos: 5.5,-0.5
+ parent: 2
+ type: Transform
+ - uid: 66
+ components:
+ - pos: 5.5,-1.5
+ parent: 2
+ type: Transform
+ - uid: 67
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -1.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 68
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -0.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 69
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 0.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 70
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -1.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 71
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -0.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 72
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 0.5,-6.5
+ parent: 2
+ type: Transform
+- proto: FloorDrain
+ entities:
+ - uid: 225
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -3.5,2.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 226
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 2.5,2.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 227
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 2.5,-3.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 228
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -3.5,-3.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 229
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -7.5,-2.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 230
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -7.5,1.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 231
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 6.5,-2.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 232
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 6.5,1.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 233
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 1.5,6.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 234
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -2.5,6.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 235
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -2.5,-7.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 236
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 1.5,-7.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 237
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -6.5,-6.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 238
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 5.5,-6.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 239
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: 5.5,5.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+ - uid: 240
+ components:
+ - rot: 1.5707963267948966 rad
+ pos: -6.5,5.5
+ parent: 2
+ type: Transform
+ - fixtures: {}
+ type: Fixtures
+- proto: FloorLavaEntity
+ entities:
+ - uid: 3
+ components:
+ - pos: -10.5,3.5
+ parent: 2
+ type: Transform
+ - uid: 4
+ components:
+ - pos: -10.5,2.5
+ parent: 2
+ type: Transform
+ - uid: 5
+ components:
+ - pos: -10.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 6
+ components:
+ - pos: -10.5,0.5
+ parent: 2
+ type: Transform
+ - uid: 7
+ components:
+ - pos: -10.5,-0.5
+ parent: 2
+ type: Transform
+ - uid: 8
+ components:
+ - pos: -10.5,-1.5
+ parent: 2
+ type: Transform
+ - uid: 9
+ components:
+ - pos: -10.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 10
+ components:
+ - pos: -10.5,-3.5
+ parent: 2
+ type: Transform
+ - uid: 11
+ components:
+ - pos: -10.5,-4.5
+ parent: 2
+ type: Transform
+ - uid: 12
+ components:
+ - pos: 9.5,-4.5
+ parent: 2
+ type: Transform
+ - uid: 13
+ components:
+ - pos: 9.5,-3.5
+ parent: 2
+ type: Transform
+ - uid: 14
+ components:
+ - pos: 9.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 15
+ components:
+ - pos: 9.5,-1.5
+ parent: 2
+ type: Transform
+ - uid: 16
+ components:
+ - pos: 9.5,-0.5
+ parent: 2
+ type: Transform
+ - uid: 17
+ components:
+ - pos: 9.5,0.5
+ parent: 2
+ type: Transform
+ - uid: 18
+ components:
+ - pos: 9.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 19
+ components:
+ - pos: 9.5,2.5
+ parent: 2
+ type: Transform
+ - uid: 20
+ components:
+ - pos: 9.5,3.5
+ parent: 2
+ type: Transform
+ - uid: 21
+ components:
+ - pos: 3.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 22
+ components:
+ - pos: 2.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 23
+ components:
+ - pos: 1.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 24
+ components:
+ - pos: 0.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 25
+ components:
+ - pos: -0.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 26
+ components:
+ - pos: -1.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 27
+ components:
+ - pos: -2.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 28
+ components:
+ - pos: -3.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 29
+ components:
+ - pos: -4.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 30
+ components:
+ - pos: -4.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 31
+ components:
+ - pos: -3.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 32
+ components:
+ - pos: -2.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 33
+ components:
+ - pos: -1.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 34
+ components:
+ - pos: -0.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 35
+ components:
+ - pos: 0.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 36
+ components:
+ - pos: 1.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 37
+ components:
+ - pos: 2.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 38
+ components:
+ - pos: 3.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 39
+ components:
+ - pos: -3.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 40
+ components:
+ - pos: -2.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 41
+ components:
+ - pos: -2.5,2.5
+ parent: 2
+ type: Transform
+ - uid: 42
+ components:
+ - pos: 1.5,2.5
+ parent: 2
+ type: Transform
+ - uid: 43
+ components:
+ - pos: 1.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 44
+ components:
+ - pos: 2.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 45
+ components:
+ - pos: 1.5,-3.5
+ parent: 2
+ type: Transform
+ - uid: 46
+ components:
+ - pos: 1.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 47
+ components:
+ - pos: 2.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 48
+ components:
+ - pos: -2.5,-3.5
+ parent: 2
+ type: Transform
+ - uid: 49
+ components:
+ - pos: -2.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 50
+ components:
+ - pos: -3.5,-2.5
+ parent: 2
+ type: Transform
+- proto: SpawnPointAssistant
+ entities:
+ - uid: 99
+ components:
+ - pos: -0.5,-0.5
+ parent: 2
+ type: Transform
+- proto: SpawnPointLatejoin
+ entities:
+ - uid: 83
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -7.5,0.5
+ parent: 2
+ type: Transform
+ - uid: 84
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -7.5,-1.5
+ parent: 2
+ type: Transform
+ - uid: 85
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -1.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 86
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 0.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 87
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 6.5,-1.5
+ parent: 2
+ type: Transform
+ - uid: 88
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 6.5,0.5
+ parent: 2
+ type: Transform
+ - uid: 89
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -1.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 90
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 0.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 91
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -6.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 92
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -7.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 93
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 5.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 94
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 6.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 95
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 5.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 96
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 6.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 97
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -6.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 98
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -7.5,-6.5
+ parent: 2
+ type: Transform
+- proto: SpawnPointObserver
+ entities:
+ - uid: 100
+ components:
+ - pos: -0.5,-0.5
+ parent: 2
+ type: Transform
+- proto: WallPlastitaniumIndestructible
+ entities:
+ - uid: 51
+ components:
+ - pos: -3.5,2.5
+ parent: 2
+ type: Transform
+ - uid: 52
+ components:
+ - pos: -3.5,-3.5
+ parent: 2
+ type: Transform
+ - uid: 53
+ components:
+ - pos: 2.5,-3.5
+ parent: 2
+ type: Transform
+ - uid: 54
+ components:
+ - pos: 2.5,2.5
+ parent: 2
+ type: Transform
+ - uid: 55
+ components:
+ - pos: -7.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 56
+ components:
+ - pos: -7.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 57
+ components:
+ - pos: 6.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 58
+ components:
+ - pos: 6.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 59
+ components:
+ - pos: 1.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 60
+ components:
+ - pos: -2.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 81
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: -2.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 82
+ components:
+ - rot: -1.5707963267948966 rad
+ pos: 1.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 101
+ components:
+ - pos: -8.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 102
+ components:
+ - pos: -7.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 103
+ components:
+ - pos: -6.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 104
+ components:
+ - pos: -6.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 105
+ components:
+ - pos: -5.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 106
+ components:
+ - pos: -5.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 107
+ components:
+ - pos: -5.5,7.5
+ parent: 2
+ type: Transform
+ - uid: 108
+ components:
+ - pos: 4.5,7.5
+ parent: 2
+ type: Transform
+ - uid: 109
+ components:
+ - pos: 4.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 110
+ components:
+ - pos: 4.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 111
+ components:
+ - pos: 5.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 112
+ components:
+ - pos: 5.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 113
+ components:
+ - pos: 6.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 114
+ components:
+ - pos: 7.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 115
+ components:
+ - pos: 7.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 116
+ components:
+ - pos: 6.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 117
+ components:
+ - pos: 5.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 118
+ components:
+ - pos: 5.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 119
+ components:
+ - pos: 4.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 120
+ components:
+ - pos: 4.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 121
+ components:
+ - pos: 4.5,-8.5
+ parent: 2
+ type: Transform
+ - uid: 122
+ components:
+ - pos: -5.5,-8.5
+ parent: 2
+ type: Transform
+ - uid: 123
+ components:
+ - pos: -5.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 124
+ components:
+ - pos: -5.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 125
+ components:
+ - pos: -6.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 126
+ components:
+ - pos: -6.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 127
+ components:
+ - pos: -7.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 128
+ components:
+ - pos: -8.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 129
+ components:
+ - pos: -5.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 130
+ components:
+ - pos: -4.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 131
+ components:
+ - pos: -3.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 132
+ components:
+ - pos: -2.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 133
+ components:
+ - pos: -1.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 134
+ components:
+ - pos: -0.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 135
+ components:
+ - pos: 0.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 136
+ components:
+ - pos: 1.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 137
+ components:
+ - pos: 2.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 138
+ components:
+ - pos: 3.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 139
+ components:
+ - pos: 4.5,10.5
+ parent: 2
+ type: Transform
+ - uid: 140
+ components:
+ - pos: 4.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 141
+ components:
+ - pos: 5.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 142
+ components:
+ - pos: 6.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 143
+ components:
+ - pos: 7.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 144
+ components:
+ - pos: 7.5,8.5
+ parent: 2
+ type: Transform
+ - uid: 145
+ components:
+ - pos: 7.5,7.5
+ parent: 2
+ type: Transform
+ - uid: 146
+ components:
+ - pos: 8.5,7.5
+ parent: 2
+ type: Transform
+ - uid: 147
+ components:
+ - pos: 9.5,7.5
+ parent: 2
+ type: Transform
+ - uid: 148
+ components:
+ - pos: 9.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 149
+ components:
+ - pos: 9.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 150
+ components:
+ - pos: 9.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 151
+ components:
+ - pos: 10.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 152
+ components:
+ - pos: 10.5,3.5
+ parent: 2
+ type: Transform
+ - uid: 153
+ components:
+ - pos: 10.5,2.5
+ parent: 2
+ type: Transform
+ - uid: 154
+ components:
+ - pos: 10.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 155
+ components:
+ - pos: 10.5,0.5
+ parent: 2
+ type: Transform
+ - uid: 156
+ components:
+ - pos: 10.5,-0.5
+ parent: 2
+ type: Transform
+ - uid: 157
+ components:
+ - pos: 10.5,-1.5
+ parent: 2
+ type: Transform
+ - uid: 158
+ components:
+ - pos: 10.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 159
+ components:
+ - pos: 10.5,-3.5
+ parent: 2
+ type: Transform
+ - uid: 160
+ components:
+ - pos: 10.5,-4.5
+ parent: 2
+ type: Transform
+ - uid: 161
+ components:
+ - pos: 10.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 162
+ components:
+ - pos: 9.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 163
+ components:
+ - pos: 9.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 164
+ components:
+ - pos: 9.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 165
+ components:
+ - pos: 9.5,-8.5
+ parent: 2
+ type: Transform
+ - uid: 166
+ components:
+ - pos: 8.5,-8.5
+ parent: 2
+ type: Transform
+ - uid: 167
+ components:
+ - pos: 7.5,-8.5
+ parent: 2
+ type: Transform
+ - uid: 168
+ components:
+ - pos: 7.5,-9.5
+ parent: 2
+ type: Transform
+ - uid: 169
+ components:
+ - pos: 7.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 170
+ components:
+ - pos: 6.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 171
+ components:
+ - pos: 5.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 172
+ components:
+ - pos: 4.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 173
+ components:
+ - pos: 4.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 174
+ components:
+ - pos: 3.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 175
+ components:
+ - pos: 2.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 176
+ components:
+ - pos: 1.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 177
+ components:
+ - pos: 0.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 178
+ components:
+ - pos: -0.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 179
+ components:
+ - pos: -1.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 180
+ components:
+ - pos: -2.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 181
+ components:
+ - pos: -3.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 182
+ components:
+ - pos: -4.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 183
+ components:
+ - pos: -5.5,-11.5
+ parent: 2
+ type: Transform
+ - uid: 184
+ components:
+ - pos: -5.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 185
+ components:
+ - pos: -6.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 186
+ components:
+ - pos: -7.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 187
+ components:
+ - pos: -8.5,-10.5
+ parent: 2
+ type: Transform
+ - uid: 188
+ components:
+ - pos: -8.5,-9.5
+ parent: 2
+ type: Transform
+ - uid: 189
+ components:
+ - pos: -8.5,-8.5
+ parent: 2
+ type: Transform
+ - uid: 190
+ components:
+ - pos: -9.5,-8.5
+ parent: 2
+ type: Transform
+ - uid: 191
+ components:
+ - pos: -10.5,-8.5
+ parent: 2
+ type: Transform
+ - uid: 192
+ components:
+ - pos: -10.5,-7.5
+ parent: 2
+ type: Transform
+ - uid: 193
+ components:
+ - pos: -10.5,-6.5
+ parent: 2
+ type: Transform
+ - uid: 194
+ components:
+ - pos: -10.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 195
+ components:
+ - pos: -11.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 196
+ components:
+ - pos: -11.5,3.5
+ parent: 2
+ type: Transform
+ - uid: 197
+ components:
+ - pos: -11.5,2.5
+ parent: 2
+ type: Transform
+ - uid: 198
+ components:
+ - pos: -11.5,1.5
+ parent: 2
+ type: Transform
+ - uid: 199
+ components:
+ - pos: -11.5,0.5
+ parent: 2
+ type: Transform
+ - uid: 200
+ components:
+ - pos: -11.5,-0.5
+ parent: 2
+ type: Transform
+ - uid: 201
+ components:
+ - pos: -11.5,-1.5
+ parent: 2
+ type: Transform
+ - uid: 202
+ components:
+ - pos: -11.5,-2.5
+ parent: 2
+ type: Transform
+ - uid: 203
+ components:
+ - pos: -11.5,-3.5
+ parent: 2
+ type: Transform
+ - uid: 204
+ components:
+ - pos: -11.5,-4.5
+ parent: 2
+ type: Transform
+ - uid: 205
+ components:
+ - pos: -11.5,-5.5
+ parent: 2
+ type: Transform
+ - uid: 206
+ components:
+ - pos: -10.5,4.5
+ parent: 2
+ type: Transform
+ - uid: 207
+ components:
+ - pos: -10.5,5.5
+ parent: 2
+ type: Transform
+ - uid: 208
+ components:
+ - pos: -10.5,6.5
+ parent: 2
+ type: Transform
+ - uid: 209
+ components:
+ - pos: -10.5,7.5
+ parent: 2
+ type: Transform
+ - uid: 210
+ components:
+ - pos: -9.5,7.5
+ parent: 2
+ type: Transform
+ - uid: 211
+ components:
+ - pos: -8.5,7.5
+ parent: 2
+ type: Transform
+ - uid: 212
+ components:
+ - pos: -8.5,8.5
+ parent: 2
+ type: Transform
+ - uid: 213
+ components:
+ - pos: -8.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 214
+ components:
+ - pos: -7.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 215
+ components:
+ - pos: -6.5,9.5
+ parent: 2
+ type: Transform
+ - uid: 216
+ components:
+ - pos: -5.5,9.5
+ parent: 2
+ type: Transform
+...
diff --git a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml
index 823e2ffc4a..aa9187a2eb 100644
--- a/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml
+++ b/Resources/Prototypes/Entities/Objects/Specific/Medical/healing.yml
@@ -298,6 +298,32 @@
stackType: AloeCream
count: 10
+- type: entity
+ parent: BaseHealingItem
+ id: HealingToolbox
+ name: healing toolbox
+ description: A powerful toolbox imbued with robust energy. It can heal your wounds and fill you with murderous intent.
+ suffix: Do Not Map
+ components:
+ - type: Sprite
+ sprite: Objects/Specific/Medical/healing_toolbox.rsi
+ state: icon
+ - type: Healing
+ damageContainers:
+ - Biological
+ damage:
+ groups: # these are all split across multiple types
+ Brute: -150
+ Burn: -150
+ Toxin: -150
+ bloodlossModifier: -20
+ delay: 1
+ selfHealPenaltyMultiplier: 0
+ healingBeginSound:
+ path: "/Audio/Items/Medical/ointment_begin.ogg"
+ healingEndSound:
+ path: "/Audio/Items/Medical/ointment_end.ogg"
+
# Pills
- type: entity
name: dexalin pill (10u)
diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml
new file mode 100644
index 0000000000..9eaf14cf9a
--- /dev/null
+++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/weapon_toolbox.yml
@@ -0,0 +1,18 @@
+- type: entity
+ parent: BaseItem
+ id: WeaponMeleeToolboxRobust
+ name: robust toolbox
+ description: A tider's weapon.
+ suffix: Do Not Map
+ components:
+ - type: Sprite
+ sprite: Objects/Tools/Toolboxes/toolbox_red.rsi
+ state: icon
+ - type: Item
+ size: 150
+ sprite: Objects/Tools/Toolboxes/toolbox_red.rsi
+ - type: MeleeWeapon
+ attackRate: 1.5
+ damage:
+ types:
+ Blunt: 20
diff --git a/Resources/Prototypes/Entities/Stations/nanotrasen.yml b/Resources/Prototypes/Entities/Stations/nanotrasen.yml
index 40cdcfe489..701db32588 100644
--- a/Resources/Prototypes/Entities/Stations/nanotrasen.yml
+++ b/Resources/Prototypes/Entities/Stations/nanotrasen.yml
@@ -35,3 +35,14 @@
noSpawn: true
components:
- type: Transform
+
+- type: entity
+ id: StandardStationArena
+ parent:
+ - BaseStation
+ - BaseStationJobsSpawning
+ - BaseStationRecords
+ - BaseStationNanotrasen
+ noSpawn: true
+ components:
+ - type: Transform
diff --git a/Resources/Prototypes/GameRules/roundstart.yml b/Resources/Prototypes/GameRules/roundstart.yml
index 1346631af5..0fc6de32ec 100644
--- a/Resources/Prototypes/GameRules/roundstart.yml
+++ b/Resources/Prototypes/GameRules/roundstart.yml
@@ -6,11 +6,34 @@
- type: GameRule
- type: entity
- id: DeathMatch
+ id: DeathMatch31
parent: BaseGameRule
noSpawn: true
components:
- type: DeathMatchRule
+ rewardSpawns:
+ - id: HealingToolbox
+ - id: ClothingOuterArmorBasicSlim
+ orGroup: loot
+ - id: ClothingHeadHelmetBasic
+ orGroup: loot
+ - id: SoapNT
+ orGroup: loot
+ - id: Bola
+ orGroup: loot
+ - id: Spear
+ orGroup: loot
+ - id: ClothingShoesGaloshes
+ orGroup: loot
+ - id: FoodPieBananaCream
+ orGroup: loot
+ - id: Stimpack
+ orGroup: loot
+ - type: KillCalloutRule
+ - type: PointManager
+ - type: RespawnDeadRule
+ - type: RespawnTracker
+ respawnDelay: 5
- type: entity
id: InactivityTimeRestart
diff --git a/Resources/Prototypes/Maps/Pools/deathmatch.yml b/Resources/Prototypes/Maps/Pools/deathmatch.yml
new file mode 100644
index 0000000000..e263e1523b
--- /dev/null
+++ b/Resources/Prototypes/Maps/Pools/deathmatch.yml
@@ -0,0 +1,4 @@
+- type: gameMapPool
+ id: DeathMatchMapPool
+ maps:
+ - MeteorArena
diff --git a/Resources/Prototypes/Maps/arenas.yml b/Resources/Prototypes/Maps/arenas.yml
new file mode 100644
index 0000000000..32f8543722
--- /dev/null
+++ b/Resources/Prototypes/Maps/arenas.yml
@@ -0,0 +1,16 @@
+- type: gameMap
+ id: MeteorArena
+ mapName: Meteor Arena
+ mapPath: /Maps/Nonstations/meteor-arena.yml
+ minPlayers: 0
+ stations:
+ Arena:
+ stationProto: StandardStationArena
+ components:
+ - type: StationNameSetup
+ mapNameTemplate: "Meteor Arena"
+ - type: StationJobs
+ overflowJobs:
+ - Passenger
+ availableJobs:
+ Passenger: [ -1, -1 ]
diff --git a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml
index a1896f135a..0d8059f0fc 100644
--- a/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml
+++ b/Resources/Prototypes/Roles/Jobs/Fun/misc_startinggear.yml
@@ -277,6 +277,19 @@
jumpsuit: ClothingUniformJumpsuitJacketMonkey
id: BartenderIDCard
+# DeathMatch Gear
+
+- type: startingGear
+ id: DeathMatchGear
+ equipment:
+ jumpsuit: ClothingUniformJumpsuitColorWhite
+ shoes: ClothingShoesBootsJack
+ ears: ClothingHeadsetGrey
+ gloves: ClothingHandsGlovesFingerless
+ innerclothingskirt: ClothingUniformJumpskirtColorWhite
+ inhand:
+ left hand: WeaponMeleeToolboxRobust
+
#Brigmedic
- type: startingGear
diff --git a/Resources/Prototypes/game_presets.yml b/Resources/Prototypes/game_presets.yml
index c732e6c250..e12822b2ea 100644
--- a/Resources/Prototypes/game_presets.yml
+++ b/Resources/Prototypes/game_presets.yml
@@ -59,8 +59,11 @@
- dm
name: death-match-title
description: death-match-description
+ maxPlayers: 15
+ showInVote: true
+ supportedMaps: DeathMatchMapPool
rules:
- - DeathMatch
+ - DeathMatch31
- type: gamePreset
id: Nukeops
diff --git a/Resources/Textures/Objects/Specific/Medical/healing_toolbox.rsi/icon.png b/Resources/Textures/Objects/Specific/Medical/healing_toolbox.rsi/icon.png
new file mode 100644
index 0000000000..b80dd151b4
Binary files /dev/null and b/Resources/Textures/Objects/Specific/Medical/healing_toolbox.rsi/icon.png differ
diff --git a/Resources/Textures/Objects/Specific/Medical/healing_toolbox.rsi/meta.json b/Resources/Textures/Objects/Specific/Medical/healing_toolbox.rsi/meta.json
new file mode 100644
index 0000000000..d8c80a1fd6
--- /dev/null
+++ b/Resources/Textures/Objects/Specific/Medical/healing_toolbox.rsi/meta.json
@@ -0,0 +1,14 @@
+{
+ "version": 1,
+ "license": "CC0-1.0",
+ "copyright": "Created by EmoGarbage404 (github) for Space Station 14.",
+ "size": {
+ "x": 32,
+ "y": 32
+ },
+ "states": [
+ {
+ "name": "icon"
+ }
+ ]
+}