1v1 me first to 31 no powerups [Deathmatch Gamemode] (#19467)
Co-authored-by: Kara <lunarautomaton6@gmail.com>
This commit is contained in:
58
Content.Client/Points/PointSystem.cs
Normal file
58
Content.Client/Points/PointSystem.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class PointSystem : SharedPointSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly CharacterInfoSystem _characterInfo = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<PointManagerComponent, ComponentHandleState>(OnHandleState);
|
||||||
|
SubscribeLocalEvent<CharacterInfoSystem.GetCharacterInfoControlsEvent>(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<PointManagerComponent>())
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -109,6 +109,7 @@ public sealed class CharacterUIController : UIController, IOnStateEntered<Gamepl
|
|||||||
_window.NameLabel.Text = entityName;
|
_window.NameLabel.Text = entityName;
|
||||||
_window.SubText.Text = job;
|
_window.SubText.Text = job;
|
||||||
_window.Objectives.RemoveAllChildren();
|
_window.Objectives.RemoveAllChildren();
|
||||||
|
_window.ObjectivesLabel.Visible = objectives.Any();
|
||||||
|
|
||||||
foreach (var (groupId, conditions) in objectives)
|
foreach (var (groupId, conditions) in objectives)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
<Label Name="SubText" VerticalAlignment="Top" StyleClasses="LabelSubText" Access="Public"/>
|
<Label Name="SubText" VerticalAlignment="Top" StyleClasses="LabelSubText" Access="Public"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<Label Text="{Loc 'character-info-objectives-label'}" HorizontalAlignment="Center"/>
|
<Label Name="ObjectivesLabel" Access="Public" Text="{Loc 'character-info-objectives-label'}" HorizontalAlignment="Center"/>
|
||||||
<BoxContainer Orientation="Vertical" Name="Objectives" Access="Public"/>
|
<BoxContainer Orientation="Vertical" Name="Objectives" Access="Public"/>
|
||||||
<cc:Placeholder Name="RolePlaceholder" Access="Public" PlaceholderText="{Loc 'character-info-roles-antagonist-text'}"/>
|
<cc:Placeholder Name="RolePlaceholder" Access="Public" PlaceholderText="{Loc 'character-info-roles-antagonist-text'}"/>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
|
|||||||
@@ -60,7 +60,8 @@ namespace Content.IntegrationTests.Tests
|
|||||||
"Saltern",
|
"Saltern",
|
||||||
"Core",
|
"Core",
|
||||||
"Marathon",
|
"Marathon",
|
||||||
"Kettle"
|
"Kettle",
|
||||||
|
"MeteorArena"
|
||||||
};
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ namespace Content.Server.Chat
|
|||||||
/// <returns>Returns true if there was a blocked attempt</returns>
|
/// <returns>Returns true if there was a blocked attempt</returns>
|
||||||
private bool SuicideAttemptBlocked(EntityUid victim, SuicideEvent suicideEvent)
|
private bool SuicideAttemptBlocked(EntityUid victim, SuicideEvent suicideEvent)
|
||||||
{
|
{
|
||||||
RaiseLocalEvent(victim, suicideEvent, false);
|
RaiseLocalEvent(victim, suicideEvent, true);
|
||||||
|
|
||||||
if (suicideEvent.AttemptBlocked)
|
if (suicideEvent.AttemptBlocked)
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -144,8 +144,9 @@ namespace Content.Server.GameTicking
|
|||||||
return (HumanoidCharacterProfile) _prefsManager.GetPreferences(p.UserId).SelectedCharacter;
|
return (HumanoidCharacterProfile) _prefsManager.GetPreferences(p.UserId).SelectedCharacter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void PlayerJoinGame(IPlayerSession session)
|
public void PlayerJoinGame(IPlayerSession session, bool silent = false)
|
||||||
{
|
{
|
||||||
|
if (!silent)
|
||||||
_chatManager.DispatchServerMessage(session, Loc.GetString("game-ticker-player-join-game-message"));
|
_chatManager.DispatchServerMessage(session, Loc.GetString("game-ticker-player-join-game-message"));
|
||||||
|
|
||||||
_playerGameStatuses[session.UserId] = PlayerGameStatus.JoinedGame;
|
_playerGameStatuses[session.UserId] = PlayerGameStatus.JoinedGame;
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
if (CurrentPreset?.MapPool != null &&
|
if (CurrentPreset?.MapPool != null &&
|
||||||
_prototypeManager.TryIndex<GameMapPoolPrototype>(CurrentPreset.MapPool, out var pool) &&
|
_prototypeManager.TryIndex<GameMapPoolPrototype>(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",
|
var msg = Loc.GetString("game-ticker-start-round-invalid-map",
|
||||||
("map", mainStationMap.MapName),
|
("map", mainStationMap.MapName),
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ namespace Content.Server.GameTicking
|
|||||||
RaiseLocalEvent(new RulePlayerJobsAssignedEvent(assignedJobs.Keys.Select(x => _playerManager.GetSessionByUserId(x)).ToArray(), profiles, force));
|
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);
|
var character = GetPlayerProfile(player);
|
||||||
|
|
||||||
@@ -114,10 +114,10 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId))
|
if (jobId != null && !_playTimeTrackings.IsAllowed(player, jobId))
|
||||||
return;
|
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!
|
// Can't spawn players with a dummy ticker!
|
||||||
if (DummyTicker)
|
if (DummyTicker)
|
||||||
@@ -150,7 +150,7 @@ namespace Content.Server.GameTicking
|
|||||||
// Do nothing, something else has handled spawning this player for us!
|
// Do nothing, something else has handled spawning this player for us!
|
||||||
if (bev.Handled)
|
if (bev.Handled)
|
||||||
{
|
{
|
||||||
PlayerJoinGame(player);
|
PlayerJoinGame(player, silent);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +177,7 @@ namespace Content.Server.GameTicking
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlayerJoinGame(player);
|
PlayerJoinGame(player, silent);
|
||||||
|
|
||||||
var data = player.ContentData();
|
var data = player.ContentData();
|
||||||
|
|
||||||
@@ -188,7 +188,7 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
|
var jobPrototype = _prototypeManager.Index<JobPrototype>(jobId);
|
||||||
var job = new JobComponent { PrototypeId = jobId };
|
var job = new JobComponent { PrototypeId = jobId };
|
||||||
_roles.MindAddRole(newMind, job);
|
_roles.MindAddRole(newMind, job, silent: silent);
|
||||||
var jobName = _jobs.MindTryGetJobName(newMind);
|
var jobName = _jobs.MindTryGetJobName(newMind);
|
||||||
|
|
||||||
_playTimeTrackings.PlayerRolesChanged(player);
|
_playTimeTrackings.PlayerRolesChanged(player);
|
||||||
@@ -199,7 +199,7 @@ namespace Content.Server.GameTicking
|
|||||||
|
|
||||||
_mind.TransferTo(newMind, mob);
|
_mind.TransferTo(newMind, mob);
|
||||||
|
|
||||||
if (lateJoin)
|
if (lateJoin && !silent)
|
||||||
{
|
{
|
||||||
_chatSystem.DispatchStationAnnouncement(station,
|
_chatSystem.DispatchStationAnnouncement(station,
|
||||||
Loc.GetString(
|
Loc.GetString(
|
||||||
@@ -230,7 +230,7 @@ namespace Content.Server.GameTicking
|
|||||||
_chatManager.DispatchServerMessage(player, Loc.GetString("job-greet-crew-shortages"));
|
_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,
|
_chatManager.DispatchServerMessage(player,
|
||||||
Loc.GetString("job-greet-station-name", ("stationName", metaData.EntityName)));
|
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.
|
// Arrivals is unable to do this during spawning as no actor is attached yet.
|
||||||
// We also want this message last.
|
// We also want this message last.
|
||||||
if (lateJoin && _arrivals.Enabled)
|
if (!silent && lateJoin && _arrivals.Enabled)
|
||||||
{
|
{
|
||||||
var arrival = _arrivals.NextShuttleArrival();
|
var arrival = _arrivals.NextShuttleArrival();
|
||||||
if (arrival == null)
|
if (arrival == null)
|
||||||
@@ -269,7 +269,14 @@ namespace Content.Server.GameTicking
|
|||||||
SpawnPlayer(player, EntityUid.Invalid);
|
SpawnPlayer(player, EntityUid.Invalid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void MakeJoinGame(IPlayerSession player, EntityUid station, string? jobId = null)
|
/// <summary>
|
||||||
|
/// Makes a player join into the game and spawn on a staiton.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="player">The player joining</param>
|
||||||
|
/// <param name="station">The station they're spawning on</param>
|
||||||
|
/// <param name="jobId">An optional job for them to spawn as</param>
|
||||||
|
/// <param name="silent">Whether or not the player should be greeted upon joining</param>
|
||||||
|
public void MakeJoinGame(IPlayerSession player, EntityUid station, string? jobId = null, bool silent = false)
|
||||||
{
|
{
|
||||||
if (!_playerGameStatuses.ContainsKey(player.UserId))
|
if (!_playerGameStatuses.ContainsKey(player.UserId))
|
||||||
return;
|
return;
|
||||||
@@ -277,7 +284,7 @@ namespace Content.Server.GameTicking
|
|||||||
if (!_userDb.IsLoadComplete(player))
|
if (!_userDb.IsLoadComplete(player))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SpawnPlayer(player, station, jobId);
|
SpawnPlayer(player, station, jobId, silent: silent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Simple GameRule that will do a free-for-all death match.
|
/// Gamerule that ends when a player gets a certain number of kills.
|
||||||
/// Kill everybody else to win.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[RegisterComponent, Access(typeof(DeathMatchRuleSystem))]
|
[RegisterComponent, Access(typeof(DeathMatchRuleSystem))]
|
||||||
public sealed partial class DeathMatchRuleComponent : Component
|
public sealed partial class DeathMatchRuleComponent : Component
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The number of points a player has to get to win.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("killCap"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public FixedPoint2 KillCap = 31;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How long until the round restarts
|
/// How long until the round restarts
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("restartDelay"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("restartDelay"), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float RestartDelay = 10f;
|
public TimeSpan RestartDelay = TimeSpan.FromSeconds(10f);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("deadCheckDelay"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("victor")]
|
||||||
public float DeadCheckDelay = 5f;
|
public NetUserId? Victor;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A timer for checking after a death
|
/// An entity spawned after a player is killed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("deadCheckTimer"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("rewardSpawns")]
|
||||||
public float? DeadCheckTimer;
|
public List<EntitySpawnEntry> RewardSpawns = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A timer for the restart.
|
/// The gear all players spawn with.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[DataField("restartTimer"), ViewVariables(VVAccess.ReadWrite)]
|
[DataField("gear", customTypeSerializer: typeof(PrototypeIdSerializer<StartingGearPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||||
public float? RestartTimer;
|
public string Gear = "DeathMatchGear";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for a rule that announces kills globally.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(KillCalloutRuleSystem))]
|
||||||
|
public sealed partial class KillCalloutRuleComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Root used to generate kill callouts
|
||||||
|
/// </summary>
|
||||||
|
[DataField("killCalloutPrefix")]
|
||||||
|
public string KillCalloutPrefix = "death-match-kill-callout-";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value used to randomly select a kill callout
|
||||||
|
/// </summary>
|
||||||
|
[DataField("killCalloutAmount")]
|
||||||
|
public int KillCalloutAmount = 60;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Root used to generate kill callouts when a player is killed by the environment
|
||||||
|
/// </summary>
|
||||||
|
[DataField("environmentKillCallouts")]
|
||||||
|
public string SelfKillCalloutPrefix = "death-match-kill-callout-env-";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A value used to randomly select a kill callout when a player is killed by the environment
|
||||||
|
/// </summary>
|
||||||
|
[DataField("selfKillCalloutAmount")]
|
||||||
|
public int SelfKillCalloutAmount = 10;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for gamemodes that automatically respawn players when they're no longer alive.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(RespawnRuleSystem))]
|
||||||
|
public sealed partial class RespawnDeadRuleComponent : Component
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.GameTicking.Rules.Components;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for globally tracking players that need to be respawned.
|
||||||
|
/// Used on gamerule entities.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(RespawnRuleSystem))]
|
||||||
|
public sealed partial class RespawnTrackerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A list of the people that should be respawned.
|
||||||
|
/// Used to make sure that we don't respawn aghosts or observers.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("players")]
|
||||||
|
public HashSet<NetUserId> Players = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The delay between dying and respawning.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("respawnDelay")]
|
||||||
|
public TimeSpan RespawnDelay = TimeSpan.Zero;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary of player netuserids and when they will respawn.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("respawnQueue")]
|
||||||
|
public Dictionary<NetUserId, TimeSpan> RespawnQueue = new();
|
||||||
|
}
|
||||||
@@ -1,12 +1,14 @@
|
|||||||
using Content.Server.Chat.Managers;
|
using Content.Server.Administration.Commands;
|
||||||
using Content.Server.GameTicking.Rules.Components;
|
using Content.Server.GameTicking.Rules.Components;
|
||||||
using Content.Shared.CCVar;
|
using Content.Server.KillTracking;
|
||||||
using Content.Shared.Damage;
|
using Content.Server.Mind;
|
||||||
using Content.Shared.Mobs.Components;
|
using Content.Server.Points;
|
||||||
using Content.Shared.Mobs.Systems;
|
using Content.Server.RoundEnd;
|
||||||
|
using Content.Server.Station.Systems;
|
||||||
|
using Content.Shared.Points;
|
||||||
|
using Content.Shared.Storage;
|
||||||
using Robust.Server.Player;
|
using Robust.Server.Player;
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Utility;
|
||||||
using Robust.Shared.Enums;
|
|
||||||
|
|
||||||
namespace Content.Server.GameTicking.Rules;
|
namespace Content.Server.GameTicking.Rules;
|
||||||
|
|
||||||
@@ -15,116 +17,116 @@ namespace Content.Server.GameTicking.Rules;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class DeathMatchRuleSystem : GameRuleSystem<DeathMatchRuleComponent>
|
public sealed class DeathMatchRuleSystem : GameRuleSystem<DeathMatchRuleComponent>
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
[Dependency] private readonly MindSystem _mind = default!;
|
||||||
[Dependency] private readonly MobStateSystem _mobStateSystem = default!;
|
[Dependency] private readonly PointSystem _point = default!;
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly RespawnRuleSystem _respawn = default!;
|
||||||
|
[Dependency] private readonly RoundEndSystem _roundEnd = default!;
|
||||||
|
[Dependency] private readonly StationSpawningSystem _stationSpawning = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<DamageChangedEvent>(OnHealthChanged);
|
SubscribeLocalEvent<PlayerBeforeSpawnEvent>(OnBeforeSpawn);
|
||||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
|
||||||
|
SubscribeLocalEvent<KillReportedEvent>(OnKillReported);
|
||||||
|
SubscribeLocalEvent<DeathMatchRuleComponent, PlayerPointChangedEvent>(OnPointChanged);
|
||||||
|
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndTextAppend);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
private void OnBeforeSpawn(PlayerBeforeSpawnEvent ev)
|
||||||
{
|
{
|
||||||
base.Shutdown();
|
var query = EntityQueryEnumerator<DeathMatchRuleComponent, RespawnTrackerComponent, PointManagerComponent, GameRuleComponent>();
|
||||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
while (query.MoveNext(out var uid, out var dm, out var tracker, out var point, out var rule))
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Started(EntityUid uid, DeathMatchRuleComponent component, GameRuleComponent gameRule, GameRuleStartedEvent args)
|
|
||||||
{
|
{
|
||||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
if (!GameTicker.IsGameRuleActive(uid, rule))
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
RunDelayedCheck();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void RunDelayedCheck()
|
|
||||||
{
|
|
||||||
var query = EntityQueryEnumerator<DeathMatchRuleComponent, GameRuleComponent>();
|
|
||||||
while (query.MoveNext(out var uid, out var deathMatch, out var gameRule))
|
|
||||||
{
|
|
||||||
if (!GameTicker.IsGameRuleActive(uid, gameRule) || deathMatch.DeadCheckTimer != null)
|
|
||||||
continue;
|
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<KillTrackerComponent>(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);
|
EnsureComp<KillTrackerComponent>(ev.Mob);
|
||||||
|
var query = EntityQueryEnumerator<DeathMatchRuleComponent, RespawnTrackerComponent, GameRuleComponent>();
|
||||||
// If the restart timer is active, that means the round is ending soon, no need to check for winners.
|
while (query.MoveNext(out var uid, out _, out var tracker, out var rule))
|
||||||
// TODO: We probably want a sane, centralized round end thingie in GameTicker, RoundEndSystem is no good...
|
|
||||||
if (component.RestartTimer != null)
|
|
||||||
{
|
{
|
||||||
component.RestartTimer -= frameTime;
|
if (!GameTicker.IsGameRuleActive(uid, rule))
|
||||||
|
continue;
|
||||||
if (component.RestartTimer > 0f)
|
_respawn.AddToTracker(ev.Mob, uid, tracker);
|
||||||
return;
|
}
|
||||||
|
|
||||||
GameTicker.EndRound();
|
|
||||||
GameTicker.RestartRound();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin) || component.DeadCheckTimer == null)
|
private void OnKillReported(ref KillReportedEvent ev)
|
||||||
return;
|
|
||||||
|
|
||||||
component.DeadCheckTimer -= frameTime;
|
|
||||||
|
|
||||||
if (component.DeadCheckTimer > 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
component.DeadCheckTimer = null;
|
|
||||||
|
|
||||||
IPlayerSession? winner = null;
|
|
||||||
foreach (var playerSession in _playerManager.ServerSessions)
|
|
||||||
{
|
{
|
||||||
if (playerSession.AttachedEntity is not { Valid: true } playerEntity
|
var query = EntityQueryEnumerator<DeathMatchRuleComponent, PointManagerComponent, GameRuleComponent>();
|
||||||
|| !TryComp(playerEntity, out MobStateComponent? state))
|
while (query.MoveNext(out var uid, out var dm, out var point, out var rule))
|
||||||
|
{
|
||||||
|
if (!GameTicker.IsGameRuleActive(uid, rule))
|
||||||
continue;
|
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;
|
continue;
|
||||||
|
|
||||||
// Found a second person alive, nothing decided yet!
|
|
||||||
if (winner != null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
winner = playerSession;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_chatManager.DispatchServerAnnouncement(winner == null
|
_point.AdjustPointValue(player.PlayerId, 1, uid, point);
|
||||||
? 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",
|
if (ev.Assist is KillPlayerSource assist && dm.Victor == null)
|
||||||
("seconds", component.RestartDelay)));
|
_point.AdjustPointValue(assist.PlayerId, 1, uid, point);
|
||||||
component.RestartTimer = component.RestartDelay;
|
|
||||||
|
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<DeathMatchRuleComponent, PointManagerComponent, GameRuleComponent>();
|
||||||
|
while (query.MoveNext(out var uid, out var dm, out var point, out var rule))
|
||||||
|
{
|
||||||
|
if (!GameTicker.IsGameRuleAdded(uid, rule))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (dm.Victor != null && _player.TryGetPlayerData(dm.Victor.Value, out var data))
|
||||||
|
{
|
||||||
|
ev.AddLine(Loc.GetString("point-scoreboard-winner", ("player", data.UserName)));
|
||||||
|
ev.AddLine("");
|
||||||
|
}
|
||||||
|
ev.AddLine(Loc.GetString("point-scoreboard-header"));
|
||||||
|
ev.AddLine(new FormattedMessage(point.Scoreboard).ToMarkup());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
99
Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs
Normal file
99
Content.Server/GameTicking/Rules/KillCalloutRuleSystem.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles calling out kills from <see cref="KillTrackingSystem"/>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class KillCalloutRuleSystem : GameRuleSystem<KillCalloutRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<KillReportedEvent>(OnKillReported);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnKillReported(ref KillReportedEvent ev)
|
||||||
|
{
|
||||||
|
var query = EntityQueryEnumerator<KillCalloutRuleComponent, GameRuleComponent>();
|
||||||
|
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<ActorComponent>(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));
|
||||||
|
}
|
||||||
|
}
|
||||||
145
Content.Server/GameTicking/Rules/RespawnRuleSystem.cs
Normal file
145
Content.Server/GameTicking/Rules/RespawnRuleSystem.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles logic and interactions related to <see cref="RespawnDeadRuleComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class RespawnRuleSystem : GameRuleSystem<RespawnDeadRuleComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||||
|
[Dependency] private readonly IGameTiming _timing = default!;
|
||||||
|
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||||
|
[Dependency] private readonly StationSystem _station = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<SuicideEvent>(OnSuicide);
|
||||||
|
SubscribeLocalEvent<MobStateChangedEvent>(OnMobStateChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSuicide(SuicideEvent ev)
|
||||||
|
{
|
||||||
|
if (!TryComp<ActorComponent>(ev.Victim, out var actor))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var query = EntityQueryEnumerator<RespawnTrackerComponent>();
|
||||||
|
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<ActorComponent>(args.Target, out var actor))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var query = EntityQueryEnumerator<RespawnDeadRuleComponent, GameRuleComponent>();
|
||||||
|
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<RespawnTrackerComponent>())
|
||||||
|
{
|
||||||
|
var queue = new Dictionary<NetUserId, TimeSpan>(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<MindComponent>(mind, out var mindComp) && mindComp.OwnedEntity.HasValue)
|
||||||
|
QueueDel(mindComp.OwnedEntity.Value);
|
||||||
|
GameTicker.MakeJoinGame(session, station, silent: true);
|
||||||
|
tracker.RespawnQueue.Remove(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a given player to the respawn tracker, ensuring that they are respawned if they die.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a given player to the respawn tracker, ensuring that they are respawned if they die.
|
||||||
|
/// </summary>
|
||||||
|
public void AddToTracker(NetUserId id, EntityUid tracker, RespawnTrackerComponent? component = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(tracker, ref component))
|
||||||
|
return;
|
||||||
|
|
||||||
|
component.Players.Add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attempts to directly respawn a player, skipping the lobby screen.
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
62
Content.Server/KillTracking/KillTrackerComponent.cs
Normal file
62
Content.Server/KillTracking/KillTrackerComponent.cs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Mobs;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
|
||||||
|
namespace Content.Server.KillTracking;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is used for entities that track player damage sources and killers.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, Access(typeof(KillTrackingSystem))]
|
||||||
|
public sealed partial class KillTrackerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The mobstate that registers as a "kill"
|
||||||
|
/// </summary>
|
||||||
|
[DataField("killState")]
|
||||||
|
public MobState KillState = MobState.Critical;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary of sources and how much damage they've done to this entity over time.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("lifetimeDamage")]
|
||||||
|
public Dictionary<KillSource, FixedPoint2> LifetimeDamage = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract record KillSource;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A kill source for players
|
||||||
|
/// </summary>
|
||||||
|
[DataDefinition, Serializable]
|
||||||
|
public sealed partial record KillPlayerSource : KillSource
|
||||||
|
{
|
||||||
|
[DataField("playerId")]
|
||||||
|
public NetUserId PlayerId;
|
||||||
|
|
||||||
|
public KillPlayerSource(NetUserId playerId)
|
||||||
|
{
|
||||||
|
PlayerId = playerId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A kill source for non-player controlled entities
|
||||||
|
/// </summary>
|
||||||
|
[DataDefinition, Serializable]
|
||||||
|
public sealed partial record KillNpcSource : KillSource
|
||||||
|
{
|
||||||
|
[DataField("npcEnt")]
|
||||||
|
public EntityUid NpcEnt;
|
||||||
|
|
||||||
|
public KillNpcSource(EntityUid npcEnt)
|
||||||
|
{
|
||||||
|
NpcEnt = npcEnt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A kill source for kills with no damage origin
|
||||||
|
/// </summary>
|
||||||
|
[DataDefinition, Serializable]
|
||||||
|
public sealed partial record KillEnvironmentSource : KillSource;
|
||||||
129
Content.Server/KillTracking/KillTrackingSystem.cs
Normal file
129
Content.Server/KillTracking/KillTrackingSystem.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles <see cref="KillTrackerComponent"/> and recording who is damaging and killing entities.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class KillTrackingSystem : EntitySystem
|
||||||
|
{
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
SubscribeLocalEvent<KillTrackerComponent, DamageChangedEvent>(OnDamageChanged);
|
||||||
|
SubscribeLocalEvent<KillTrackerComponent, MobStateChangedEvent>(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<ActorComponent>(uid)?.PlayerSession.UserId ||
|
||||||
|
assistSource is KillNpcSource assistNpc && assistNpc.NpcEnt == uid ||
|
||||||
|
assistSource is KillPlayerSource assistPlayer && assistPlayer.PlayerId == CompOrNull<ActorComponent>(uid)?.PlayerSession.UserId;
|
||||||
|
|
||||||
|
var ev = new KillReportedEvent(uid, killSource, assistSource, suicide);
|
||||||
|
RaiseLocalEvent(uid, ref ev, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KillSource GetKillSource(EntityUid? sourceEntity)
|
||||||
|
{
|
||||||
|
if (TryComp<ActorComponent>(sourceEntity, out var actor))
|
||||||
|
return new KillPlayerSource(actor.PlayerSession.UserId);
|
||||||
|
if (HasComp<HTNComponent>(sourceEntity))
|
||||||
|
return new KillNpcSource(sourceEntity.Value);
|
||||||
|
return new KillEnvironmentSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
private KillSource? GetLargestSource(Dictionary<KillSource, FixedPoint2> lifetimeDamages)
|
||||||
|
{
|
||||||
|
KillSource? maxSource = null;
|
||||||
|
var maxDamage = FixedPoint2.Zero;
|
||||||
|
foreach (var (source, damage) in lifetimeDamages)
|
||||||
|
{
|
||||||
|
if (damage < maxDamage)
|
||||||
|
continue;
|
||||||
|
maxSource = source;
|
||||||
|
maxDamage = damage;
|
||||||
|
}
|
||||||
|
|
||||||
|
return maxSource;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event broadcasted and raised by-ref on an entity with <see cref="KillTrackerComponent"/> when they are killed.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Entity">The entity that was killed</param>
|
||||||
|
/// <param name="Primary">The primary source of the kill</param>
|
||||||
|
/// <param name="Assist">A secondary source of the kill. Can be null.</param>
|
||||||
|
/// <param name="Suicide">True if the entity that was killed caused their own death.</param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct KillReportedEvent(EntityUid Entity, KillSource Primary, KillSource? Assist, bool Suicide);
|
||||||
@@ -85,7 +85,7 @@ public sealed class GameMapManager : IGameMapManager
|
|||||||
var poolPrototype = _entityManager.System<GameTicker>().Preset?.MapPool ??
|
var poolPrototype = _entityManager.System<GameTicker>().Preset?.MapPool ??
|
||||||
_configurationManager.GetCVar(CCVars.GameMapPool);
|
_configurationManager.GetCVar(CCVars.GameMapPool);
|
||||||
|
|
||||||
if (_prototypeManager.TryIndex<GameMapPoolPrototype>(_configurationManager.GetCVar(CCVars.GameMapPool), out var pool))
|
if (_prototypeManager.TryIndex<GameMapPoolPrototype>(poolPrototype, out var pool))
|
||||||
{
|
{
|
||||||
foreach (var map in pool.Maps)
|
foreach (var map in pool.Maps)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -84,10 +84,18 @@ public sealed class HealingSystem : EntitySystem
|
|||||||
var total = healed?.Total ?? FixedPoint2.Zero;
|
var total = healed?.Total ?? FixedPoint2.Zero;
|
||||||
|
|
||||||
// Re-verify that we can heal the damage.
|
// Re-verify that we can heal the damage.
|
||||||
_stacks.Use(args.Used.Value, 1);
|
|
||||||
|
|
||||||
if (_stacks.GetCount(args.Used.Value) <= 0)
|
if (TryComp<StackComponent>(args.Used.Value, out var stackComp))
|
||||||
|
{
|
||||||
|
_stacks.Use(args.Used.Value, 1, stackComp);
|
||||||
|
|
||||||
|
if (_stacks.GetCount(args.Used.Value, stackComp) <= 0)
|
||||||
dontRepeat = true;
|
dontRepeat = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QueueDel(args.Used.Value);
|
||||||
|
}
|
||||||
|
|
||||||
if (uid != args.User)
|
if (uid != args.User)
|
||||||
{
|
{
|
||||||
@@ -157,7 +165,7 @@ public sealed class HealingSystem : EntitySystem
|
|||||||
if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
|
if (user != target && !_interactionSystem.InRangeUnobstructed(user, target, popup: true))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!TryComp<StackComponent>(uid, out var stack) || stack.Count < 1)
|
if (TryComp<StackComponent>(uid, out var stack) && stack.Count < 1)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!TryComp<BloodstreamComponent>(target, out var bloodstream))
|
if (!TryComp<BloodstreamComponent>(target, out var bloodstream))
|
||||||
|
|||||||
96
Content.Server/Points/PointSystem.cs
Normal file
96
Content.Server/Points/PointSystem.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public sealed class PointSystem : SharedPointSystem
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
|
[Dependency] private readonly PvsOverrideSystem _pvsOverride = default!;
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<PointManagerComponent, ComponentStartup>(OnStartup);
|
||||||
|
SubscribeLocalEvent<PointManagerComponent, ComponentGetState>(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the specified point value to a player.
|
||||||
|
/// </summary>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the amount of points for a player
|
||||||
|
/// </summary>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of points for a given player
|
||||||
|
/// </summary>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,6 +26,9 @@ public sealed class JobSystem : EntitySystem
|
|||||||
|
|
||||||
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, ref MindRoleAddedEvent args)
|
private void MindOnDoGreeting(EntityUid mindId, MindComponent component, ref MindRoleAddedEvent args)
|
||||||
{
|
{
|
||||||
|
if (args.Silent)
|
||||||
|
return;
|
||||||
|
|
||||||
if (!_mind.TryGetSession(mindId, out var session))
|
if (!_mind.TryGetSession(mindId, out var session))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,4 @@
|
|||||||
/// <see cref="RoleAddedEvent"/> for the one raised on player entities.
|
/// <see cref="RoleAddedEvent"/> for the one raised on player entities.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ByRefEvent]
|
[ByRefEvent]
|
||||||
public readonly record struct MindRoleAddedEvent;
|
public readonly record struct MindRoleAddedEvent(bool Silent);
|
||||||
|
|||||||
@@ -9,4 +9,4 @@ namespace Content.Server.Roles;
|
|||||||
/// <param name="MindId">The mind id associated with the player.</param>
|
/// <param name="MindId">The mind id associated with the player.</param>
|
||||||
/// <param name="Mind">The mind component associated with the mind id.</param>
|
/// <param name="Mind">The mind component associated with the mind id.</param>
|
||||||
/// <param name="Antagonist">Whether or not the role makes the player an antagonist.</param>
|
/// <param name="Antagonist">Whether or not the role makes the player an antagonist.</param>
|
||||||
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);
|
||||||
|
|||||||
@@ -66,11 +66,12 @@ public sealed class RoleSystem : EntitySystem
|
|||||||
/// <param name="mindId">The mind to add the role to.</param>
|
/// <param name="mindId">The mind to add the role to.</param>
|
||||||
/// <param name="component">The role instance to add.</param>
|
/// <param name="component">The role instance to add.</param>
|
||||||
/// <typeparam name="T">The role type to add.</typeparam>
|
/// <typeparam name="T">The role type to add.</typeparam>
|
||||||
|
/// <param name="silent">Whether or not the role should be added silently</param>
|
||||||
/// <returns>The instance of the role.</returns>
|
/// <returns>The instance of the role.</returns>
|
||||||
/// <exception cref="ArgumentException">
|
/// <exception cref="ArgumentException">
|
||||||
/// Thrown if we already have a role with this type.
|
/// Thrown if we already have a role with this type.
|
||||||
/// </exception>
|
/// </exception>
|
||||||
public void MindAddRole<T>(EntityUid mindId, T component, MindComponent? mind = null) where T : Component, new()
|
public void MindAddRole<T>(EntityUid mindId, T component, MindComponent? mind = null, bool silent = false) where T : Component, new()
|
||||||
{
|
{
|
||||||
if (!Resolve(mindId, ref mind))
|
if (!Resolve(mindId, ref mind))
|
||||||
return;
|
return;
|
||||||
@@ -86,7 +87,7 @@ public sealed class RoleSystem : EntitySystem
|
|||||||
var mindEv = new MindRoleAddedEvent();
|
var mindEv = new MindRoleAddedEvent();
|
||||||
RaiseLocalEvent(mindId, ref mindEv);
|
RaiseLocalEvent(mindId, ref mindEv);
|
||||||
|
|
||||||
var message = new RoleAddedEvent(mindId, mind, antagonist);
|
var message = new RoleAddedEvent(mindId, mind, antagonist, silent);
|
||||||
if (mind.OwnedEntity != null)
|
if (mind.OwnedEntity != null)
|
||||||
{
|
{
|
||||||
RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
|
RaiseLocalEvent(mind.OwnedEntity.Value, message, true);
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ namespace Content.Server.RoundEnd
|
|||||||
RaiseLocalEvent(RoundEndSystemChangedEvent.Default);
|
RaiseLocalEvent(RoundEndSystemChangedEvent.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndRound()
|
public void EndRound(TimeSpan? countdownTime = null)
|
||||||
{
|
{
|
||||||
if (_gameTicker.RunLevel != GameRunLevel.InRound) return;
|
if (_gameTicker.RunLevel != GameRunLevel.InRound) return;
|
||||||
LastCountdownStart = null;
|
LastCountdownStart = null;
|
||||||
@@ -211,17 +211,17 @@ namespace Content.Server.RoundEnd
|
|||||||
_countdownTokenSource?.Cancel();
|
_countdownTokenSource?.Cancel();
|
||||||
_countdownTokenSource = new();
|
_countdownTokenSource = new();
|
||||||
|
|
||||||
var countdownTime = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.RoundRestartTime));
|
countdownTime ??= TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.RoundRestartTime));
|
||||||
int time;
|
int time;
|
||||||
string unitsLocString;
|
string unitsLocString;
|
||||||
if (countdownTime.TotalSeconds < 60)
|
if (countdownTime.Value.TotalSeconds < 60)
|
||||||
{
|
{
|
||||||
time = countdownTime.Seconds;
|
time = countdownTime.Value.Seconds;
|
||||||
unitsLocString = "eta-units-seconds";
|
unitsLocString = "eta-units-seconds";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
time = countdownTime.Minutes;
|
time = countdownTime.Value.Minutes;
|
||||||
unitsLocString = "eta-units-minutes";
|
unitsLocString = "eta-units-minutes";
|
||||||
}
|
}
|
||||||
_chatManager.DispatchServerAnnouncement(
|
_chatManager.DispatchServerAnnouncement(
|
||||||
@@ -229,7 +229,7 @@ namespace Content.Server.RoundEnd
|
|||||||
"round-end-system-round-restart-eta-announcement",
|
"round-end-system-round-restart-eta-announcement",
|
||||||
("time", time),
|
("time", time),
|
||||||
("units", Loc.GetString(unitsLocString))));
|
("units", Loc.GetString(unitsLocString))));
|
||||||
Timer.Spawn(countdownTime, AfterEndRoundRestart, _countdownTokenSource.Token);
|
Timer.Spawn(countdownTime.Value, AfterEndRoundRestart, _countdownTokenSource.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AfterEndRoundRestart()
|
private void AfterEndRoundRestart()
|
||||||
|
|||||||
@@ -123,12 +123,6 @@ namespace Content.Shared.Damage
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (damage == null)
|
|
||||||
{
|
|
||||||
Log.Error("Null DamageSpecifier. Probably because a required yaml field was not given.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (damage.Empty)
|
if (damage.Empty)
|
||||||
{
|
{
|
||||||
return damage;
|
return damage;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ public partial class MobStateSystem
|
|||||||
|
|
||||||
var ev = new UpdateMobStateEvent {Target = entity, Component = component, Origin = origin};
|
var ev = new UpdateMobStateEvent {Target = entity, Component = component, Origin = origin};
|
||||||
RaiseLocalEvent(entity, ref ev);
|
RaiseLocalEvent(entity, ref ev);
|
||||||
ChangeState(entity, component, ev.State);
|
ChangeState(entity, component, ev.State, origin: origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -297,14 +297,14 @@ public sealed class MobThresholdSystem : EntitySystem
|
|||||||
#region Private Implementation
|
#region Private Implementation
|
||||||
|
|
||||||
private void CheckThresholds(EntityUid target, MobStateComponent mobStateComponent,
|
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())
|
foreach (var (threshold, mobState) in thresholdsComponent.Thresholds.Reverse())
|
||||||
{
|
{
|
||||||
if (damageableComponent.TotalDamage < threshold)
|
if (damageableComponent.TotalDamage < threshold)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
TriggerThreshold(target, mobState, mobStateComponent, thresholdsComponent);
|
TriggerThreshold(target, mobState, mobStateComponent, thresholdsComponent, origin);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,7 +313,8 @@ public sealed class MobThresholdSystem : EntitySystem
|
|||||||
EntityUid target,
|
EntityUid target,
|
||||||
MobState newState,
|
MobState newState,
|
||||||
MobStateComponent? mobState = null,
|
MobStateComponent? mobState = null,
|
||||||
MobThresholdsComponent? thresholds = null)
|
MobThresholdsComponent? thresholds = null,
|
||||||
|
EntityUid? origin = null)
|
||||||
{
|
{
|
||||||
if (!Resolve(target, ref mobState, ref thresholds) ||
|
if (!Resolve(target, ref mobState, ref thresholds) ||
|
||||||
mobState.CurrentState == newState)
|
mobState.CurrentState == newState)
|
||||||
@@ -327,7 +328,7 @@ public sealed class MobThresholdSystem : EntitySystem
|
|||||||
Dirty(target, thresholds);
|
Dirty(target, thresholds);
|
||||||
}
|
}
|
||||||
|
|
||||||
_mobStateSystem.UpdateMobState(target, mobState);
|
_mobStateSystem.UpdateMobState(target, mobState, origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateAlerts(EntityUid target, MobState currentMobState, MobThresholdsComponent? threshold = null,
|
private void UpdateAlerts(EntityUid target, MobState currentMobState, MobThresholdsComponent? threshold = null,
|
||||||
@@ -374,7 +375,7 @@ public sealed class MobThresholdSystem : EntitySystem
|
|||||||
{
|
{
|
||||||
if (!TryComp<MobStateComponent>(target, out var mobState))
|
if (!TryComp<MobStateComponent>(target, out var mobState))
|
||||||
return;
|
return;
|
||||||
CheckThresholds(target, mobState, thresholds, args.Damageable);
|
CheckThresholds(target, mobState, thresholds, args.Damageable, args.Origin);
|
||||||
var ev = new MobThresholdChecked(target, mobState, thresholds, args.Damageable);
|
var ev = new MobThresholdChecked(target, mobState, thresholds, args.Damageable);
|
||||||
RaiseLocalEvent(target, ref ev, true);
|
RaiseLocalEvent(target, ref ev, true);
|
||||||
UpdateAlerts(target, mobState.CurrentState, thresholds, args.Damageable);
|
UpdateAlerts(target, mobState.CurrentState, thresholds, args.Damageable);
|
||||||
|
|||||||
40
Content.Shared/Points/PointManagerComponent.cs
Normal file
40
Content.Shared/Points/PointManagerComponent.cs
Normal file
@@ -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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is a component that generically stores points for all players.
|
||||||
|
/// </summary>
|
||||||
|
[RegisterComponent, NetworkedComponent, Access(typeof(SharedPointSystem))]
|
||||||
|
public sealed partial class PointManagerComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A dictionary of a player's netuserID to the amount of points they have.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("points")]
|
||||||
|
public Dictionary<NetUserId, FixedPoint2> Points = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A text-only version of the scoreboard used by the client.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("scoreboard")]
|
||||||
|
public FormattedMessage Scoreboard = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable, NetSerializable]
|
||||||
|
public sealed class PointManagerComponentState : ComponentState
|
||||||
|
{
|
||||||
|
public Dictionary<NetUserId, FixedPoint2> Points;
|
||||||
|
|
||||||
|
public FormattedMessage Scoreboard;
|
||||||
|
|
||||||
|
public PointManagerComponentState(Dictionary<NetUserId, FixedPoint2> points, FormattedMessage scoreboard)
|
||||||
|
{
|
||||||
|
Points = points;
|
||||||
|
Scoreboard = scoreboard;
|
||||||
|
}
|
||||||
|
}
|
||||||
86
Content.Shared/Points/SharedPointSystem.cs
Normal file
86
Content.Shared/Points/SharedPointSystem.cs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Robust.Shared.Network;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Shared.Points;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This handles modifying point counts for <see cref="PointManagerComponent"/>
|
||||||
|
/// </summary>
|
||||||
|
public abstract class SharedPointSystem : EntitySystem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the specified point value to a player.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the amount of points for a player
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of points for a given player
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that a player is being tracked by the PointManager, giving them a default score of 0.
|
||||||
|
/// </summary>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns a formatted message containing a ranking of all the currently online players and their scores.
|
||||||
|
/// </summary>
|
||||||
|
public virtual FormattedMessage GetScoreboard(EntityUid uid, PointManagerComponent? component = null)
|
||||||
|
{
|
||||||
|
return new FormattedMessage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Event raised on the point manager entity and broadcasted whenever a player's points change.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="Player"></param>
|
||||||
|
/// <param name="Points"></param>
|
||||||
|
[ByRefEvent]
|
||||||
|
public readonly record struct PlayerPointChangedEvent(NetUserId Player, FixedPoint2 Points);
|
||||||
@@ -1,2 +1,79 @@
|
|||||||
death-match-title = DeathMatch
|
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!
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
# General
|
# General
|
||||||
rule-restarting-in-seconds = Restarting in {$seconds} seconds.
|
rule-restarting-in-seconds = Restarting in {$seconds} seconds.
|
||||||
rule-time-has-run-out = Time has run out!
|
rule-time-has-run-out = Time has run out!
|
||||||
|
|
||||||
|
# Respawning
|
||||||
|
rule-respawn-in-seconds = Respawning in {$second} seconds...
|
||||||
|
|||||||
6
Resources/Locale/en-US/points/points.ftl
Normal file
6
Resources/Locale/en-US/points/points.ftl
Normal file
@@ -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]
|
||||||
1634
Resources/Maps/Nonstations/meteor-arena.yml
Normal file
1634
Resources/Maps/Nonstations/meteor-arena.yml
Normal file
File diff suppressed because it is too large
Load Diff
@@ -298,6 +298,32 @@
|
|||||||
stackType: AloeCream
|
stackType: AloeCream
|
||||||
count: 10
|
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
|
# Pills
|
||||||
- type: entity
|
- type: entity
|
||||||
name: dexalin pill (10u)
|
name: dexalin pill (10u)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -35,3 +35,14 @@
|
|||||||
noSpawn: true
|
noSpawn: true
|
||||||
components:
|
components:
|
||||||
- type: Transform
|
- type: Transform
|
||||||
|
|
||||||
|
- type: entity
|
||||||
|
id: StandardStationArena
|
||||||
|
parent:
|
||||||
|
- BaseStation
|
||||||
|
- BaseStationJobsSpawning
|
||||||
|
- BaseStationRecords
|
||||||
|
- BaseStationNanotrasen
|
||||||
|
noSpawn: true
|
||||||
|
components:
|
||||||
|
- type: Transform
|
||||||
|
|||||||
@@ -6,11 +6,34 @@
|
|||||||
- type: GameRule
|
- type: GameRule
|
||||||
|
|
||||||
- type: entity
|
- type: entity
|
||||||
id: DeathMatch
|
id: DeathMatch31
|
||||||
parent: BaseGameRule
|
parent: BaseGameRule
|
||||||
noSpawn: true
|
noSpawn: true
|
||||||
components:
|
components:
|
||||||
- type: DeathMatchRule
|
- 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
|
- type: entity
|
||||||
id: InactivityTimeRestart
|
id: InactivityTimeRestart
|
||||||
|
|||||||
4
Resources/Prototypes/Maps/Pools/deathmatch.yml
Normal file
4
Resources/Prototypes/Maps/Pools/deathmatch.yml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
- type: gameMapPool
|
||||||
|
id: DeathMatchMapPool
|
||||||
|
maps:
|
||||||
|
- MeteorArena
|
||||||
16
Resources/Prototypes/Maps/arenas.yml
Normal file
16
Resources/Prototypes/Maps/arenas.yml
Normal file
@@ -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 ]
|
||||||
@@ -277,6 +277,19 @@
|
|||||||
jumpsuit: ClothingUniformJumpsuitJacketMonkey
|
jumpsuit: ClothingUniformJumpsuitJacketMonkey
|
||||||
id: BartenderIDCard
|
id: BartenderIDCard
|
||||||
|
|
||||||
|
# DeathMatch Gear
|
||||||
|
|
||||||
|
- type: startingGear
|
||||||
|
id: DeathMatchGear
|
||||||
|
equipment:
|
||||||
|
jumpsuit: ClothingUniformJumpsuitColorWhite
|
||||||
|
shoes: ClothingShoesBootsJack
|
||||||
|
ears: ClothingHeadsetGrey
|
||||||
|
gloves: ClothingHandsGlovesFingerless
|
||||||
|
innerclothingskirt: ClothingUniformJumpskirtColorWhite
|
||||||
|
inhand:
|
||||||
|
left hand: WeaponMeleeToolboxRobust
|
||||||
|
|
||||||
#Brigmedic
|
#Brigmedic
|
||||||
|
|
||||||
- type: startingGear
|
- type: startingGear
|
||||||
|
|||||||
@@ -59,8 +59,11 @@
|
|||||||
- dm
|
- dm
|
||||||
name: death-match-title
|
name: death-match-title
|
||||||
description: death-match-description
|
description: death-match-description
|
||||||
|
maxPlayers: 15
|
||||||
|
showInVote: true
|
||||||
|
supportedMaps: DeathMatchMapPool
|
||||||
rules:
|
rules:
|
||||||
- DeathMatch
|
- DeathMatch31
|
||||||
|
|
||||||
- type: gamePreset
|
- type: gamePreset
|
||||||
id: Nukeops
|
id: Nukeops
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 683 B |
@@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user