diff --git a/Content.Client/Ghost/GhostSystem.cs b/Content.Client/Ghost/GhostSystem.cs index ee80470807..ebe9629a4a 100644 --- a/Content.Client/Ghost/GhostSystem.cs +++ b/Content.Client/Ghost/GhostSystem.cs @@ -136,6 +136,7 @@ namespace Content.Client.Ghost //_actions.RemoveAction(uid, component.ToggleGhostHearingActionEntity); //Dont need in CP14 //CP14 _actions.RemoveAction(uid, component.CP14ToggleRoofActionEntity); + _actions.RemoveAction(uid, component.CP14RespawnActionEntity); //CP14 end if (uid != _playerManager.LocalEntity) diff --git a/Content.Server/GameTicking/GameTicker.Spawning.cs b/Content.Server/GameTicking/GameTicker.Spawning.cs index d758b36197..b05572849e 100644 --- a/Content.Server/GameTicking/GameTicker.Spawning.cs +++ b/Content.Server/GameTicking/GameTicker.Spawning.cs @@ -1,6 +1,7 @@ using System.Globalization; using System.Linq; using System.Numerics; +using Content.Server._CP14.Respawn; using Content.Server.Administration.Managers; using Content.Server.Administration.Systems; using Content.Server.GameTicking.Events; @@ -212,6 +213,13 @@ namespace Content.Server.GameTicking character = HumanoidCharacterProfile.RandomWithSpecies(speciesId); } + //CP14 blocking rejoining to game with same character + var CP14Ev = new CP14PlayerSpawnAttemptEvent(player, character, jobId, lateJoin, station); + RaiseLocalEvent(CP14Ev); + if (CP14Ev.Cancelled) + return; + //CP14 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); diff --git a/Content.Server/Ghost/GhostSystem.cs b/Content.Server/Ghost/GhostSystem.cs index 1c10d08dcd..cda6996053 100644 --- a/Content.Server/Ghost/GhostSystem.cs +++ b/Content.Server/Ghost/GhostSystem.cs @@ -224,18 +224,19 @@ namespace Content.Server.Ghost // Entity can't see ghosts anymore. _eye.RefreshVisibilityMask(uid); - _actions.RemoveAction(uid, component.BooActionEntity); + //_actions.RemoveAction(uid, component.BooActionEntity); //Dont need in CP14 } private void OnMapInit(EntityUid uid, GhostComponent component, MapInitEvent args) { - _actions.AddAction(uid, ref component.BooActionEntity, component.BooAction); + //_actions.AddAction(uid, ref component.BooActionEntity, component.BooAction); //Dont need in CP14 //_actions.AddAction(uid, ref component.ToggleGhostHearingActionEntity, component.ToggleGhostHearingAction); //Dont need in CP14 _actions.AddAction(uid, ref component.ToggleLightingActionEntity, component.ToggleLightingAction); _actions.AddAction(uid, ref component.ToggleFoVActionEntity, component.ToggleFoVAction); _actions.AddAction(uid, ref component.ToggleGhostsActionEntity, component.ToggleGhostsAction); //CP14 _actions.AddAction(uid, ref component.CP14ToggleRoofActionEntity, component.CP14ToggleRoofAction); + _actions.AddAction(uid, ref component.CP14RespawnActionEntity, component.CP14RespawnAction); //CP14 end } diff --git a/Content.Server/_CP14/Respawn/CP14RespawnSystem.cs b/Content.Server/_CP14/Respawn/CP14RespawnSystem.cs new file mode 100644 index 0000000000..8b50e5a3e9 --- /dev/null +++ b/Content.Server/_CP14/Respawn/CP14RespawnSystem.cs @@ -0,0 +1,97 @@ +using Content.Server.Chat.Systems; +using Content.Server.GameTicking; +using Content.Server.GameTicking.Events; +using Content.Shared._CP14.Respawn; +using Content.Shared.Administration.Managers; +using Content.Shared.GameTicking; +using Content.Shared.Ghost; +using Content.Shared.Preferences; +using JetBrains.Annotations; +using Robust.Shared.Audio; +using Robust.Shared.Network; +using Robust.Shared.Player; + +namespace Content.Server._CP14.Respawn; + +public sealed partial class CP14RespawnSystem : EntitySystem +{ + [Dependency] private readonly GameTicker _gameTicker = default!; + [Dependency] private readonly ChatSystem _chat = default!; + [Dependency] private readonly ISharedAdminManager _admin = default!; + + /// + /// We keep a list of all characters connected in a round to prevent the same character from re-entering the round. + /// Of course, we only check by character name, and this is easy to circumvent by renaming the character, + /// but the main goal here is to make the player understand that this is not the right thing to do. + /// If a player abuses the ability to re-enter a round, the administration will take care of it. + /// + private Dictionary> _usedCharacters = new(); + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnRespawnAction); + SubscribeLocalEvent(OnBeforePlayerSpawn); + SubscribeLocalEvent(OnRoundEnd); + } + + private void OnRoundEnd(RoundStartingEvent ev) + { + _usedCharacters.Clear(); + } + + private void OnBeforePlayerSpawn(CP14PlayerSpawnAttemptEvent ev) + { + if (_admin.IsAdmin(ev.Player, true)) + return; + + if (!_usedCharacters.TryGetValue(ev.Player.UserId, out var usedCharacters)) + { + usedCharacters = []; + _usedCharacters[ev.Player.UserId] = usedCharacters; + } + + if (usedCharacters.Contains(ev.Profile.Name)) + { + var filter = Filter.Empty().AddPlayer(ev.Player); + _chat.DispatchFilteredAnnouncement( + filter, + Loc.GetString("cp14-respawn-same-char-bloked"), + colorOverride: Color.Red, + announcementSound: new SoundPathSpecifier("/Audio/Effects/beep1.ogg"), + sender: "Server"); + ev.Cancel(); + return; + } + + usedCharacters.Add(ev.Profile.Name); + } + + private void OnRespawnAction(Entity ent, ref CP14RespawnAction args) + { + if (!TryComp(ent, out var actor)) + return; + + _gameTicker.Respawn(actor.PlayerSession); + } +} + +/// +/// Called before a player spawns for a specific character. Can be canceled by prohibiting connection for that character. +/// +[PublicAPI] +public sealed class CP14PlayerSpawnAttemptEvent( + ICommonSession player, + HumanoidCharacterProfile profile, + string? jobId, + bool lateJoin, + EntityUid station) + : CancellableEntityEventArgs +{ + public ICommonSession Player { get; } = player; + public HumanoidCharacterProfile Profile { get; } = profile; + public string? JobId { get; } = jobId; + public bool LateJoin { get; } = lateJoin; + public EntityUid Station { get; } = station; +} diff --git a/Content.Shared/Ghost/GhostComponent.cs b/Content.Shared/Ghost/GhostComponent.cs index c0a287af0d..ca87871c5c 100644 --- a/Content.Shared/Ghost/GhostComponent.cs +++ b/Content.Shared/Ghost/GhostComponent.cs @@ -47,8 +47,14 @@ public sealed partial class GhostComponent : Component [DataField] public EntProtoId CP14ToggleRoofAction = "CP14ActionToggleRoofs"; + [DataField] + public EntProtoId CP14RespawnAction = "CP14ActionRespawn"; + [DataField, AutoNetworkedField] public EntityUid? CP14ToggleRoofActionEntity; + + [DataField, AutoNetworkedField] + public EntityUid? CP14RespawnActionEntity; //CP14 Ghost entities end // End actions diff --git a/Content.Shared/_CP14/Respawn/CP14SharedRespawnSystem.cs b/Content.Shared/_CP14/Respawn/CP14SharedRespawnSystem.cs new file mode 100644 index 0000000000..09c49d3ed7 --- /dev/null +++ b/Content.Shared/_CP14/Respawn/CP14SharedRespawnSystem.cs @@ -0,0 +1,7 @@ +using Content.Shared.Actions; + +namespace Content.Shared._CP14.Respawn; + +public sealed partial class CP14RespawnAction : InstantActionEvent +{ +} diff --git a/Resources/Locale/en-US/_CP14/respawn.ftl b/Resources/Locale/en-US/_CP14/respawn.ftl new file mode 100644 index 0000000000..a7b27721f8 --- /dev/null +++ b/Resources/Locale/en-US/_CP14/respawn.ftl @@ -0,0 +1,3 @@ +cp14-respawn-action-popup = Are you sure you want to return to the lobby? You will no longer be able to play as this character! + +cp14-respawn-same-char-bloked = This character has died. You can no longer use them in this round. \ No newline at end of file diff --git a/Resources/Locale/ru-RU/_CP14/respawn.ftl b/Resources/Locale/ru-RU/_CP14/respawn.ftl new file mode 100644 index 0000000000..cd5e437cc4 --- /dev/null +++ b/Resources/Locale/ru-RU/_CP14/respawn.ftl @@ -0,0 +1,3 @@ +cp14-respawn-action-popup = Вы уверены что хотите вернуться в лобби? Игра за этого персонажа станет недоступной! + +cp14-respawn-same-char-bloked = Этот персонаж погиб. Вы больше не можете использовать его в этом раунде. \ No newline at end of file diff --git a/Resources/Prototypes/_CP14/Entities/Actions/ghost.yml b/Resources/Prototypes/_CP14/Entities/Actions/ghost.yml index a4aed57606..8acb83eca7 100644 --- a/Resources/Prototypes/_CP14/Entities/Actions/ghost.yml +++ b/Resources/Prototypes/_CP14/Entities/Actions/ghost.yml @@ -38,3 +38,20 @@ - type: InstantAction event: !type:CP14ToggleRoofVisibilityAction +- type: entity + id: CP14ActionRespawn + name: Respawn + description: Return to the lobby to connect with a new character. + components: + - type: Action + priority: 20 + startDelay: true + useDelay: 300 + checkCanInteract: false + icon: + sprite: _CP14/Actions/zLevel.rsi + state: respawn + - type: ConfirmableAction + popup: cp14-respawn-action-popup + - type: InstantAction + event: !type:CP14RespawnAction \ No newline at end of file diff --git a/Resources/Textures/_CP14/Actions/zLevel.rsi/meta.json b/Resources/Textures/_CP14/Actions/zLevel.rsi/meta.json index 59cc25eb7b..6abdad2bc3 100644 --- a/Resources/Textures/_CP14/Actions/zLevel.rsi/meta.json +++ b/Resources/Textures/_CP14/Actions/zLevel.rsi/meta.json @@ -15,6 +15,9 @@ }, { "name": "roof" + }, + { + "name": "respawn" } ] } diff --git a/Resources/Textures/_CP14/Actions/zLevel.rsi/respawn.png b/Resources/Textures/_CP14/Actions/zLevel.rsi/respawn.png new file mode 100644 index 0000000000..d0fe4e1abc Binary files /dev/null and b/Resources/Textures/_CP14/Actions/zLevel.rsi/respawn.png differ