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 System #998

Merged
merged 3 commits into from
Oct 4, 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()
{
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;

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

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

private void ReturnToRound()
{
_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}" />
</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;

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();
}

public void Hide()
Expand Down
3 changes: 2 additions & 1 deletion 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.Ghost;
using Content.Server.GameTicking.Presets;
using Content.Server.Maps;
using Content.Shared.CCVar;
Expand All @@ -21,7 +22,7 @@ namespace Content.Server.GameTicking
public sealed partial class GameTicker
{
[Dependency] private readonly MobThresholdSystem _mobThresholdSystem = default!;

[Dependency] private readonly GhostReturnToRoundSystem _ghostReturnToRound = default!;
public const float PresetFailedCooldownIncrease = 30f;

/// <summary>
Expand Down
70 changes: 70 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,20 @@ private void SpawnPlayer(ICommonSession player, HumanoidCharacterProfile charact
return;
}

//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;
}

// 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 @@ -345,6 +361,60 @@ public void SpawnObserver(ICommonSession player)
_adminLogger.Add(LogType.LateJoin, LogImpact.Low, $"{player.Name} late joined the round as an Observer with {ToPrettyString(ghost):entity}.");
}

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;
}

#region Mob Spawning Helpers
private EntityUid SpawnObserverMob()
{
Expand Down
80 changes: 80 additions & 0 deletions Content.Server/Ghost/GhostReturnToRoundSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
using Content.Server.Administration.Logs;
using Content.Server.Chat.Managers;
using Content.Server.GameTicking;
using Content.Shared.Database;
using Content.Shared.CCVar;
using Content.Shared.Ghost;
using Robust.Server.Player;
using Robust.Shared.Configuration;
using Robust.Shared.Network;
using Robust.Shared.Timing;

namespace Content.Server.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!;
[Dependency] private readonly GameTicker _ticker = 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.Channel;
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(CCVars.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(CCVars.GhostRespawnTime);
var timePast = (_gameTiming.CurTime - deathTime).TotalMinutes;
if (timePast >= timeUntilRespawn)
{
_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));
}
}
13 changes: 11 additions & 2 deletions Content.Shared/CCVar/CCVars.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2472,8 +2472,8 @@ public static readonly CVarDef<float>
CVarDef.Create("rest.hold_look_up", false, CVar.CLIENT | CVar.ARCHIVE);

/// <summary>
/// When true, entities that fall to the ground will be able to crawl under tables and
/// plastic flaps, allowing them to take cover from gunshots.
/// When true, entities that fall to the ground will be able to crawl under tables and
/// plastic flaps, allowing them to take cover from gunshots.
/// </summary>
public static readonly CVarDef<bool> CrawlUnderTables =
CVarDef.Create("rest.crawlundertables", false, CVar.REPLICATED);
Expand Down Expand Up @@ -2506,5 +2506,14 @@ public static readonly CVarDef<float>

#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
}
}
3 changes: 3 additions & 0 deletions Content.Shared/Ghost/SharedGhostSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,7 @@ public GhostUpdateGhostRoleCountEvent(int availableGhostRoleCount)
AvailableGhostRoles = availableGhostRoleCount;
}
}

[Serializable, NetSerializable]
public sealed class GhostReturnToRoundRequest : EntityEventArgs;
}
1 change: 1 addition & 0 deletions Resources/Locale/en-US/ghost/ghost-gui.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ ghost-roles-window-rules-footer = The button will enable after {$time} seconds (

ghost-return-to-body-title = Return to Body
ghost-return-to-body-text = You are being revived! Return to your body?
ghost-gui-return-to-round-button = Return to round
15 changes: 15 additions & 0 deletions Resources/Locale/en-US/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/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/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 } вернулся в лобби.
Loading