Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Content.Client/Ghost/GhostSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,5 +181,11 @@ public void ToggleGhostVisibility()
{
GhostVisibility = !GhostVisibility;
}

public void ReturnToRound() // WD EDIT
{
var msg = new GhostReturnToRoundRequest();
RaiseNetworkEvent(msg);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ public void LoadGui()
Gui.ReturnToBodyPressed += ReturnToBody;
Gui.GhostRolesPressed += GhostRolesPressed;
Gui.TargetWindow.WarpClicked += OnWarpClicked;
Gui.ReturnToRoundPressed += ReturnToRound; // WD EDIT

UpdateGui();
}
Expand All @@ -133,6 +134,7 @@ public void UnloadGui()
Gui.ReturnToBodyPressed -= ReturnToBody;
Gui.GhostRolesPressed -= GhostRolesPressed;
Gui.TargetWindow.WarpClicked -= OnWarpClicked;
Gui.ReturnToRoundPressed -= ReturnToRound; // WD EDIT

Gui.Hide();
}
Expand All @@ -142,6 +144,11 @@ private void ReturnToBody()
_system?.ReturnToBody();
}

private void ReturnToRound() // WD EDIT
{
_system?.ReturnToRound();
}

private void RequestWarps()
{
_system?.RequestWarps();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
<Button Name="ReturnToBodyButton" Text="{Loc ghost-gui-return-to-body-button}" />
<Button Name="GhostWarpButton" Text="{Loc ghost-gui-ghost-warp-button}" />
<Button Name="GhostRolesButton" />
<Button Name="ReturnToRound" Text="{Loc ghost-gui-return-to-round-button}" /> <!-- WD EDIT -->
</BoxContainer>
</widgets:GhostGui>
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand All @@ -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()
Expand Down
11 changes: 11 additions & 0 deletions Content.Server/GameTicking/GameTicker.GamePreset.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -303,6 +305,15 @@ public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaComma
_mind.Visit(mindId, ghost, mind);
else
_mind.TransferTo(mindId, ghost, mind: mind);

// WD EDIT START
if (mind.Session == null)
return true;

var userId = mind.Session!.UserId;
if (!_ghostReturnToRound.DeathTime.TryGetValue(userId, out _))
_ghostReturnToRound.DeathTime[userId] = _gameTiming.CurTime;
// WD EDIT END
return true;
}

Expand Down
81 changes: 81 additions & 0 deletions Content.Server/GameTicking/GameTicker.Spawning.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -154,6 +156,66 @@ 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))
{
var sameChar = false;
var checkAvoid = false;

var allPlayerMinds = EntityQuery<MindComponent>()
.Where(mind => mind.OriginalOwnerUserId == player.UserId);

foreach (var mind in allPlayerMinds)
{
if (mind.CharacterName == character.Name)
{
sameChar = true;
break;
}

if (mind.CharacterName != null)
{
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;
sameChar = true;

break;
}
case >= 50f:
{
_chatManager.SendAdminAlert(Loc.GetString("ghost-respawn-log-character-almost-same",
("player", player.Name), ("try", true), ("oldName", mind.CharacterName), ("newName", character.Name)));

break;
}
}
}
}

if (sameChar)
{
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);
Expand Down Expand Up @@ -346,6 +408,25 @@ 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 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()
{
Expand Down
108 changes: 108 additions & 0 deletions Content.Server/_White/Ghost/GhostReturnToRoundSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
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!;

public readonly Dictionary<NetUserId, TimeSpan> DeathTime = new();

public override void Initialize()
{
SubscribeNetworkEvent<GhostReturnToRoundRequest>(OnGhostReturnToRoundRequest);
SubscribeLocalEvent<RoundRestartCleanupEvent>(ResetDeathTimes);
}

private void OnGhostReturnToRoundRequest(GhostReturnToRoundRequest msg, EntitySessionEventArgs args)
{
var cfg = IoCManager.Resolve<IConfigurationManager>();
var maxPlayers = cfg.GetCVar(WhiteCVars.GhostRespawnMaxPlayers);
var connectedClient = args.SenderSession.ConnectedClient;
if (_playerManager.PlayerCount >= maxPlayers)
{
var message = Loc.GetString("ghost-respawn-max-players", ("players", maxPlayers));
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server,
message,
wrappedMessage,
default,
false,
connectedClient,
Color.Red);
return;
}

var userId = args.SenderSession.UserId;
if (!DeathTime.TryGetValue(userId, out var deathTime))
{
var message = Loc.GetString("ghost-respawn-bug");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server,
message,
wrappedMessage,
default,
false,
connectedClient,
Color.Red);
DeathTime[userId] = _gameTiming.CurTime;
return;
}

var timeUntilRespawn = (double) cfg.GetCVar(WhiteCVars.GhostRespawnTime);
var timePast = (_gameTiming.CurTime - deathTime).TotalMinutes;
if (timePast >= timeUntilRespawn)
{
var ticker = Get<GameTicker>();
var playerMgr = IoCManager.Resolve<IPlayerManager>();
playerMgr.TryGetSessionById(userId, out var targetPlayer);

if (targetPlayer != null)
ticker.Respawn(targetPlayer);
DeathTime.Remove(userId);

_adminLogger.Add(LogType.Mind, LogImpact.Medium, $"{Loc.GetString("ghost-respawn-log-return-to-lobby", ("userName", connectedClient.UserName))}");

var message = Loc.GetString("ghost-respawn-window-rules-footer");
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server,
message,
wrappedMessage,
default,
false,
connectedClient,
Color.Red);

}
else
{
var message = Loc.GetString("ghost-respawn-time-left", ("time", (int) (timeUntilRespawn - timePast)));
var wrappedMessage = Loc.GetString("chat-manager-server-wrap-message", ("message", message));
_chatManager.ChatMessageToOne(Shared.Chat.ChatChannel.Server,
message,
wrappedMessage,
default,
false,
connectedClient,
Color.Red);
}
}

private void ResetDeathTimes(RoundRestartCleanupEvent ev)
{
DeathTime.Clear();
}
}
5 changes: 5 additions & 0 deletions Content.Shared/Ghost/SharedGhostSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,9 @@ public GhostUpdateGhostRoleCountEvent(int availableGhostRoleCount)
AvailableGhostRoles = availableGhostRoleCount;
}
}

// WD EDIT START
[Serializable, NetSerializable]
public sealed class GhostReturnToRoundRequest : EntityEventArgs;
// WD EDIT END
}
10 changes: 9 additions & 1 deletion Content.Shared/_White/CVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public sealed class WhiteCVars

public static readonly CVarDef<double> AspectChance =
CVarDef.Create("aspects.chance", 0.1d, CVar.SERVERONLY);

#endregion

#region Keybind
Expand All @@ -31,4 +31,12 @@ public static readonly CVarDef<string>
ServerCulture = CVarDef.Create("white.culture", "ru-RU", CVar.REPLICATED | CVar.SERVER);

#endregion

#region GhostRespawn
public static readonly CVarDef<float> GhostRespawnTime =
CVarDef.Create("ghost.respawn_time", 15f, CVar.SERVERONLY);

public static readonly CVarDef<int> GhostRespawnMaxPlayers =
CVarDef.Create("ghost.respawn_max_players", 40, CVar.SERVERONLY);
#endregion
}
1 change: 1 addition & 0 deletions Resources/Locale/en-US/_white/ghost/ghost-gui.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ghost-gui-return-to-round-button = Return to round
16 changes: 16 additions & 0 deletions Resources/Locale/en-US/_white/ghost/ghost-respawn.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
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-bug = There is no time of death. The standard value is set.
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.
1 change: 1 addition & 0 deletions Resources/Locale/ru-RU/_white/ghost/ghost-gui.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ghost-gui-return-to-round-button = Вернуться в раунд
17 changes: 17 additions & 0 deletions Resources/Locale/ru-RU/_white/ghost/ghost-respawn.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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-bug = Нет времени смерти. Установлено стандартное значение.
ghost-respawn-same-character = Нельзя заходить в раунд за того же персонажа. Поменяйте его в настройках персонажей.

ghost-respawn-log-character-almost-same = Игрок { $player } { $try ->
[true] зашёл
*[false] попытался зайти
} в раунд после возвращения в лобби с похожим именем. Прошлое имя: { $oldName }, текущее: { $newName }.
ghost-respawn-log-return-to-lobby = { $userName } вернулся в лобби.
Loading