diff --git a/Content.Client/Ghost/GhostSystem.cs b/Content.Client/Ghost/GhostSystem.cs
index c42e7cd0e0..4bc6ba4c87 100644
--- a/Content.Client/Ghost/GhostSystem.cs
+++ b/Content.Client/Ghost/GhostSystem.cs
@@ -181,5 +181,11 @@ public void ToggleGhostVisibility()
{
GhostVisibility = !GhostVisibility;
}
+
+ public void ReturnToRound() // WD EDIT
+ {
+ var msg = new GhostReturnToRoundRequest();
+ RaiseNetworkEvent(msg);
+ }
}
}
diff --git a/Content.Client/UserInterface/Systems/Ghost/GhostUIController.cs b/Content.Client/UserInterface/Systems/Ghost/GhostUIController.cs
index 12d6c65953..3834b35fe7 100644
--- a/Content.Client/UserInterface/Systems/Ghost/GhostUIController.cs
+++ b/Content.Client/UserInterface/Systems/Ghost/GhostUIController.cs
@@ -120,6 +120,7 @@ public void LoadGui()
Gui.ReturnToBodyPressed += ReturnToBody;
Gui.GhostRolesPressed += GhostRolesPressed;
Gui.TargetWindow.WarpClicked += OnWarpClicked;
+ Gui.ReturnToRoundPressed += ReturnToRound; // WD EDIT
UpdateGui();
}
@@ -133,6 +134,7 @@ public void UnloadGui()
Gui.ReturnToBodyPressed -= ReturnToBody;
Gui.GhostRolesPressed -= GhostRolesPressed;
Gui.TargetWindow.WarpClicked -= OnWarpClicked;
+ Gui.ReturnToRoundPressed -= ReturnToRound; // WD EDIT
Gui.Hide();
}
@@ -142,6 +144,11 @@ private void ReturnToBody()
_system?.ReturnToBody();
}
+ private void ReturnToRound() // WD EDIT
+ {
+ _system?.ReturnToRound();
+ }
+
private void RequestWarps()
{
_system?.RequestWarps();
diff --git a/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml b/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml
index 0f65debb4e..7e1760aa5e 100644
--- a/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml
+++ b/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml
@@ -5,5 +5,6 @@
+
diff --git a/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml.cs b/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml.cs
index 18c1d545a7..ee3f8005a9 100644
--- a/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml.cs
+++ b/Content.Client/UserInterface/Systems/Ghost/Widgets/GhostGui.xaml.cs
@@ -14,6 +14,7 @@ public sealed partial class GhostGui : UIWidget
public event Action? RequestWarpsPressed;
public event Action? ReturnToBodyPressed;
public event Action? GhostRolesPressed;
+ public event Action? ReturnToRoundPressed; // WD EDIT
public GhostGui()
{
@@ -26,6 +27,7 @@ public GhostGui()
GhostWarpButton.OnPressed += _ => RequestWarpsPressed?.Invoke();
ReturnToBodyButton.OnPressed += _ => ReturnToBodyPressed?.Invoke();
GhostRolesButton.OnPressed += _ => GhostRolesPressed?.Invoke();
+ ReturnToRound.OnPressed += _ => ReturnToRoundPressed?.Invoke(); // WD EDIT
}
public void Hide()
diff --git a/Content.Server/GameTicking/GameTicker.GamePreset.cs b/Content.Server/GameTicking/GameTicker.GamePreset.cs
index b97a16ab99..8ddd0ba180 100644
--- a/Content.Server/GameTicking/GameTicker.GamePreset.cs
+++ b/Content.Server/GameTicking/GameTicker.GamePreset.cs
@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
+using Content.Server._White.Ghost;
using Content.Server.GameTicking.Presets;
using Content.Server.Maps;
using Content.Shared.CCVar;
@@ -21,6 +22,7 @@ namespace Content.Server.GameTicking
public sealed partial class GameTicker
{
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;
+ [Dependency] private readonly GhostReturnToRoundSystem _ghostReturnToRound = default!; // WD EDIT
public const float PresetFailedCooldownIncrease = 30f;
@@ -303,6 +305,7 @@ public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaComma
_mind.Visit(mindId, ghost, mind);
else
_mind.TransferTo(mindId, ghost, mind: mind);
+
return true;
}
diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs
index b28521c20a..3d35128e08 100644
--- a/Content.Server/GameTicking/GameTicker.Spawning.cs
+++ b/Content.Server/GameTicking/GameTicker.Spawning.cs
@@ -7,7 +7,9 @@
using Content.Server.Speech.Components;
using Content.Server.Station.Components;
using Content.Shared.CCVar;
+using Content.Shared.Chat;
using Content.Shared.Database;
+using Content.Shared.Mind;
using Content.Shared.Players;
using Content.Shared.Preferences;
using Content.Shared.Roles;
@@ -154,6 +156,22 @@ private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile charact
return;
}
+ //WD EDIT START
+ //Ghost system return to round, check for whether the character isn't the same.
+ if (lateJoin && !_adminManager.IsAdmin(player) && !CheckGhostReturnToRound(player, character, out var checkAvoid))
+ {
+ var message = checkAvoid
+ ? Loc.GetString("ghost-respawn-same-character-slightly-changed-name")
+ : Loc.GetString("ghost-respawn-same-character");
+ var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
+
+ _chatManager.ChatMessageToOne(ChatChannel.Server, message, wrappedMessage,
+ default, false, player.Channel, Color.Red);
+
+ return;
+ }
+ //WD EDIT END
+
// We raise this event to allow other systems to handle spawning this player themselves. (e.g. late-join wizard, etc)
var bev = new PlayerBeforeSpawnEvent(player, character, jobId, lateJoin, station);
RaiseLocalEvent(bev);
@@ -346,6 +364,66 @@ public void SpawnObserver(ICommonSession player)
_adminLogger.Add(LogType.LateJoin, LogImpact.Low, $"{player.Name} late joined the round as an Observer with {ToPrettyString(ghost):entity}.");
}
+ //WD EDIT START
+ private bool CheckGhostReturnToRound(ICommonSession player, HumanoidCharacterProfile character, out bool checkAvoid)
+ {
+ checkAvoid = false;
+
+ var allPlayerMinds = EntityQuery()
+ .Where(mind => mind.OriginalOwnerUserId == player.UserId);
+
+ foreach (var mind in allPlayerMinds)
+ {
+ if (mind.CharacterName == character.Name)
+ return false;
+
+ if (mind.CharacterName == null)
+ continue;
+
+ var similarity = CalculateStringSimilarity(mind.CharacterName, character.Name);
+ switch (similarity)
+ {
+ case >= 85f:
+ {
+ _chatManager.SendAdminAlert(Loc.GetString("ghost-respawn-log-character-almost-same",
+ ("player", player.Name), ("try", false), ("oldName", mind.CharacterName),
+ ("newName", character.Name)));
+ checkAvoid = true;
+
+ return false;
+ }
+ case >= 50f:
+ {
+ _chatManager.SendAdminAlert(Loc.GetString("ghost-respawn-log-character-almost-same",
+ ("player", player.Name), ("try", true), ("oldName", mind.CharacterName),
+ ("newName", character.Name)));
+
+ break;
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private float CalculateStringSimilarity(string str1, string str2)
+ {
+ var minLength = Math.Min(str1.Length, str2.Length);
+ var matchingCharacters = 0;
+
+ for (var i = 0; i < minLength; i++)
+ {
+ if (str1[i] == str2[i])
+ matchingCharacters++;
+ }
+
+ float maxLength = Math.Max(str1.Length, str2.Length);
+ var similarityPercentage = (matchingCharacters / maxLength) * 100;
+
+ return similarityPercentage;
+ }
+ //WD EDIT END
+
#region Mob Spawning Helpers
private EntityUid SpawnObserverMob()
{
diff --git a/Content.Server/_White/Ghost/GhostReturnToRoundSystem.cs b/Content.Server/_White/Ghost/GhostReturnToRoundSystem.cs
new file mode 100644
index 0000000000..e3c5a0647f
--- /dev/null
+++ b/Content.Server/_White/Ghost/GhostReturnToRoundSystem.cs
@@ -0,0 +1,81 @@
+using Content.Server.Administration.Logs;
+using Content.Server.Chat.Managers;
+using Content.Server.GameTicking;
+using Content.Shared._White;
+using Content.Shared.Database;
+using Content.Shared.GameTicking;
+using Content.Shared.Ghost;
+using Robust.Server.Player;
+using Robust.Shared.Configuration;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+
+namespace Content.Server._White.Ghost;
+
+public sealed class GhostReturnToRoundSystem : EntitySystem
+{
+ [Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly IAdminLogManager _adminLogger = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ public override void Initialize()
+ {
+ SubscribeNetworkEvent(OnGhostReturnToRoundRequest);
+ }
+
+ private void OnGhostReturnToRoundRequest(GhostReturnToRoundRequest msg, EntitySessionEventArgs args)
+ {
+ var uid = args.SenderSession.AttachedEntity;
+
+ if (uid == null)
+ return;
+
+ var connectedClient = args.SenderSession.ConnectedClient;
+ var userId = args.SenderSession.UserId;
+
+ TryGhostReturnToRound(uid.Value, connectedClient, userId, out var message, out var wrappedMessage);
+
+ _chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server,
+ message,
+ wrappedMessage,
+ default,
+ false,
+ connectedClient,
+ Color.Red);
+ }
+
+ private void TryGhostReturnToRound(EntityUid uid, INetChannel connectedClient, NetUserId userId, out string message, out string wrappedMessage)
+ {
+ var maxPlayers = _cfg.GetCVar(WhiteCVars.GhostRespawnMaxPlayers);
+ if (_playerManager.PlayerCount >= maxPlayers)
+ {
+ message = Loc.GetString("ghost-respawn-max-players", ("players", maxPlayers));
+ wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
+ return;
+ }
+
+ var deathTime = EnsureComp(uid).TimeOfDeath;
+ var timeUntilRespawn = _cfg.GetCVar(WhiteCVars.GhostRespawnTime);
+ var timePast = (_gameTiming.CurTime - deathTime).TotalMinutes;
+ if (timePast >= timeUntilRespawn)
+ {
+ var ticker = Get();
+ _playerManager.TryGetSessionById(userId, out var targetPlayer);
+
+ if (targetPlayer != null)
+ ticker.Respawn(targetPlayer);
+
+ _adminLogger.Add(LogType.Mind, LogImpact.Medium, $"{Loc.GetString("ghost-respawn-log-return-to-lobby", ("userName", connectedClient.UserName))}");
+
+ message = Loc.GetString("ghost-respawn-window-rules-footer");
+ wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
+
+ return;
+ }
+
+ message = Loc.GetString("ghost-respawn-time-left", ("time", (int) (timeUntilRespawn - timePast)));
+ wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
+ }
+}
diff --git a/Content.Shared/Ghost/SharedGhostSystem.cs b/Content.Shared/Ghost/SharedGhostSystem.cs
index 24da2f144f..ede37bda2d 100644
--- a/Content.Shared/Ghost/SharedGhostSystem.cs
+++ b/Content.Shared/Ghost/SharedGhostSystem.cs
@@ -148,4 +148,9 @@ public GhostUpdateGhostRoleCountEvent(int availableGhostRoleCount)
AvailableGhostRoles = availableGhostRoleCount;
}
}
+
+ // WD EDIT START
+ [Serializable, NetSerializable]
+ public sealed class GhostReturnToRoundRequest : EntityEventArgs;
+ // WD EDIT END
}
diff --git a/Content.Shared/_White/CVars.cs b/Content.Shared/_White/CVars.cs
index 1bc90e4935..48c2f5bd78 100644
--- a/Content.Shared/_White/CVars.cs
+++ b/Content.Shared/_White/CVars.cs
@@ -32,6 +32,15 @@ public static readonly CVarDef
#endregion
+ #region GhostRespawn
+ public static readonly CVarDef GhostRespawnTime =
+ CVarDef.Create("ghost.respawn_time", 15d, CVar.SERVERONLY);
+
+ public static readonly CVarDef GhostRespawnMaxPlayers =
+ CVarDef.Create("ghost.respawn_max_players", 40, CVar.SERVERONLY);
+
+ #endregion
+
#region OptionsMisc
public static readonly CVarDef LogInChat =
diff --git a/Resources/Locale/en-US/_white/ghost/ghost-gui.ftl b/Resources/Locale/en-US/_white/ghost/ghost-gui.ftl
new file mode 100644
index 0000000000..9a89f477a6
--- /dev/null
+++ b/Resources/Locale/en-US/_white/ghost/ghost-gui.ftl
@@ -0,0 +1 @@
+ghost-gui-return-to-round-button = Return to round
\ No newline at end of file
diff --git a/Resources/Locale/en-US/_white/ghost/ghost-respawn.ftl b/Resources/Locale/en-US/_white/ghost/ghost-respawn.ftl
new file mode 100644
index 0000000000..7c5d6f75a8
--- /dev/null
+++ b/Resources/Locale/en-US/_white/ghost/ghost-respawn.ftl
@@ -0,0 +1,15 @@
+ghost-respawn-time-left = Before the opportunity to return to the round { $time }
+ { $time ->
+ [one] minute
+ *[other] minutes
+ }
+ghost-respawn-max-players = The function is not available, there should be fewer players on the server { $players }.
+ghost-respawn-window-title = Rules for returning to the round
+ghost-respawn-window-rules-footer = By using this feature, you [color=#ff7700]agree[/color] [color=#ff0000]not to transfer[/color] the knowledge of your past character to a new one. For violation of the clause specified here, [color=#ff0000]a ban in the amount of 3 days or more follows[/color].
+ghost-respawn-same-character = You cannot enter the round for the same character. Change it in the character settings.
+
+ghost-respawn-log-character-almost-same = Player { $player } { $try ->
+ [true] join
+ *[false] tried to join
+} in the round after the respawn with a similar name. Past name: { $oldName }, current: { $newName }.
+ghost-respawn-log-return-to-lobby = { $userName } returned to the lobby.
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/_white/ghost/ghost-gui.ftl b/Resources/Locale/ru-RU/_white/ghost/ghost-gui.ftl
new file mode 100644
index 0000000000..ce9084d21f
--- /dev/null
+++ b/Resources/Locale/ru-RU/_white/ghost/ghost-gui.ftl
@@ -0,0 +1 @@
+ghost-gui-return-to-round-button = Вернуться в раунд
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/_white/ghost/ghost-respawn.ftl b/Resources/Locale/ru-RU/_white/ghost/ghost-respawn.ftl
new file mode 100644
index 0000000000..eaa118b399
--- /dev/null
+++ b/Resources/Locale/ru-RU/_white/ghost/ghost-respawn.ftl
@@ -0,0 +1,16 @@
+ghost-respawn-time-left = До возможности вернуться в раунд { $time }
+ { $time ->
+ [one] минута
+ [few] минуты
+ *[other] минут
+ }
+ghost-respawn-max-players = Функция недоступна, игроков на сервере должно быть меньше { $players }.
+ghost-respawn-window-title = Правила возвращения в раунд
+ghost-respawn-window-rules-footer = Пользуясь это функцией, вы [color=#ff7700]обязуетесь[/color] [color=#ff0000]не переносить[/color] знания своего прошлого персонажа в нового. За нарушение пункта, указанного здесь, следует [color=#ff0000]бан в размере от 3-ех дней[/color].
+ghost-respawn-same-character = Нельзя заходить в раунд за того же персонажа. Поменяйте его в настройках персонажей.
+
+ghost-respawn-log-character-almost-same = Игрок { $player } { $try ->
+ [true] зашёл
+ *[false] попытался зайти
+} в раунд после возвращения в лобби с похожим именем. Прошлое имя: { $oldName }, текущее: { $newName }.
+ghost-respawn-log-return-to-lobby = { $userName } вернулся в лобби.
\ No newline at end of file
diff --git a/Resources/Locale/ru-RU/ghost/ghost-gui.ftl b/Resources/Locale/ru-RU/ghost/ghost-gui.ftl
index 074860a264..fcbde2e178 100644
--- a/Resources/Locale/ru-RU/ghost/ghost-gui.ftl
+++ b/Resources/Locale/ru-RU/ghost/ghost-gui.ftl
@@ -30,18 +30,4 @@ ghost-roles-window-no-roles-available-label = В настоящее время
ghost-return-to-body-title = Вернуться в тело
ghost-return-to-body-text = Вы возрождаетесь! Вернуться в свое тело?
-ghost-roles-window-rules-footer = Кнопка станет доступна через { $time } секунд (эта задержка нужна, чтобы убедиться, что вы прочитали правила).
-
-ghost-respawn-time-left = Минут осталось до возможности вернуться в раунд - { $time }.
-ghost-respawn-max-players = Функция недоступна, игроков на сервере должно быть меньше { $players }.
-ghost-respawn-window-title = Правила возвращения в раунд
-ghost-respawn-window-request-button-timer = Принять ({ $time }сек.)
-ghost-respawn-window-request-button = Принять
-ghost-respawn-window-rules-footer = Пользуясь это функцией, вы [color=#ff7700]обязуетесь[/color] [color=#ff0000]не переносить[/color] знания своего прошлого персонажа в нового, [color=#ff0000]не метамстить[/color]. Каждый новый персонаж - [color=#ff7700]чистый уникальный лист[/color], который никак не связан с предыдущим. Поэтому не забудьте [color=#ff7700]поменять персонажа[/color] перед заходом, а также помните, что за нарушение пункта, указанного здесь, следует [color=#ff0000]бан в размере от 3ех дней[/color].
-ghost-respawn-bug = Нет времени смерти. Установлено стандартное значение.
-ghost-respawn-same-character = Нельзя заходить в раунд за того же персонажа. Поменяйте его в настройках персонажей.
-ghost-respawn-character-almost-same = Игрок { $player } { $try ->
- [true] зашёл
- *[false] попытался зайти
-} в раунд после респауна с похожим именем. Прошлое имя: { $oldName }, текущее: { $newName }.
-ghost-respawn-same-character-slightly-changed-name = Попытка обойти запрет входа в раунд тем же персонажем. Ваши действия будут переданы администрации!
+ghost-roles-window-rules-footer = Кнопка станет доступна через { $time } секунд (эта задержка нужна, чтобы убедиться, что вы прочитали правила).
\ No newline at end of file