Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Port] Respawn Button / Кнопка Респавна #56

Merged
merged 8 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 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,7 @@ public bool OnGhostAttempt(EntityUid mindId, bool canReturnGlobal, bool viaComma
_mind.Visit(mindId, ghost, mind);
else
_mind.TransferTo(mindId, ghost, mind: mind);

return true;
}

Expand Down
78 changes: 78 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,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);
Expand Down Expand Up @@ -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<MindComponent>()
.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()
{
Expand Down
81 changes: 81 additions & 0 deletions Content.Server/_White/Ghost/GhostReturnToRoundSystem.cs
Original file line number Diff line number Diff line change
@@ -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<GhostReturnToRoundRequest>(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<GhostComponent>(uid).TimeOfDeath;
var timeUntilRespawn = _cfg.GetCVar(WhiteCVars.GhostRespawnTime);
var timePast = (_gameTiming.CurTime - deathTime).TotalMinutes;
if (timePast >= timeUntilRespawn)
{
var ticker = Get<GameTicker>();
_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));
}
}
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
}
9 changes: 9 additions & 0 deletions Content.Shared/_White/CVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ public static readonly CVarDef<string>

#endregion

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

public static readonly CVarDef<int> GhostRespawnMaxPlayers =
CVarDef.Create("ghost.respawn_max_players", 40, CVar.SERVERONLY);

#endregion

#region OptionsMisc

public static readonly CVarDef<bool> LogInChat =
Expand Down
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
15 changes: 15 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,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.
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 = Вернуться в раунд
16 changes: 16 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,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 } вернулся в лобби.
16 changes: 1 addition & 15 deletions Resources/Locale/ru-RU/ghost/ghost-gui.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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 } секунд (эта задержка нужна, чтобы убедиться, что вы прочитали правила).
Loading