From 07275846cf397923fb9864053653c0cf17fe4fd2 Mon Sep 17 00:00:00 2001
From: Volodymir Ohorodnytskyi <102746941+Legendaxe@users.noreply.github.com>
Date: Sun, 18 Jun 2023 18:13:30 +0300
Subject: [PATCH] add: configurable teleport to cryo for disconnected players
(#77)
---
.../Preferences/UI/HumanoidProfileEditor.xaml | 4 +
.../UI/HumanoidProfileEditor.xaml.cs | 16 +++
.../SS220/CryopodSSD/AfkSSDSystem.cs | 101 ++++++++++++++++++
.../SS220/CryopodSSD/CryopodSSDSystem.cs | 91 +++++++++++++++-
Content.Shared/CCVar/CCVars.cs | 3 +
.../Preferences/HumanoidCharacterProfile.cs | 10 +-
.../CryopodSSD/SharedCryopodSSDSystem.cs | 15 ++-
.../ui/humanoid-profile-editor.ftl | 2 +
.../SS220/CryopodSSD/CryopodSSD.yml | 22 +++-
9 files changed, 256 insertions(+), 8 deletions(-)
create mode 100644 Content.Server/SS220/CryopodSSD/AfkSSDSystem.cs
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
index 493d1ded795e88..59761f9dbdebbd 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml
@@ -158,6 +158,10 @@
+
+
+
+
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
index fe0994a4bf84e1..2efff20b452ae3 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
@@ -73,6 +73,8 @@ public sealed partial class HumanoidProfileEditor : Control
private SingleMarkingPicker _facialHairPicker => CFacialHairPicker;
private EyeColorPicker _eyesPicker => CEyeColorPicker;
+ private CheckBox _teleportAfkToCryoStorage => CTeleportAfkToCryoStorage;
+
private TabContainer _tabContainer => CTabContainer;
private BoxContainer _jobList => CJobList;
private BoxContainer _antagList => CAntagList;
@@ -513,6 +515,14 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
};
_previewSpriteSideControl.AddChild(_previewSpriteSide);
#endregion Dummy
+
+ #region TeleportAfkToCryoStorage
+
+ _tabContainer.SetTabTitle(5, Loc.GetString("humanoid-profile-edtior-afkPreferences-tab"));
+ _teleportAfkToCryoStorage.Pressed = Profile?.TeleportAfkToCryoStorage ?? true;
+ _teleportAfkToCryoStorage.OnToggled += args => SetTeleportAfkToCryoStorage(args.Pressed);
+
+ #endregion TeleportAfkToCryoStorage
#endregion Left
@@ -845,6 +855,12 @@ private void SetBackpack(BackpackPreference newBackpack)
IsDirty = true;
}
+ private void SetTeleportAfkToCryoStorage(bool newTeleportAfkToCryoStorage)
+ {
+ Profile = Profile?.WithTeleportAfkToCryoStorage(newTeleportAfkToCryoStorage);
+ IsDirty = true;
+ }
+
public void Save()
{
IsDirty = false;
diff --git a/Content.Server/SS220/CryopodSSD/AfkSSDSystem.cs b/Content.Server/SS220/CryopodSSD/AfkSSDSystem.cs
new file mode 100644
index 00000000000000..2c35010fb09772
--- /dev/null
+++ b/Content.Server/SS220/CryopodSSD/AfkSSDSystem.cs
@@ -0,0 +1,101 @@
+// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
+
+using System.Linq;
+using Content.Server.Afk;
+using Content.Server.GameTicking;
+using Content.Server.Mind.Components;
+using Content.Server.Preferences.Managers;
+using Content.Shared.Body.Components;
+using Content.Shared.CCVar;
+using Content.Shared.Preferences;
+using Robust.Server.Player;
+using Robust.Shared.Configuration;
+using Robust.Shared.Enums;
+using Robust.Shared.Network;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Server.SS220.CryopodSSD;
+
+public sealed class AfkSSDSystem : EntitySystem
+{
+ [Dependency] private readonly CryopodSSDSystem _cryopodSSDSystem = default!;
+ [Dependency] private readonly IServerPreferencesManager _preferencesManager = default!;
+ [Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly IGameTiming _gameTiming = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ private float _afkTeleportTocryo;
+
+ private readonly Dictionary<(EntityUid, NetUserId), (TimeSpan, bool)> _entityEnteredSSDTimes = new();
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ _cfg.OnValueChanged(CCVars.AfkTeleportToCryo, SetAfkTeleportToCryo, true);
+ _playerManager.PlayerStatusChanged += OnPlayerChange;
+ }
+
+ private void SetAfkTeleportToCryo(float value)
+ => _afkTeleportTocryo = value;
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+
+ _cfg.UnsubValueChanged(CCVars.AfkTeleportToCryo, SetAfkTeleportToCryo);
+ _playerManager.PlayerStatusChanged -= OnPlayerChange;
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+ foreach (var pair in _entityEnteredSSDTimes.Where(uid => HasComp(uid.Key.Item1)))
+ {
+ if (pair.Value.Item2 && IsTeleportAfkToCryoTime(pair.Value.Item1)
+ && _cryopodSSDSystem.TeleportEntityToCryoStorageWithDelay(pair.Key.Item1))
+ {
+ _entityEnteredSSDTimes.Remove(pair.Key);
+ }
+ }
+ }
+
+ private bool IsTeleportAfkToCryoTime(TimeSpan time)
+ {
+ var timeOut = TimeSpan.FromSeconds(_afkTeleportTocryo);
+ return _gameTiming.CurTime - time > timeOut;
+ }
+
+ private void OnPlayerChange(object? sender, SessionStatusEventArgs e)
+ {
+
+ switch (e.NewStatus)
+ {
+ case SessionStatus.Disconnected:
+ if (e.Session.AttachedEntity is null
+ || !HasComp(e.Session.AttachedEntity)
+ || !HasComp(e.Session.AttachedEntity))
+ {
+ break;
+ }
+
+ if (!_preferencesManager.TryGetCachedPreferences(e.Session.UserId, out var preferences)
+ || preferences.SelectedCharacter is not HumanoidCharacterProfile humanoidPreferences)
+ {
+ break;
+ }
+ _entityEnteredSSDTimes[(e.Session.AttachedEntity.Value, e.Session.UserId)]
+ = (_gameTiming.CurTime, humanoidPreferences.TeleportAfkToCryoStorage);
+ break;
+ case SessionStatus.Connected:
+ if (_entityEnteredSSDTimes
+ .TryFirstOrNull(item => item.Key.Item2 == e.Session.UserId, out var item))
+ {
+ _entityEnteredSSDTimes.Remove(item.Value.Key);
+ }
+
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/Content.Server/SS220/CryopodSSD/CryopodSSDSystem.cs b/Content.Server/SS220/CryopodSSD/CryopodSSDSystem.cs
index 4f1d78589b1c65..e33f0949331a8e 100644
--- a/Content.Server/SS220/CryopodSSD/CryopodSSDSystem.cs
+++ b/Content.Server/SS220/CryopodSSD/CryopodSSDSystem.cs
@@ -1,8 +1,11 @@
// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt
using Content.Server.Mind.Components;
+using Content.Server.Station.Systems;
+using Content.Shared.Audio;
using Content.Shared.CCVar;
using Content.Shared.DoAfter;
using Content.Shared.DragDrop;
+using Content.Shared.Mobs.Systems;
using Content.Shared.SS220.CryopodSSD;
using Content.Shared.Verbs;
using Robust.Shared.Configuration;
@@ -20,6 +23,9 @@ public sealed class CryopodSSDSystem : SharedCryopodSSDSystem
{
[Dependency] private readonly SSDStorageConsoleSystem _SSDStorageConsoleSystem = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfterSystem = default!;
+ [Dependency] private readonly MobStateSystem _mobStateSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
+ [Dependency] private readonly StationSystem _stationSystem = default!;
[Dependency] private readonly IConfigurationManager _cfg = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
private ISawmill _sawmill = default!;
@@ -35,10 +41,11 @@ public override void Initialize()
_cfg.OnValueChanged(CCVars.AutoTransferToCryoDelay, SetAutoTransferDelay, true);
SubscribeLocalEvent(OnComponentInit);
-
+
SubscribeLocalEvent>(AddAlternativeVerbs);
SubscribeLocalEvent(OnCryopodSSDLeaveAction);
+ SubscribeLocalEvent(OnTeleportFinished);
SubscribeLocalEvent(OnDragFinished);
SubscribeLocalEvent(HandleDragDropOn);
}
@@ -62,6 +69,13 @@ public override void Update(float frameTime)
TransferToCryoStorage(uid, cryopodSSDComp);
}
}
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+
+ _cfg.UnsubValueChanged(CCVars.AutoTransferToCryoDelay, SetAutoTransferDelay);
+ }
///
/// Ejects body from cryopod
@@ -84,6 +98,73 @@ public override void Update(float frameTime)
base.EjectBody(uid, cryopodSsdComponent);
return contained;
}
+
+
+ ///
+ /// Tries to teleport target inside cryopod, if any available
+ ///
+ /// Target to teleport in first matching cryopod
+ /// true if player successfully transferred to cryo storage, otherwise returns false
+ public bool TeleportEntityToCryoStorageWithDelay(EntityUid target)
+ {
+ var station = _stationSystem.GetOwningStation(target);
+
+ if (station is null)
+ {
+ return false;
+ }
+
+ foreach (var comp in EntityQuery())
+ {
+ if (comp.BodyContainer.ContainedEntity == target)
+ {
+ return true;
+ }
+ }
+
+ var cryopodSSDComponents = EntityQueryEnumerator();
+
+ while (cryopodSSDComponents
+ .MoveNext(out var cryopodSSDUid, out var cryopodSSDComp))
+ {
+ if (cryopodSSDComp.BodyContainer.ContainedEntity is null
+ && _stationSystem.GetOwningStation(cryopodSSDUid) == station)
+ {
+ var portal = Spawn("CryoStoragePortal", Transform(target).Coordinates);
+
+ if (TryComp(portal, out var ambientSoundComponent))
+ {
+ _audioSystem.PlayPvs(ambientSoundComponent.Sound, portal);
+ }
+
+ var doAfterArgs = new DoAfterArgs(target, cryopodSSDComp.EntryDelay, new TeleportToCryoFinished(portal), cryopodSSDUid)
+ {
+ BreakOnDamage = false,
+ BreakOnTargetMove = false,
+ BreakOnUserMove = true,
+ NeedHand = false,
+ };
+
+ _doAfterSystem.TryStartDoAfter(doAfterArgs);
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void OnTeleportFinished(EntityUid uid, CryopodSSDComponent component, TeleportToCryoFinished args)
+ {
+ InsertBody(uid, args.User, component);
+ TransferToCryoStorage(uid, component);
+
+ if (TryComp(args.PortalId, out var ambientSoundComponent))
+ {
+ _audioSystem.PlayPvs(ambientSoundComponent.Sound, args.PortalId);
+ }
+
+ EntityManager.DeleteEntity(args.PortalId);
+ }
private void SetAutoTransferDelay(float value) => _autoTransferDelay = value;
@@ -96,7 +177,13 @@ private void HandleDragDropOn(EntityUid uid, CryopodSSDComponent cryopodSsdCompo
if (!TryComp(args.Dragged, out MindComponent? mind) || !mind.HasMind)
{
- _sawmill.Error($"{ToPrettyString(args.User)} tries to put non-playable entity into SSD cryopod {ToPrettyString(args.Dragged)}");
+ _sawmill.Info($"{ToPrettyString(args.User)} tries to put non-playable entity into SSD cryopod {ToPrettyString(args.Dragged)}");
+ return;
+ }
+
+ if (_mobStateSystem.IsDead(args.Dragged))
+ {
+ _sawmill.Log(LogLevel.Warning,$"{ToPrettyString(args.User)} tries to put dead entity(passing client check) {ToPrettyString(args.Dragged)} into SSD cryopod, potentially client exploit");
return;
}
diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs
index 138c7afd642e42..e66959bd4ce8f7 100644
--- a/Content.Shared/CCVar/CCVars.cs
+++ b/Content.Shared/CCVar/CCVars.cs
@@ -1341,6 +1341,9 @@ public static readonly CVarDef
public static readonly CVarDef AfkTimeKick =
CVarDef.Create("afk.time_kick", 600f, CVar.SERVERONLY);
+ public static readonly CVarDef AfkTeleportToCryo =
+ CVarDef.Create("afk.teleport_to_cryo", 1800f, CVar.SERVERONLY);
+
/*
* IC
*/
diff --git a/Content.Shared/Preferences/HumanoidCharacterProfile.cs b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
index e450407630dd01..b40310f605cb55 100644
--- a/Content.Shared/Preferences/HumanoidCharacterProfile.cs
+++ b/Content.Shared/Preferences/HumanoidCharacterProfile.cs
@@ -46,7 +46,8 @@ private HumanoidCharacterProfile(
Dictionary jobPriorities,
PreferenceUnavailableMode preferenceUnavailable,
List antagPreferences,
- List traitPreferences)
+ List traitPreferences,
+ bool teleportAfkToCryoStorage = true)
{
Name = name;
FlavorText = flavortext;
@@ -62,6 +63,7 @@ private HumanoidCharacterProfile(
PreferenceUnavailable = preferenceUnavailable;
_antagPreferences = antagPreferences;
_traitPreferences = traitPreferences;
+ TeleportAfkToCryoStorage = teleportAfkToCryoStorage;
}
/// Copy constructor but with overridable references (to prevent useless copies)
@@ -71,7 +73,7 @@ private HumanoidCharacterProfile(
List antagPreferences,
List traitPreferences)
: this(other.Name, other.FlavorText, other.Species, other.Voice, other.Age, other.Sex, other.Gender, other.Appearance, other.Clothing, other.Backpack,
- jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences)
+ jobPriorities, other.PreferenceUnavailable, antagPreferences, traitPreferences, other.TeleportAfkToCryoStorage)
{
}
@@ -224,6 +226,7 @@ public static HumanoidCharacterProfile RandomWithSpecies(string species = Shared
public IReadOnlyList AntagPreferences => _antagPreferences;
public IReadOnlyList TraitPreferences => _traitPreferences;
public PreferenceUnavailableMode PreferenceUnavailable { get; private set; }
+ public bool TeleportAfkToCryoStorage { get; private set; } = true;
public HumanoidCharacterProfile WithName(string name)
{
@@ -346,6 +349,9 @@ public HumanoidCharacterProfile WithTraitPreference(string traitId, bool pref)
return new(this, _jobPriorities, _antagPreferences, list);
}
+ public HumanoidCharacterProfile WithTeleportAfkToCryoStorage(bool teleportAfkToCryoStorage)
+ => new(this) { TeleportAfkToCryoStorage = teleportAfkToCryoStorage };
+
public string Summary =>
Loc.GetString(
"humanoid-character-profile-summary",
diff --git a/Content.Shared/SS220/CryopodSSD/SharedCryopodSSDSystem.cs b/Content.Shared/SS220/CryopodSSD/SharedCryopodSSDSystem.cs
index 1525a5834dadff..005802893d2988 100644
--- a/Content.Shared/SS220/CryopodSSD/SharedCryopodSSDSystem.cs
+++ b/Content.Shared/SS220/CryopodSSD/SharedCryopodSSDSystem.cs
@@ -54,7 +54,7 @@ public bool InsertBody(EntityUid uid, EntityUid target, CryopodSSDComponent cryo
return false;
var xform = Transform(target);
- cryopodSsdComponent.BodyContainer.Insert(target, transform: xform);
+ cryopodSsdComponent.BodyContainer.Insert(target, transform: xform, force: true);
if (_prototypeManager.TryIndex("CryopodSSDLeave", out var leaveAction))
{
@@ -118,7 +118,7 @@ private void OnCryopodSSDCanDropTarget(EntityUid uid, CryopodSSDComponent compon
if (args.Handled)
return;
- args.CanDrop = HasComp(args.Dragged);
+ args.CanDrop = HasComp(args.Dragged) && _mobStateSystem.IsAlive(args.Dragged);
args.Handled = true;
}
@@ -168,6 +168,17 @@ protected void AddAlternativeVerbs(EntityUid uid, CryopodSSDComponent cryopodSSD
public sealed class CryopodSSDDragFinished : SimpleDoAfterEvent
{
}
+
+ [Serializable, NetSerializable]
+ public sealed class TeleportToCryoFinished : SimpleDoAfterEvent
+ {
+ public EntityUid PortalId { get; private set; }
+
+ public TeleportToCryoFinished(EntityUid portalId)
+ {
+ PortalId = portalId;
+ }
+ }
}
}
diff --git a/Resources/Locale/ru-RU/preferences/ui/humanoid-profile-editor.ftl b/Resources/Locale/ru-RU/preferences/ui/humanoid-profile-editor.ftl
index ccd86ee30733b2..9e4ddfcaec5745 100644
--- a/Resources/Locale/ru-RU/preferences/ui/humanoid-profile-editor.ftl
+++ b/Resources/Locale/ru-RU/preferences/ui/humanoid-profile-editor.ftl
@@ -39,3 +39,5 @@ humanoid-profile-editor-job-priority-never-button = Никогда
humanoid-profile-editor-naming-rules-warning = Внимание: Оскорбительные или странные имена и описания могут повлечь за собой беседу с администрацией. Прочитайте \[Правила\].
humanoid-profile-editor-markings-tab = Черты внешности
humanoid-profile-editor-flavortext-tab = Описание
+humanoid-profile-edtior-afkPreferences-tab = ССД
+humanoid-profile-edtior-afkPreferences-teleport-to-cryo-storage = Телепортация в крио хранилище при длительном ССД
\ No newline at end of file
diff --git a/Resources/Prototypes/SS220/CryopodSSD/CryopodSSD.yml b/Resources/Prototypes/SS220/CryopodSSD/CryopodSSD.yml
index efac119da8c887..83d36ad6c50efe 100644
--- a/Resources/Prototypes/SS220/CryopodSSD/CryopodSSD.yml
+++ b/Resources/Prototypes/SS220/CryopodSSD/CryopodSSD.yml
@@ -32,6 +32,8 @@
type: CryopodSSDBoundUserInterface
- type: SSDStorageConsole
whitelist:
+ components:
+ - Stamp
tags:
- HighRiskItem
- type: ContainerContainer
@@ -64,7 +66,6 @@
sprite: SS220/Structures/Machines/cryopodSSD.rsi
state: icon
- type: CryopodSSD
- autoTransferToCryoDelay: 900
- type: SSDStorageConsole
isItReallyCryopod: true
- type: ContainerContainer
@@ -72,4 +73,21 @@
storagebase: !type:Container
pod-body:
!type:ContainerSlot
- showEnts: false
\ No newline at end of file
+ showEnts: false
+
+- type: entity
+ id: CryoStoragePortal
+ noSpawn: true
+ name: портал в крио
+ components:
+ - type: Sprite
+ sprite: /Textures/Effects/portal.rsi
+ state: portal-blue
+ - type: PointLight
+ netsync: false
+ color: SkyBlue
+ radius: 3
+ energy: 3
+ - type: AmbientSound
+ enabled: false
+ sound: /Audio/Effects/teleport_departure.ogg