Revert "Refactor Game Modes" oh god oh fuck go back it was too good to be true (#5855)
This commit is contained in:
@@ -1,127 +0,0 @@
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
/// <summary>
|
||||
/// Simple GameRule that will do a free-for-all death match.
|
||||
/// Kill everybody else to win.
|
||||
/// </summary>
|
||||
public sealed class DeathMatchRuleSystem : GameRuleSystem
|
||||
{
|
||||
public override string Prototype => "DeathMatch";
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private const float RestartDelay = 10f;
|
||||
private const float DeadCheckDelay = 5f;
|
||||
|
||||
private float? _deadCheckTimer = null;
|
||||
private float? _restartTimer = null;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<DamageChangedEvent>(OnHealthChanged);
|
||||
}
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
||||
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
_deadCheckTimer = null;
|
||||
_restartTimer = null;
|
||||
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnHealthChanged(DamageChangedEvent _)
|
||||
{
|
||||
RunDelayedCheck();
|
||||
}
|
||||
|
||||
private void OnPlayerStatusChanged(object? _, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Disconnected)
|
||||
{
|
||||
RunDelayedCheck();
|
||||
}
|
||||
}
|
||||
|
||||
private void RunDelayedCheck()
|
||||
{
|
||||
if (!Enabled || _deadCheckTimer != null)
|
||||
return;
|
||||
|
||||
_deadCheckTimer = DeadCheckDelay;
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
// 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 (_restartTimer != null)
|
||||
{
|
||||
_restartTimer -= frameTime;
|
||||
|
||||
if (_restartTimer > 0f)
|
||||
return;
|
||||
|
||||
GameTicker.EndRound();
|
||||
GameTicker.RestartRound();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin) || _deadCheckTimer == null)
|
||||
return;
|
||||
|
||||
_deadCheckTimer -= frameTime;
|
||||
|
||||
if (_deadCheckTimer > 0)
|
||||
return;
|
||||
|
||||
_deadCheckTimer = null;
|
||||
|
||||
IPlayerSession? winner = null;
|
||||
foreach (var playerSession in _playerManager.ServerSessions)
|
||||
{
|
||||
if (playerSession.AttachedEntity is not {Valid: true} playerEntity
|
||||
|| !TryComp(playerEntity, out MobStateComponent? state))
|
||||
continue;
|
||||
|
||||
if (!state.IsAlive())
|
||||
continue;
|
||||
|
||||
// Found a second person alive, nothing decided yet!
|
||||
if (winner != null)
|
||||
return;
|
||||
|
||||
winner = playerSession;
|
||||
}
|
||||
|
||||
_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", RestartDelay)));
|
||||
_restartTimer = RestartDelay;
|
||||
}
|
||||
}
|
||||
19
Content.Server/GameTicking/Rules/GameRule.cs
Normal file
19
Content.Server/GameTicking/Rules/GameRule.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
[PublicAPI]
|
||||
public abstract class GameRule : IEntityEventSubscriber
|
||||
{
|
||||
public virtual void Added()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void Removed()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
[Prototype("gameRule")]
|
||||
public class GameRulePrototype : IPrototype
|
||||
{
|
||||
[DataField("id", required:true)]
|
||||
public string ID { get; } = default!;
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
[PublicAPI]
|
||||
public abstract class GameRuleSystem : EntitySystem
|
||||
{
|
||||
[Dependency] protected GameTicker GameTicker = default!;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this GameRule is currently enabled or not.
|
||||
/// Be sure to check this before doing anything rule-specific.
|
||||
/// </summary>
|
||||
public bool Enabled { get; protected set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// When the GameRule prototype with this ID is added, this system will be enabled.
|
||||
/// When it gets removed, this system will be disabled.
|
||||
/// </summary>
|
||||
public abstract string Prototype { get; }
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GameRuleAddedEvent>(OnGameRuleAdded);
|
||||
SubscribeLocalEvent<GameRuleRemovedEvent>(OnGameRuleRemoved);
|
||||
|
||||
}
|
||||
|
||||
private void OnGameRuleAdded(GameRuleAddedEvent ev)
|
||||
{
|
||||
if (ev.Rule.ID != Prototype)
|
||||
return;
|
||||
|
||||
Enabled = true;
|
||||
Added();
|
||||
}
|
||||
|
||||
private void OnGameRuleRemoved(GameRuleRemovedEvent ev)
|
||||
{
|
||||
if (ev.Rule.ID != Prototype)
|
||||
return;
|
||||
|
||||
Enabled = false;
|
||||
Removed();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when the game rule has been added and this system has been enabled.
|
||||
/// </summary>
|
||||
public abstract void Added();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the game rule has been removed and this system has been disabled.
|
||||
/// </summary>
|
||||
public abstract void Removed();
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public class InactivityTimeRestartRuleSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
|
||||
public override string Prototype => "InactivityTimeRestart";
|
||||
|
||||
private CancellationTokenSource _timerCancel = new();
|
||||
|
||||
public TimeSpan InactivityMaxTime { get; set; } = TimeSpan.FromMinutes(10);
|
||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
||||
}
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged -= PlayerStatusChanged;
|
||||
|
||||
StopTimer();
|
||||
}
|
||||
|
||||
public void RestartTimer()
|
||||
{
|
||||
_timerCancel.Cancel();
|
||||
_timerCancel = new CancellationTokenSource();
|
||||
Timer.Spawn(InactivityMaxTime, TimerFired, _timerCancel.Token);
|
||||
}
|
||||
|
||||
public void StopTimer()
|
||||
{
|
||||
_timerCancel.Cancel();
|
||||
}
|
||||
|
||||
private void TimerFired()
|
||||
{
|
||||
GameTicker.EndRound(Loc.GetString("rule-time-has-run-out"));
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds",(int) RoundEndDelay.TotalSeconds)));
|
||||
|
||||
Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
|
||||
}
|
||||
|
||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
switch (args.New)
|
||||
{
|
||||
case GameRunLevel.InRound:
|
||||
RestartTimer();
|
||||
break;
|
||||
case GameRunLevel.PreRoundLobby:
|
||||
case GameRunLevel.PostRound:
|
||||
StopTimer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (GameTicker.RunLevel != GameRunLevel.InRound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_playerManager.PlayerCount == 0)
|
||||
{
|
||||
RestartTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
StopTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public sealed class MaxTimeRestartRuleSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
|
||||
public override string Prototype => "MaxTimeRestart";
|
||||
|
||||
private CancellationTokenSource _timerCancel = new();
|
||||
|
||||
public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromMinutes(5);
|
||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<GameRunLevelChangedEvent>(RunLevelChanged);
|
||||
}
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
if(GameTicker.RunLevel == GameRunLevel.InRound)
|
||||
RestartTimer();
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
RoundMaxTime = TimeSpan.FromMinutes(5);
|
||||
RoundEndDelay = TimeSpan.FromMinutes(10);
|
||||
|
||||
StopTimer();
|
||||
}
|
||||
|
||||
public void RestartTimer()
|
||||
{
|
||||
_timerCancel.Cancel();
|
||||
_timerCancel = new CancellationTokenSource();
|
||||
Timer.Spawn(RoundMaxTime, TimerFired, _timerCancel.Token);
|
||||
}
|
||||
|
||||
public void StopTimer()
|
||||
{
|
||||
_timerCancel.Cancel();
|
||||
}
|
||||
|
||||
private void TimerFired()
|
||||
{
|
||||
GameTicker.EndRound(Loc.GetString("rule-time-has-run-out"));
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",("seconds", (int) RoundEndDelay.TotalSeconds)));
|
||||
|
||||
Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
|
||||
}
|
||||
|
||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
switch (args.New)
|
||||
{
|
||||
case GameRunLevel.InRound:
|
||||
RestartTimer();
|
||||
break;
|
||||
case GameRunLevel.PreRoundLobby:
|
||||
case GameRunLevel.PostRound:
|
||||
StopTimer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
110
Content.Server/GameTicking/Rules/RuleDeathMatch.cs
Normal file
110
Content.Server/GameTicking/Rules/RuleDeathMatch.cs
Normal file
@@ -0,0 +1,110 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple GameRule that will do a free-for-all death match.
|
||||
/// Kill everybody else to win.
|
||||
/// </summary>
|
||||
public sealed class RuleDeathMatch : GameRule, IEntityEventSubscriber
|
||||
{
|
||||
private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(5);
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private CancellationTokenSource? _checkTimerCancel;
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-death-match-added-announcement"));
|
||||
|
||||
_entityManager.EventBus.SubscribeEvent<DamageChangedEvent>(EventSource.Local, this, OnHealthChanged);
|
||||
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
base.Removed();
|
||||
|
||||
_entityManager.EventBus.UnsubscribeEvent<DamageChangedEvent>(EventSource.Local, this);
|
||||
_playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnHealthChanged(DamageChangedEvent _)
|
||||
{
|
||||
_runDelayedCheck();
|
||||
}
|
||||
|
||||
private void _checkForWinner()
|
||||
{
|
||||
_checkTimerCancel = null;
|
||||
|
||||
if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin))
|
||||
return;
|
||||
|
||||
IPlayerSession? winner = null;
|
||||
foreach (var playerSession in _playerManager.ServerSessions)
|
||||
{
|
||||
if (playerSession.AttachedEntity is not {Valid: true} playerEntity
|
||||
|| !_entityManager.TryGetComponent(playerEntity, out MobStateComponent? state))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!state.IsAlive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (winner != null)
|
||||
{
|
||||
// Found a second person alive, nothing decided yet!
|
||||
return;
|
||||
}
|
||||
|
||||
winner = playerSession;
|
||||
}
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(winner == null
|
||||
? Loc.GetString("rule-death-match-check-winner-stalemate")
|
||||
: Loc.GetString("rule-death-match-check-winner",("winner", winner)));
|
||||
|
||||
var restartDelay = 10;
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds", restartDelay)));
|
||||
|
||||
Timer.Spawn(TimeSpan.FromSeconds(restartDelay), () => EntitySystem.Get<GameTicker>().RestartRound());
|
||||
}
|
||||
|
||||
private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.Disconnected)
|
||||
{
|
||||
_runDelayedCheck();
|
||||
}
|
||||
}
|
||||
|
||||
private void _runDelayedCheck()
|
||||
{
|
||||
_checkTimerCancel?.Cancel();
|
||||
_checkTimerCancel = new CancellationTokenSource();
|
||||
|
||||
Timer.Spawn(DeadCheckDelay, _checkForWinner, _checkTimerCancel.Token);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
public class RuleInactivityTimeRestart : GameRule
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private CancellationTokenSource _timerCancel = new();
|
||||
|
||||
public TimeSpan InactivityMaxTime { get; set; } = TimeSpan.FromMinutes(10);
|
||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
base.Added();
|
||||
|
||||
_entityManager.EventBus.SubscribeEvent<GameRunLevelChangedEvent>(EventSource.Local, this, RunLevelChanged);
|
||||
_playerManager.PlayerStatusChanged += PlayerStatusChanged;
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
base.Removed();
|
||||
|
||||
_entityManager.EventBus.UnsubscribeEvents(this);
|
||||
_playerManager.PlayerStatusChanged -= PlayerStatusChanged;
|
||||
|
||||
StopTimer();
|
||||
}
|
||||
|
||||
public void RestartTimer()
|
||||
{
|
||||
_timerCancel.Cancel();
|
||||
_timerCancel = new CancellationTokenSource();
|
||||
Timer.Spawn(InactivityMaxTime, TimerFired, _timerCancel.Token);
|
||||
}
|
||||
|
||||
public void StopTimer()
|
||||
{
|
||||
_timerCancel.Cancel();
|
||||
}
|
||||
|
||||
private void TimerFired()
|
||||
{
|
||||
var gameticker = EntitySystem.Get<GameTicker>();
|
||||
gameticker.EndRound(Loc.GetString("rule-time-has-run-out"));
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds",(int) RoundEndDelay.TotalSeconds)));
|
||||
|
||||
Timer.Spawn(RoundEndDelay, () => gameticker.RestartRound());
|
||||
}
|
||||
|
||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||
{
|
||||
switch (args.New)
|
||||
{
|
||||
case GameRunLevel.InRound:
|
||||
RestartTimer();
|
||||
break;
|
||||
case GameRunLevel.PreRoundLobby:
|
||||
case GameRunLevel.PostRound:
|
||||
StopTimer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (EntitySystem.Get<GameTicker>().RunLevel != GameRunLevel.InRound)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_playerManager.PlayerCount == 0)
|
||||
{
|
||||
RestartTimer();
|
||||
}
|
||||
else
|
||||
{
|
||||
StopTimer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
71
Content.Server/GameTicking/Rules/RuleMaxTimeRestart.cs
Normal file
71
Content.Server/GameTicking/Rules/RuleMaxTimeRestart.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
public sealed class RuleMaxTimeRestart : GameRule
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
private CancellationTokenSource _timerCancel = new();
|
||||
|
||||
public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromMinutes(5);
|
||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
base.Added();
|
||||
|
||||
_entityManager.EventBus.SubscribeEvent<GameRunLevelChangedEvent>(EventSource.Local, this, RunLevelChanged);
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
base.Removed();
|
||||
|
||||
_entityManager.EventBus.UnsubscribeEvents(this);
|
||||
StopTimer();
|
||||
}
|
||||
|
||||
public void RestartTimer()
|
||||
{
|
||||
_timerCancel.Cancel();
|
||||
_timerCancel = new CancellationTokenSource();
|
||||
Timer.Spawn(RoundMaxTime, TimerFired, _timerCancel.Token);
|
||||
}
|
||||
|
||||
public void StopTimer()
|
||||
{
|
||||
_timerCancel.Cancel();
|
||||
}
|
||||
|
||||
private void TimerFired()
|
||||
{
|
||||
EntitySystem.Get<GameTicker>().EndRound(Loc.GetString("rule-time-has-run-out"));
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds",("seconds", (int) RoundEndDelay.TotalSeconds)));
|
||||
|
||||
Timer.Spawn(RoundEndDelay, () => EntitySystem.Get<GameTicker>().RestartRound());
|
||||
}
|
||||
|
||||
private void RunLevelChanged(GameRunLevelChangedEvent args)
|
||||
{
|
||||
switch (args.New)
|
||||
{
|
||||
case GameRunLevel.InRound:
|
||||
RestartTimer();
|
||||
break;
|
||||
case GameRunLevel.PreRoundLobby:
|
||||
case GameRunLevel.PostRound:
|
||||
StopTimer();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
168
Content.Server/GameTicking/Rules/RuleSuspicion.cs
Normal file
168
Content.Server/GameTicking/Rules/RuleSuspicion.cs
Normal file
@@ -0,0 +1,168 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Doors;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Suspicion;
|
||||
using Content.Server.Suspicion.EntitySystems;
|
||||
using Content.Server.Suspicion.Roles;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
/// <summary>
|
||||
/// Simple GameRule that will do a TTT-like gamemode with traitors.
|
||||
/// </summary>
|
||||
public sealed class RuleSuspicion : GameRule
|
||||
{
|
||||
private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(1);
|
||||
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
|
||||
[DataField("addedSound")] private SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
|
||||
|
||||
private readonly CancellationTokenSource _checkTimerCancel = new();
|
||||
private TimeSpan _endTime;
|
||||
|
||||
public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromSeconds(CCVars.SuspicionMaxTimeSeconds.DefaultValue);
|
||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
RoundMaxTime = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.SuspicionMaxTimeSeconds));
|
||||
|
||||
_endTime = _timing.CurTime + RoundMaxTime;
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-added-announcement"));
|
||||
|
||||
var filter = Filter.Empty()
|
||||
.AddWhere(session => ((IPlayerSession) session).ContentData()?.Mind?.HasRole<SuspicionTraitorRole>() ?? false);
|
||||
|
||||
SoundSystem.Play(filter, _addedSound.GetSound(), AudioParams.Default);
|
||||
EntitySystem.Get<SuspicionEndTimerSystem>().EndTime = _endTime;
|
||||
|
||||
EntitySystem.Get<DoorSystem>().AccessType = DoorSystem.AccessTypes.AllowAllNoExternal;
|
||||
|
||||
Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
base.Removed();
|
||||
|
||||
EntitySystem.Get<DoorSystem>().AccessType = DoorSystem.AccessTypes.Id;
|
||||
EntitySystem.Get<SuspicionEndTimerSystem>().EndTime = null;
|
||||
|
||||
_checkTimerCancel.Cancel();
|
||||
}
|
||||
|
||||
private void Timeout()
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-traitor-time-has-run-out"));
|
||||
|
||||
EndRound(Victory.Innocents);
|
||||
}
|
||||
|
||||
private void CheckWinConditions()
|
||||
{
|
||||
if (!_cfg.GetCVar(CCVars.GameLobbyEnableWin))
|
||||
return;
|
||||
|
||||
var traitorsAlive = 0;
|
||||
var innocentsAlive = 0;
|
||||
|
||||
foreach (var playerSession in _playerManager.ServerSessions)
|
||||
{
|
||||
if (playerSession.AttachedEntity is not {Valid: true} playerEntity
|
||||
|| !_entities.TryGetComponent(playerEntity, out MobStateComponent? mobState)
|
||||
|| !_entities.HasComponent<SuspicionRoleComponent>(playerEntity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mobState.IsAlive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var mind = playerSession.ContentData()?.Mind;
|
||||
|
||||
if (mind != null && mind.HasRole<SuspicionTraitorRole>())
|
||||
traitorsAlive++;
|
||||
else
|
||||
innocentsAlive++;
|
||||
}
|
||||
|
||||
if (innocentsAlive + traitorsAlive == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-stalemate"));
|
||||
EndRound(Victory.Stalemate);
|
||||
}
|
||||
|
||||
else if (traitorsAlive == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-station-win"));
|
||||
EndRound(Victory.Innocents);
|
||||
}
|
||||
else if (innocentsAlive == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-traitor-win"));
|
||||
EndRound(Victory.Traitors);
|
||||
}
|
||||
else if (_timing.CurTime > _endTime)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-traitor-time-has-run-out"));
|
||||
EndRound(Victory.Innocents);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Victory
|
||||
{
|
||||
Stalemate,
|
||||
Innocents,
|
||||
Traitors
|
||||
}
|
||||
|
||||
private void EndRound(Victory victory)
|
||||
{
|
||||
string text;
|
||||
|
||||
switch (victory)
|
||||
{
|
||||
case Victory.Innocents:
|
||||
text = Loc.GetString("rule-suspicion-end-round-innocents-victory");
|
||||
break;
|
||||
case Victory.Traitors:
|
||||
text = Loc.GetString("rule-suspicion-end-round-trators-victory");
|
||||
break;
|
||||
default:
|
||||
text = Loc.GetString("rule-suspicion-end-round-nobody-victory");
|
||||
break;
|
||||
}
|
||||
|
||||
var gameTicker = EntitySystem.Get<GameTicker>();
|
||||
gameTicker.EndRound(text);
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds", (int) RoundEndDelay.TotalSeconds)));
|
||||
_checkTimerCancel.Cancel();
|
||||
|
||||
Timer.Spawn(RoundEndDelay, () => gameTicker.RestartRound());
|
||||
}
|
||||
}
|
||||
}
|
||||
30
Content.Server/GameTicking/Rules/RuleTraitor.cs
Normal file
30
Content.Server/GameTicking/Rules/RuleTraitor.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Traitor;
|
||||
using Content.Shared.Sound;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
public class RuleTraitor : GameRule
|
||||
{
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
|
||||
[DataField("addedSound")] private SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-traitor-added-announcement"));
|
||||
|
||||
var filter = Filter.Empty()
|
||||
.AddWhere(session => ((IPlayerSession)session).ContentData()?.Mind?.HasRole<TraitorRole>() ?? false);
|
||||
|
||||
SoundSystem.Play(filter, _addedSound.GetSound(), AudioParams.Default);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace Content.Server.GameTicking.Rules
|
||||
{
|
||||
public class RuleTraitorDeathMatch : GameRule
|
||||
{
|
||||
// This class only exists so that the game rule is available for the conditional spawner.
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
using Content.Server.Sandbox;
|
||||
using Robust.Shared.IoC;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public class SandboxRuleSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly ISandboxManager _sandbox = default!;
|
||||
|
||||
public override string Prototype => "Sandbox";
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
_sandbox.IsSandboxEnabled = true;
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
_sandbox.IsSandboxEnabled = false;
|
||||
}
|
||||
}
|
||||
@@ -1,413 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Doors;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Suspicion;
|
||||
using Content.Server.Suspicion.Roles;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.GameTicking;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.Roles;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Suspicion;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Server.GameObjects;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
/// <summary>
|
||||
/// Simple GameRule that will do a TTT-like gamemode with traitors.
|
||||
/// </summary>
|
||||
public sealed class SuspicionRuleSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IGameTiming _timing = default!;
|
||||
[Dependency] private readonly IEntityManager _entities = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly DoorSystem _doorSystem = default!;
|
||||
|
||||
public override string Prototype => "Suspicion";
|
||||
|
||||
private static readonly TimeSpan DeadCheckDelay = TimeSpan.FromSeconds(1);
|
||||
|
||||
private readonly HashSet<SuspicionRoleComponent> _traitors = new();
|
||||
|
||||
public IReadOnlyCollection<SuspicionRoleComponent> Traitors => _traitors;
|
||||
|
||||
[DataField("addedSound")] private SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
|
||||
|
||||
private CancellationTokenSource _checkTimerCancel = new();
|
||||
private TimeSpan? _endTime;
|
||||
|
||||
public TimeSpan? EndTime
|
||||
{
|
||||
get => _endTime;
|
||||
set
|
||||
{
|
||||
_endTime = value;
|
||||
SendUpdateToAll();
|
||||
}
|
||||
}
|
||||
|
||||
public TimeSpan RoundMaxTime { get; set; } = TimeSpan.FromSeconds(CCVars.SuspicionMaxTimeSeconds.DefaultValue);
|
||||
public TimeSpan RoundEndDelay { get; set; } = TimeSpan.FromSeconds(10);
|
||||
|
||||
private const string TraitorID = "SuspicionTraitor";
|
||||
private const string InnocentID = "SuspicionInnocent";
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayersAssigned);
|
||||
SubscribeLocalEvent<RoundStartAttemptEvent>(OnRoundStartAttempt);
|
||||
SubscribeLocalEvent<RefreshLateJoinAllowedEvent>(OnLateJoinRefresh);
|
||||
SubscribeLocalEvent<RoundRestartCleanupEvent>(Reset);
|
||||
|
||||
SubscribeLocalEvent<SuspicionRoleComponent, PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<SuspicionRoleComponent, PlayerDetachedEvent>(OnPlayerDetached);
|
||||
SubscribeLocalEvent<SuspicionRoleComponent, RoleAddedEvent>(OnRoleAdded);
|
||||
SubscribeLocalEvent<SuspicionRoleComponent, RoleRemovedEvent>(OnRoleRemoved);
|
||||
}
|
||||
|
||||
private void OnRoundStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var minPlayers = _cfg.GetCVar(CCVars.SuspicionMinPlayers);
|
||||
|
||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement($"Not enough players readied up for the game! There were {ev.Players.Length} players readied up out of {minPlayers} needed.");
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.Players.Length == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement("No players readied up! Can't start Suspicion.");
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayersAssigned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var minTraitors = _cfg.GetCVar(CCVars.SuspicionMinTraitors);
|
||||
var playersPerTraitor = _cfg.GetCVar(CCVars.SuspicionPlayersPerTraitor);
|
||||
var traitorStartingBalance = _cfg.GetCVar(CCVars.SuspicionStartingBalance);
|
||||
|
||||
var list = new List<IPlayerSession>(ev.Players);
|
||||
var prefList = new List<IPlayerSession>();
|
||||
|
||||
foreach (var player in list)
|
||||
{
|
||||
if (!ev.Profiles.ContainsKey(player.UserId) || player.AttachedEntity is not {} attached)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
prefList.Add(player);
|
||||
|
||||
attached.EnsureComponent<SuspicionRoleComponent>();
|
||||
}
|
||||
|
||||
var numTraitors = MathHelper.Clamp(ev.Players.Length / playersPerTraitor,
|
||||
minTraitors, ev.Players.Length);
|
||||
|
||||
var traitors = new List<SuspicionTraitorRole>();
|
||||
|
||||
for (var i = 0; i < numTraitors; i++)
|
||||
{
|
||||
IPlayerSession traitor;
|
||||
if(prefList.Count == 0)
|
||||
{
|
||||
if (list.Count == 0)
|
||||
{
|
||||
Logger.InfoS("preset", "Insufficient ready players to fill up with traitors, stopping the selection.");
|
||||
break;
|
||||
}
|
||||
traitor = _random.PickAndTake(list);
|
||||
Logger.InfoS("preset", "Insufficient preferred traitors, picking at random.");
|
||||
}
|
||||
else
|
||||
{
|
||||
traitor = _random.PickAndTake(prefList);
|
||||
list.Remove(traitor);
|
||||
Logger.InfoS("preset", "Selected a preferred traitor.");
|
||||
}
|
||||
var mind = traitor.Data.ContentData()?.Mind;
|
||||
var antagPrototype = _prototypeManager.Index<AntagPrototype>(TraitorID);
|
||||
|
||||
DebugTools.AssertNotNull(mind?.OwnedEntity);
|
||||
|
||||
var traitorRole = new SuspicionTraitorRole(mind!, antagPrototype);
|
||||
mind!.AddRole(traitorRole);
|
||||
traitors.Add(traitorRole);
|
||||
|
||||
// creadth: we need to create uplink for the antag.
|
||||
// PDA should be in place already, so we just need to
|
||||
// initiate uplink account.
|
||||
var uplinkAccount = new UplinkAccount(traitorStartingBalance, mind.OwnedEntity!);
|
||||
var accounts = EntityManager.EntitySysManager.GetEntitySystem<UplinkAccountsSystem>();
|
||||
accounts.AddNewAccount(uplinkAccount);
|
||||
|
||||
// try to place uplink
|
||||
if (!EntityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
|
||||
.AddUplink(mind.OwnedEntity!.Value, uplinkAccount))
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var player in list)
|
||||
{
|
||||
var mind = player.Data.ContentData()?.Mind;
|
||||
var antagPrototype = _prototypeManager.Index<AntagPrototype>(InnocentID);
|
||||
|
||||
DebugTools.AssertNotNull(mind);
|
||||
|
||||
mind!.AddRole(new SuspicionInnocentRole(mind, antagPrototype));
|
||||
}
|
||||
|
||||
foreach (var traitor in traitors)
|
||||
{
|
||||
traitor.GreetSuspicion(traitors, _chatManager);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
_playerManager.PlayerStatusChanged += PlayerManagerOnPlayerStatusChanged;
|
||||
|
||||
RoundMaxTime = TimeSpan.FromSeconds(_cfg.GetCVar(CCVars.SuspicionMaxTimeSeconds));
|
||||
|
||||
EndTime = _timing.CurTime + RoundMaxTime;
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-added-announcement"));
|
||||
|
||||
var filter = Filter.Empty()
|
||||
.AddWhere(session => ((IPlayerSession) session).ContentData()?.Mind?.HasRole<SuspicionTraitorRole>() ?? false);
|
||||
|
||||
SoundSystem.Play(filter, _addedSound.GetSound(), AudioParams.Default);
|
||||
|
||||
_doorSystem.AccessType = DoorSystem.AccessTypes.AllowAllNoExternal;
|
||||
|
||||
_checkTimerCancel = new CancellationTokenSource();
|
||||
Timer.SpawnRepeating(DeadCheckDelay, CheckWinConditions, _checkTimerCancel.Token);
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
_doorSystem.AccessType = DoorSystem.AccessTypes.Id;
|
||||
EndTime = null;
|
||||
_traitors.Clear();
|
||||
|
||||
_playerManager.PlayerStatusChanged -= PlayerManagerOnPlayerStatusChanged;
|
||||
|
||||
_checkTimerCancel.Cancel();
|
||||
}
|
||||
|
||||
private void CheckWinConditions()
|
||||
{
|
||||
if (!Enabled || !_cfg.GetCVar(CCVars.GameLobbyEnableWin))
|
||||
return;
|
||||
|
||||
var traitorsAlive = 0;
|
||||
var innocentsAlive = 0;
|
||||
|
||||
foreach (var playerSession in _playerManager.ServerSessions)
|
||||
{
|
||||
if (playerSession.AttachedEntity is not {Valid: true} playerEntity
|
||||
|| !_entities.TryGetComponent(playerEntity, out MobStateComponent? mobState)
|
||||
|| !_entities.HasComponent<SuspicionRoleComponent>(playerEntity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!mobState.IsAlive())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var mind = playerSession.ContentData()?.Mind;
|
||||
|
||||
if (mind != null && mind.HasRole<SuspicionTraitorRole>())
|
||||
traitorsAlive++;
|
||||
else
|
||||
innocentsAlive++;
|
||||
}
|
||||
|
||||
if (innocentsAlive + traitorsAlive == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-stalemate"));
|
||||
EndRound(Victory.Stalemate);
|
||||
}
|
||||
|
||||
else if (traitorsAlive == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-station-win"));
|
||||
EndRound(Victory.Innocents);
|
||||
}
|
||||
else if (innocentsAlive == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-check-winner-traitor-win"));
|
||||
EndRound(Victory.Traitors);
|
||||
}
|
||||
else if (_timing.CurTime > _endTime)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-suspicion-traitor-time-has-run-out"));
|
||||
EndRound(Victory.Innocents);
|
||||
}
|
||||
}
|
||||
|
||||
private enum Victory
|
||||
{
|
||||
Stalemate,
|
||||
Innocents,
|
||||
Traitors
|
||||
}
|
||||
|
||||
private void EndRound(Victory victory)
|
||||
{
|
||||
string text;
|
||||
|
||||
switch (victory)
|
||||
{
|
||||
case Victory.Innocents:
|
||||
text = Loc.GetString("rule-suspicion-end-round-innocents-victory");
|
||||
break;
|
||||
case Victory.Traitors:
|
||||
text = Loc.GetString("rule-suspicion-end-round-traitors-victory");
|
||||
break;
|
||||
default:
|
||||
text = Loc.GetString("rule-suspicion-end-round-nobody-victory");
|
||||
break;
|
||||
}
|
||||
|
||||
GameTicker.EndRound(text);
|
||||
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-restarting-in-seconds", ("seconds", (int) RoundEndDelay.TotalSeconds)));
|
||||
_checkTimerCancel.Cancel();
|
||||
|
||||
Timer.Spawn(RoundEndDelay, () => GameTicker.RestartRound());
|
||||
}
|
||||
|
||||
private void PlayerManagerOnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
|
||||
{
|
||||
if (e.NewStatus == SessionStatus.InGame)
|
||||
{
|
||||
SendUpdateTimerMessage(e.Session);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendUpdateToAll()
|
||||
{
|
||||
foreach (var player in _playerManager.ServerSessions.Where(p => p.Status == SessionStatus.InGame))
|
||||
{
|
||||
SendUpdateTimerMessage(player);
|
||||
}
|
||||
}
|
||||
|
||||
private void SendUpdateTimerMessage(IPlayerSession player)
|
||||
{
|
||||
var msg = new SuspicionMessages.SetSuspicionEndTimerMessage
|
||||
{
|
||||
EndTime = EndTime
|
||||
};
|
||||
|
||||
EntityManager.EntityNetManager?.SendSystemNetworkMessage(msg, player.ConnectedClient);
|
||||
}
|
||||
|
||||
public void AddTraitor(SuspicionRoleComponent role)
|
||||
{
|
||||
if (!_traitors.Add(role))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var traitor in _traitors)
|
||||
{
|
||||
traitor.AddAlly(role);
|
||||
}
|
||||
|
||||
role.SetAllies(_traitors);
|
||||
}
|
||||
|
||||
public void RemoveTraitor(SuspicionRoleComponent role)
|
||||
{
|
||||
if (!_traitors.Remove(role))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var traitor in _traitors)
|
||||
{
|
||||
traitor.RemoveAlly(role);
|
||||
}
|
||||
|
||||
role.ClearAllies();
|
||||
}
|
||||
|
||||
private void Reset(RoundRestartCleanupEvent ev)
|
||||
{
|
||||
EndTime = null;
|
||||
_traitors.Clear();
|
||||
}
|
||||
|
||||
private void OnPlayerDetached(EntityUid uid, SuspicionRoleComponent component, PlayerDetachedEvent args)
|
||||
{
|
||||
component.SyncRoles();
|
||||
}
|
||||
|
||||
private void OnPlayerAttached(EntityUid uid, SuspicionRoleComponent component, PlayerAttachedEvent args)
|
||||
{
|
||||
component.SyncRoles();
|
||||
}
|
||||
|
||||
private void OnRoleAdded(EntityUid uid, SuspicionRoleComponent component, RoleAddedEvent args)
|
||||
{
|
||||
if (args.Role is not SuspicionRole role) return;
|
||||
component.Role = role;
|
||||
}
|
||||
|
||||
private void OnRoleRemoved(EntityUid uid, SuspicionRoleComponent component, RoleRemovedEvent args)
|
||||
{
|
||||
if (args.Role is not SuspicionRole) return;
|
||||
component.Role = null;
|
||||
}
|
||||
|
||||
private void OnLateJoinRefresh(RefreshLateJoinAllowedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
ev.Disallow();
|
||||
}
|
||||
}
|
||||
@@ -1,270 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Atmos.EntitySystems;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Hands.Components;
|
||||
using Content.Server.Inventory.Components;
|
||||
using Content.Server.Items;
|
||||
using Content.Server.PDA;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Spawners.Components;
|
||||
using Content.Server.Traitor;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Server.Traitor.Uplink.Components;
|
||||
using Content.Server.TraitorDeathMatch.Components;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Damage;
|
||||
using Content.Shared.Damage.Prototypes;
|
||||
using Content.Shared.Inventory;
|
||||
using Content.Shared.MobState.Components;
|
||||
using Content.Shared.PDA;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public class TraitorDeathMatchRuleSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IPlayerManager _playerManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _robustRandom = default!;
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly MaxTimeRestartRuleSystem _restarter = default!;
|
||||
|
||||
public override string Prototype => "TraitorDeathMatch";
|
||||
|
||||
public string PDAPrototypeName => "CaptainPDA";
|
||||
public string BeltPrototypeName => "ClothingBeltJanitorFilled";
|
||||
public string BackpackPrototypeName => "ClothingBackpackFilled";
|
||||
|
||||
private bool _safeToEndRound = false;
|
||||
|
||||
private readonly Dictionary<UplinkAccount, string> _allOriginalNames = new();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
||||
SubscribeLocalEvent<PlayerSpawnCompleteEvent>(OnPlayerSpawned);
|
||||
SubscribeLocalEvent<GhostAttemptHandleEvent>(OnGhostAttempt);
|
||||
}
|
||||
|
||||
private void OnPlayerSpawned(PlayerSpawnCompleteEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var session = ev.Player;
|
||||
var startingBalance = _cfg.GetCVar(CCVars.TraitorDeathMatchStartingBalance);
|
||||
|
||||
// Yup, they're a traitor
|
||||
var mind = session.Data.ContentData()?.Mind;
|
||||
if (mind == null)
|
||||
{
|
||||
Logger.ErrorS("preset", "Failed getting mind for TDM player.");
|
||||
return;
|
||||
}
|
||||
|
||||
var traitorRole = new TraitorRole(mind);
|
||||
mind.AddRole(traitorRole);
|
||||
|
||||
// Delete anything that may contain "dangerous" role-specific items.
|
||||
// (This includes the PDA, as everybody gets the captain PDA in this mode for true-all-access reasons.)
|
||||
if (mind.OwnedEntity is {Valid: true} owned && TryComp(owned, out InventoryComponent? inventory))
|
||||
{
|
||||
var victimSlots = new[] {EquipmentSlotDefines.Slots.IDCARD, EquipmentSlotDefines.Slots.BELT, EquipmentSlotDefines.Slots.BACKPACK};
|
||||
foreach (var slot in victimSlots)
|
||||
{
|
||||
if (inventory.TryGetSlotItem(slot, out ItemComponent? vItem))
|
||||
Del(vItem.Owner);
|
||||
}
|
||||
|
||||
// Replace their items:
|
||||
|
||||
var ownedCoords = Transform(owned).Coordinates;
|
||||
|
||||
// pda
|
||||
var newPDA = Spawn(PDAPrototypeName, ownedCoords);
|
||||
inventory.Equip(EquipmentSlotDefines.Slots.IDCARD, Comp<ItemComponent>(newPDA));
|
||||
|
||||
// belt
|
||||
var newTmp = Spawn(BeltPrototypeName, ownedCoords);
|
||||
inventory.Equip(EquipmentSlotDefines.Slots.BELT, Comp<ItemComponent>(newTmp));
|
||||
|
||||
// backpack
|
||||
newTmp = Spawn(BackpackPrototypeName, ownedCoords);
|
||||
inventory.Equip(EquipmentSlotDefines.Slots.BACKPACK, Comp<ItemComponent>(newTmp));
|
||||
|
||||
// Like normal traitors, they need access to a traitor account.
|
||||
var uplinkAccount = new UplinkAccount(startingBalance, owned);
|
||||
var accounts = EntityManager.EntitySysManager.GetEntitySystem<UplinkAccountsSystem>();
|
||||
accounts.AddNewAccount(uplinkAccount);
|
||||
|
||||
EntityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
|
||||
.AddUplink(owned, uplinkAccount, newPDA);
|
||||
|
||||
_allOriginalNames[uplinkAccount] = Name(owned);
|
||||
|
||||
// The PDA needs to be marked with the correct owner.
|
||||
var pda = Comp<PDAComponent>(newPDA);
|
||||
EntityManager.EntitySysManager.GetEntitySystem<PDASystem>().SetOwner(pda, Name(owned));
|
||||
EntityManager.AddComponent<TraitorDeathMatchReliableOwnerTagComponent>(newPDA).UserId = mind.UserId;
|
||||
}
|
||||
|
||||
// Finally, it would be preferable if they spawned as far away from other players as reasonably possible.
|
||||
if (mind.OwnedEntity != null && FindAnyIsolatedSpawnLocation(mind, out var bestTarget))
|
||||
{
|
||||
Transform(mind.OwnedEntity.Value).Coordinates = bestTarget;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The station is too drained of air to safely continue.
|
||||
if (_safeToEndRound)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-death-match-station-is-too-unsafe-announcement"));
|
||||
_restarter.RoundMaxTime = TimeSpan.FromMinutes(1);
|
||||
_restarter.RestartTimer();
|
||||
_safeToEndRound = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnGhostAttempt(GhostAttemptHandleEvent ev)
|
||||
{
|
||||
if (!Enabled || ev.Handled)
|
||||
return;
|
||||
|
||||
ev.Handled = true;
|
||||
|
||||
var mind = ev.Mind;
|
||||
|
||||
if (mind.OwnedEntity is {Valid: true} entity && TryComp(entity, out MobStateComponent? mobState))
|
||||
{
|
||||
if (mobState.IsCritical())
|
||||
{
|
||||
// TODO BODY SYSTEM KILL
|
||||
var damage = new DamageSpecifier(_prototypeManager.Index<DamageTypePrototype>("Asphyxiation"), 100);
|
||||
Get<DamageableSystem>().TryChangeDamage(entity, damage, true);
|
||||
}
|
||||
else if (!mobState.IsDead())
|
||||
{
|
||||
if (HasComp<HandsComponent>(entity))
|
||||
{
|
||||
ev.Result = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var session = mind.Session;
|
||||
if (session == null)
|
||||
{
|
||||
ev.Result = false;
|
||||
return;
|
||||
}
|
||||
|
||||
GameTicker.Respawn(session);
|
||||
ev.Result = true;
|
||||
}
|
||||
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var lines = new List<string>();
|
||||
lines.Add(Loc.GetString("traitor-death-match-end-round-description-first-line"));
|
||||
foreach (var uplink in EntityManager.EntityQuery<UplinkComponent>(true))
|
||||
{
|
||||
var uplinkAcc = uplink.UplinkAccount;
|
||||
if (uplinkAcc != null && _allOriginalNames.ContainsKey(uplinkAcc))
|
||||
{
|
||||
lines.Add(Loc.GetString("traitor-death-match-end-round-description-entry",
|
||||
("originalName", _allOriginalNames[uplinkAcc]),
|
||||
("tcBalance", uplinkAcc.Balance)));
|
||||
}
|
||||
}
|
||||
|
||||
ev.AddLine(string.Join('\n', lines));
|
||||
}
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
_restarter.RoundMaxTime = TimeSpan.FromMinutes(30);
|
||||
_restarter.RestartTimer();
|
||||
_safeToEndRound = true;
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
}
|
||||
|
||||
// It would be nice if this function were moved to some generic helpers class.
|
||||
private bool FindAnyIsolatedSpawnLocation(Mind.Mind ignoreMe, out EntityCoordinates bestTarget)
|
||||
{
|
||||
// Collate people to avoid...
|
||||
var existingPlayerPoints = new List<EntityCoordinates>();
|
||||
foreach (var player in _playerManager.ServerSessions)
|
||||
{
|
||||
var avoidMeMind = player.Data.ContentData()?.Mind;
|
||||
if ((avoidMeMind == null) || (avoidMeMind == ignoreMe))
|
||||
continue;
|
||||
var avoidMeEntity = avoidMeMind.OwnedEntity;
|
||||
if (avoidMeEntity == null)
|
||||
continue;
|
||||
if (TryComp(avoidMeEntity.Value, out MobStateComponent? mobState))
|
||||
{
|
||||
// Does have mob state component; if critical or dead, they don't really matter for spawn checks
|
||||
if (mobState.IsCritical() || mobState.IsDead())
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Doesn't have mob state component. Assume something interesting is going on and don't count this as someone to avoid.
|
||||
continue;
|
||||
}
|
||||
existingPlayerPoints.Add(Transform(avoidMeEntity.Value).Coordinates);
|
||||
}
|
||||
|
||||
// Iterate over each possible spawn point, comparing to the existing player points.
|
||||
// On failure, the returned target is the location that we're already at.
|
||||
var bestTargetDistanceFromNearest = -1.0f;
|
||||
// Need the random shuffle or it stuffs the first person into Atmospherics pretty reliably
|
||||
var ents = EntityManager.EntityQuery<SpawnPointComponent>().Select(x => x.Owner).ToList();
|
||||
_robustRandom.Shuffle(ents);
|
||||
var foundATarget = false;
|
||||
bestTarget = EntityCoordinates.Invalid;
|
||||
var atmosphereSystem = EntitySystem.Get<AtmosphereSystem>();
|
||||
foreach (var entity in ents)
|
||||
{
|
||||
if (!atmosphereSystem.IsTileMixtureProbablySafe(Transform(entity).Coordinates))
|
||||
continue;
|
||||
|
||||
var distanceFromNearest = float.PositiveInfinity;
|
||||
foreach (var existing in existingPlayerPoints)
|
||||
{
|
||||
if (Transform(entity).Coordinates.TryDistance(EntityManager, existing, out var dist))
|
||||
distanceFromNearest = Math.Min(distanceFromNearest, dist);
|
||||
}
|
||||
if (bestTargetDistanceFromNearest < distanceFromNearest)
|
||||
{
|
||||
bestTarget = Transform(entity).Coordinates;
|
||||
bestTargetDistanceFromNearest = distanceFromNearest;
|
||||
foundATarget = true;
|
||||
}
|
||||
}
|
||||
return foundATarget;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,262 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.Chat.Managers;
|
||||
using Content.Server.Objectives.Interfaces;
|
||||
using Content.Server.Players;
|
||||
using Content.Server.Roles;
|
||||
using Content.Server.Traitor;
|
||||
using Content.Server.Traitor.Uplink;
|
||||
using Content.Server.Traitor.Uplink.Account;
|
||||
using Content.Shared.CCVar;
|
||||
using Content.Shared.Dataset;
|
||||
using Content.Shared.Sound;
|
||||
using Content.Shared.Traitor.Uplink;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Audio;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Player;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Random;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Content.Server.GameTicking.Rules;
|
||||
|
||||
public class TraitorRuleSystem : GameRuleSystem
|
||||
{
|
||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IObjectivesManager _objectivesManager = default!;
|
||||
[Dependency] private readonly IChatManager _chatManager = default!;
|
||||
|
||||
public override string Prototype => "Traitor";
|
||||
|
||||
private readonly SoundSpecifier _addedSound = new SoundPathSpecifier("/Audio/Misc/tatoralert.ogg");
|
||||
private readonly List<TraitorRole> _traitors = new ();
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
SubscribeLocalEvent<RoundStartAttemptEvent>(OnStartAttempt);
|
||||
SubscribeLocalEvent<RulePlayerJobsAssignedEvent>(OnPlayersSpawned);
|
||||
SubscribeLocalEvent<RoundEndTextAppendEvent>(OnRoundEndText);
|
||||
}
|
||||
|
||||
public override void Added()
|
||||
{
|
||||
// This seems silly, but I'll leave it.
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("rule-traitor-added-announcement"));
|
||||
}
|
||||
|
||||
public override void Removed()
|
||||
{
|
||||
_traitors.Clear();
|
||||
}
|
||||
|
||||
private void OnStartAttempt(RoundStartAttemptEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var minPlayers = _cfg.GetCVar(CCVars.TraitorMinPlayers);
|
||||
if (!ev.Forced && ev.Players.Length < minPlayers)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-not-enough-ready-players", ("readyPlayersCount", ev.Players.Length), ("minimumPlayers", minPlayers)));
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.Players.Length == 0)
|
||||
{
|
||||
_chatManager.DispatchServerAnnouncement(Loc.GetString("traitor-no-one-ready"));
|
||||
ev.Cancel();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnPlayersSpawned(RulePlayerJobsAssignedEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var playersPerTraitor = _cfg.GetCVar(CCVars.TraitorPlayersPerTraitor);
|
||||
var maxTraitors = _cfg.GetCVar(CCVars.TraitorMaxTraitors);
|
||||
var codewordCount = _cfg.GetCVar(CCVars.TraitorCodewordCount);
|
||||
var startingBalance = _cfg.GetCVar(CCVars.TraitorStartingBalance);
|
||||
var maxDifficulty = _cfg.GetCVar(CCVars.TraitorMaxDifficulty);
|
||||
var maxPicks = _cfg.GetCVar(CCVars.TraitorMaxPicks);
|
||||
|
||||
var list = new List<IPlayerSession>(ev.Players).Where(x =>
|
||||
x.Data.ContentData()?.Mind?.AllRoles.All(role => role is not Job {CanBeAntag: false}) ?? false
|
||||
).ToList();
|
||||
|
||||
var prefList = new List<IPlayerSession>();
|
||||
|
||||
foreach (var player in list)
|
||||
{
|
||||
if (!ev.Profiles.ContainsKey(player.UserId))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var profile = ev.Profiles[player.UserId];
|
||||
if (profile.AntagPreferences.Contains("Traitor"))
|
||||
{
|
||||
prefList.Add(player);
|
||||
}
|
||||
}
|
||||
|
||||
var numTraitors = MathHelper.Clamp(ev.Players.Length / playersPerTraitor,
|
||||
1, maxTraitors);
|
||||
|
||||
for (var i = 0; i < numTraitors; i++)
|
||||
{
|
||||
IPlayerSession traitor;
|
||||
if(prefList.Count < numTraitors)
|
||||
{
|
||||
if (list.Count == 0)
|
||||
{
|
||||
Logger.InfoS("preset", "Insufficient ready players to fill up with traitors, stopping the selection.");
|
||||
break;
|
||||
}
|
||||
traitor = _random.PickAndTake(list);
|
||||
Logger.InfoS("preset", "Insufficient preferred traitors, picking at random.");
|
||||
}
|
||||
else
|
||||
{
|
||||
traitor = _random.PickAndTake(prefList);
|
||||
list.Remove(traitor);
|
||||
Logger.InfoS("preset", "Selected a preferred traitor.");
|
||||
}
|
||||
var mind = traitor.Data.ContentData()?.Mind;
|
||||
if (mind == null)
|
||||
{
|
||||
Logger.ErrorS("preset", "Failed getting mind for picked traitor.");
|
||||
continue;
|
||||
}
|
||||
|
||||
// creadth: we need to create uplink for the antag.
|
||||
// PDA should be in place already, so we just need to
|
||||
// initiate uplink account.
|
||||
DebugTools.AssertNotNull(mind.OwnedEntity);
|
||||
|
||||
var uplinkAccount = new UplinkAccount(startingBalance, mind.OwnedEntity!);
|
||||
var accounts = EntityManager.EntitySysManager.GetEntitySystem<UplinkAccountsSystem>();
|
||||
accounts.AddNewAccount(uplinkAccount);
|
||||
|
||||
if (!EntityManager.EntitySysManager.GetEntitySystem<UplinkSystem>()
|
||||
.AddUplink(mind.OwnedEntity!.Value, uplinkAccount))
|
||||
continue;
|
||||
|
||||
var traitorRole = new TraitorRole(mind);
|
||||
mind.AddRole(traitorRole);
|
||||
_traitors.Add(traitorRole);
|
||||
}
|
||||
|
||||
var adjectives = _prototypeManager.Index<DatasetPrototype>("adjectives").Values;
|
||||
var verbs = _prototypeManager.Index<DatasetPrototype>("verbs").Values;
|
||||
|
||||
var codewordPool = adjectives.Concat(verbs).ToList();
|
||||
var finalCodewordCount = Math.Min(codewordCount, codewordPool.Count);
|
||||
var codewords = new string[finalCodewordCount];
|
||||
for (var i = 0; i < finalCodewordCount; i++)
|
||||
{
|
||||
codewords[i] = _random.PickAndTake(codewordPool);
|
||||
}
|
||||
|
||||
foreach (var traitor in _traitors)
|
||||
{
|
||||
traitor.GreetTraitor(codewords);
|
||||
|
||||
//give traitors their objectives
|
||||
var difficulty = 0f;
|
||||
for (var pick = 0; pick < maxPicks && maxDifficulty > difficulty; pick++)
|
||||
{
|
||||
var objective = _objectivesManager.GetRandomObjective(traitor.Mind);
|
||||
if (objective == null) continue;
|
||||
if (traitor.Mind.TryAddObjective(objective))
|
||||
difficulty += objective.Difficulty;
|
||||
}
|
||||
}
|
||||
|
||||
SoundSystem.Play(Filter.Empty().AddPlayers(_traitors.Select(t => t.Mind.Session!)), _addedSound.GetSound(), AudioParams.Default);
|
||||
}
|
||||
|
||||
private void OnRoundEndText(RoundEndTextAppendEvent ev)
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
var result = Loc.GetString("traitor-round-end-result", ("traitorCount", _traitors.Count));
|
||||
|
||||
foreach (var traitor in _traitors)
|
||||
{
|
||||
var name = traitor.Mind.CharacterName;
|
||||
traitor.Mind.TryGetSession(out var session);
|
||||
var username = session?.Name;
|
||||
|
||||
var objectives = traitor.Mind.AllObjectives.ToArray();
|
||||
if (objectives.Length == 0)
|
||||
{
|
||||
if (username != null)
|
||||
{
|
||||
if (name == null)
|
||||
result += "\n" + Loc.GetString("traitor-user-was-a-traitor", ("user", username));
|
||||
else
|
||||
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-named", ("user", username), ("name", name));
|
||||
}
|
||||
else if (name != null)
|
||||
result += "\n" + Loc.GetString("traitor-was-a-traitor-named", ("name", name));
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (username != null)
|
||||
{
|
||||
if (name == null)
|
||||
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives", ("user", username));
|
||||
else
|
||||
result += "\n" + Loc.GetString("traitor-user-was-a-traitor-with-objectives-named", ("user", username), ("name", name));
|
||||
}
|
||||
else if (name != null)
|
||||
result += "\n" + Loc.GetString("traitor-was-a-traitor-with-objectives-named", ("name", name));
|
||||
|
||||
foreach (var objectiveGroup in objectives.GroupBy(o => o.Prototype.Issuer))
|
||||
{
|
||||
result += "\n" + Loc.GetString($"preset-traitor-objective-issuer-{objectiveGroup.Key}");
|
||||
|
||||
foreach (var objective in objectiveGroup)
|
||||
{
|
||||
foreach (var condition in objective.Conditions)
|
||||
{
|
||||
var progress = condition.Progress;
|
||||
if (progress > 0.99f)
|
||||
{
|
||||
result += "\n- " + Loc.GetString(
|
||||
"traitor-objective-condition-success",
|
||||
("condition", condition.Title),
|
||||
("markupColor", "green")
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
result += "\n- " + Loc.GetString(
|
||||
"traitor-objective-condition-fail",
|
||||
("condition", condition.Title),
|
||||
("progress", (int) (progress * 100)),
|
||||
("markupColor", "red")
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ev.AddLine(result);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user