diff --git a/Content.Server/Tips/TipsSystem.cs b/Content.Server/Tips/TipsSystem.cs new file mode 100644 index 0000000000..3ca7a0414b --- /dev/null +++ b/Content.Server/Tips/TipsSystem.cs @@ -0,0 +1,118 @@ +using Content.Server.Chat.Managers; +using Content.Server.GameTicking; +using Content.Shared.CCVar; +using Content.Shared.Chat; +using Content.Shared.Dataset; +using Robust.Shared.Configuration; +using Robust.Shared.Player; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using SpaceWizards.Sodium.Interop; + +namespace Content.Server.Tips; + +/// +/// Handles periodically displaying gameplay tips to all players ingame. +/// +public sealed class TipsSystem : EntitySystem +{ + [Dependency] private readonly IChatManager _chat = default!; + [Dependency] private readonly IPrototypeManager _prototype = default!; + [Dependency] private readonly IConfigurationManager _cfg = default!; + [Dependency] private readonly IGameTiming _timing = default!; + [Dependency] private readonly IRobustRandom _random = default!; + [Dependency] private readonly GameTicker _ticker = default!; + + private bool _tipsEnabled; + private float _tipTimeOutOfRound; + private float _tipTimeInRound; + private string _tipsDataset = ""; + + [ViewVariables(VVAccess.ReadWrite)] + private TimeSpan _nextTipTime = TimeSpan.Zero; + + public override void Initialize() + { + base.Initialize(); + + SubscribeLocalEvent(OnGameRunLevelChanged); + _cfg.OnValueChanged(CCVars.TipFrequencyOutOfRound, SetOutOfRound, true); + _cfg.OnValueChanged(CCVars.TipFrequencyInRound, SetInRound, true); + _cfg.OnValueChanged(CCVars.TipsEnabled, SetEnabled, true); + _cfg.OnValueChanged(CCVars.TipsDataset, SetDataset, true); + + RecalculateNextTipTime(); + } + + public override void Update(float frameTime) + { + base.Update(frameTime); + + if (!_tipsEnabled) + return; + + if (_nextTipTime != TimeSpan.Zero && _timing.CurTime > _nextTipTime) + { + AnnounceRandomTip(); + RecalculateNextTipTime(); + } + } + + private void SetOutOfRound(float value) + { + _tipTimeOutOfRound = value; + } + + private void SetInRound(float value) + { + _tipTimeInRound = value; + } + + private void SetEnabled(bool value) + { + _tipsEnabled = value; + + if (_nextTipTime != TimeSpan.Zero) + RecalculateNextTipTime(); + } + + private void SetDataset(string value) + { + _tipsDataset = value; + } + + private void AnnounceRandomTip() + { + if (!_prototype.TryIndex(_tipsDataset, out var tips)) + return; + + var tip = _random.Pick(tips.Values); + var msg = Loc.GetString("tips-system-chat-message-wrap", ("tip", tip)); + + _chat.ChatMessageToManyFiltered(Filter.Broadcast(), ChatChannel.OOC, tip, msg, + EntityUid.Invalid, false, false, Color.MediumPurple); + } + + private void RecalculateNextTipTime() + { + if (_ticker.RunLevel == GameRunLevel.InRound) + { + _nextTipTime = _timing.CurTime + TimeSpan.FromSeconds(_tipTimeInRound); + } + else + { + _nextTipTime = _timing.CurTime + TimeSpan.FromSeconds(_tipTimeOutOfRound); + } + } + + private void OnGameRunLevelChanged(GameRunLevelChangedEvent ev) + { + // reset for lobby -> inround + // reset for inround -> post but not post -> lobby + if (ev.New == GameRunLevel.InRound || ev.Old == GameRunLevel.InRound) + { + RecalculateNextTipTime(); + } + } +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 584f947051..bf6dd087fe 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -385,6 +385,35 @@ namespace Content.Shared.CCVar public static readonly CVarDef PiratesPlayersPerOp = CVarDef.Create("pirates.players_per_pirate", 5); + /* + * Tips + */ + + /// + /// Whether tips being shown is enabled at all. + /// + public static readonly CVarDef TipsEnabled = + CVarDef.Create("tips.enabled", true); + + /// + /// The dataset prototype to use when selecting a random tip. + /// + public static readonly CVarDef TipsDataset = + CVarDef.Create("tips.dataset", "Tips"); + + /// + /// The number of seconds between each tip being displayed when the round is not actively going + /// (i.e. postround or lobby) + /// + public static readonly CVarDef TipFrequencyOutOfRound = + CVarDef.Create("tips.out_of_game_frequency", 60f * 2); + + /// + /// The number of seconds between each tip being displayed when the round is actively going + /// + public static readonly CVarDef TipFrequencyInRound = + CVarDef.Create("tips.in_game_frequency", 60f * 30); + /* * Console */ diff --git a/Resources/Locale/en-US/tips/tips-system.ftl b/Resources/Locale/en-US/tips/tips-system.ftl new file mode 100644 index 0000000000..5ae3ce9d83 --- /dev/null +++ b/Resources/Locale/en-US/tips/tips-system.ftl @@ -0,0 +1 @@ +tips-system-chat-message-wrap = Tip: {$tip} diff --git a/Resources/Prototypes/Datasets/tips.yml b/Resources/Prototypes/Datasets/tips.yml new file mode 100644 index 0000000000..63ae6c140e --- /dev/null +++ b/Resources/Prototypes/Datasets/tips.yml @@ -0,0 +1,22 @@ +- type: dataset + id: Tips + values: + - "If you're on fire, you can click the alert on the right of your screen to stop, drop, and roll." + - "You can view and edit all keybindings used ingame at any time through the Options menu." + - "You can access the ingame guidebook through the escape menu, or by pressing Numpad 0 by default." + - "Some entities ingame have guidebook entries associated with them, which you can view by examining the entity and clicking the question mark icon." + - "You can make a rough examination of someone's health by examining them and clicking the plus icon." + - "Artifacts have the ability to gain permanent effects for some triggered nodes, including becoming an intercom or an extremely efficient generator." + - "You can avoid slipping on most puddles by walking. However, some strong chemicals like space lube will slip people anyway." + - "Botanists can mutate and crossbreed plants together to create more potent produce that also has higher yields." + - "Some plants, such as galaxythistle, can be ground up into extremely useful and potent medicines." + - "Mopping up puddles and draining them into other containers conserves the reagents found in the puddle." + - "Floor drains, usually found in the chef's freezer or janitor's office, rapidly consume reagent found in puddles around them--including blood." + - "Detectives can chase criminals more effectively by using fingerprint and fiber data obtained from forensic scans of objects the perpetrator likely interacted with." + - "Cognizine, a hard to manufacture chemical, makes animals sentient when they are injected with it." + - "Doctors can create vaccines for diseases using swabs from diseased victims." + - "Loaded mousetraps are incredibly effective at dealing with all manner of low-mass mobs--including Rat Servants." + - "Fire extinguishers can be loaded with any reagent in the game." + - "Some reagents, like chlorine trifluoride, have unique effects when applied by touch, such as through a spray bottle or foam." + - "Most machines in the game can be upgraded with high-level parts from Research and Development to great effect." + - "Remember to touch grass in between playing Space Station 14 every once in a while."