1v1 me first to 31 no powerups [Deathmatch Gamemode] (#19467)
Co-authored-by: Kara <lunarautomaton6@gmail.com>
This commit is contained in:
@@ -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>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
[RegisterComponent, Access(typeof(DeathMatchRuleSystem))]
|
||||
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>
|
||||
/// How long until the round restarts
|
||||
/// </summary>
|
||||
[DataField("restartDelay"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float RestartDelay = 10f;
|
||||
public TimeSpan RestartDelay = TimeSpan.FromSeconds(10f);
|
||||
|
||||
/// <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>
|
||||
[DataField("deadCheckDelay"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float DeadCheckDelay = 5f;
|
||||
[DataField("victor")]
|
||||
public NetUserId? Victor;
|
||||
|
||||
/// <summary>
|
||||
/// A timer for checking after a death
|
||||
/// An entity spawned after a player is killed.
|
||||
/// </summary>
|
||||
[DataField("deadCheckTimer"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float? DeadCheckTimer;
|
||||
[DataField("rewardSpawns")]
|
||||
public List<EntitySpawnEntry> RewardSpawns = new();
|
||||
|
||||
/// <summary>
|
||||
/// A timer for the restart.
|
||||
/// The gear all players spawn with.
|
||||
/// </summary>
|
||||
[DataField("restartTimer"), ViewVariables(VVAccess.ReadWrite)]
|
||||
public float? RestartTimer;
|
||||
[DataField("gear", customTypeSerializer: typeof(PrototypeIdSerializer<StartingGearPrototype>)), ViewVariables(VVAccess.ReadWrite)]
|
||||
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.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;
|
||||
/// </summary>
|
||||
public sealed class DeathMatchRuleSystem : GameRuleSystem<DeathMatchRuleComponent>
|
||||
{
|
||||
[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<DamageChangedEvent>(OnHealthChanged);
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
SubscribeLocalEvent<PlayerBeforeSpawnEvent>(OnBeforeSpawn);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnSpawnComplete);
|
||||
SubscribeLocalEvent<KillReportedEvent>(OnKillReported);
|
||||
SubscribeLocalEvent<DeathMatchRuleComponent, PlayerPointChangedEvent>(OnPointChanged);
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(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<DeathMatchRuleComponent, RespawnTrackerComponent, PointManagerComponent, GameRuleComponent>();
|
||||
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<DeathMatchRuleComponent, GameRuleComponent>();
|
||||
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<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);
|
||||
|
||||
// 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<KillTrackerComponent>(ev.Mob);
|
||||
var query = EntityQueryEnumerator<DeathMatchRuleComponent, RespawnTrackerComponent, GameRuleComponent>();
|
||||
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<DeathMatchRuleComponent, PointManagerComponent, GameRuleComponent>();
|
||||
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<DeathMatchRuleComponent, PointManagerComponent, GameRuleComponent>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user