using Content.Server.Chat.Managers; using Content.Server.GameTicking; using Content.Shared.CCVar; using Content.Shared.Chat; using Content.Shared.Dataset; using Content.Shared.Tips; using Robust.Server.GameObjects; using Robust.Server.Player; using Robust.Shared.Configuration; using Robust.Shared.Console; using Robust.Shared.Player; using Robust.Shared.Prototypes; using Robust.Shared.Random; using Robust.Shared.Timing; 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!; [Dependency] private readonly IConsoleHost _conHost = 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); Subs.CVar(_cfg, CCVars.TipFrequencyOutOfRound, SetOutOfRound, true); Subs.CVar(_cfg, CCVars.TipFrequencyInRound, SetInRound, true); Subs.CVar(_cfg, CCVars.TipsEnabled, SetEnabled, true); Subs.CVar(_cfg, CCVars.TipsDataset, SetDataset, true); RecalculateNextTipTime(); _conHost.RegisterCommand("clippy", SendClippy); _conHost.RegisterCommand("tip", SendTip); } private void SendTip(IConsoleShell shell, string argstr, string[] args) { AnnounceRandomTip(); RecalculateNextTipTime(); } private void SendClippy(IConsoleShell shell, string argstr, string[] args) { if (args.Length < 2) { shell.WriteLine( "usage: clippy [entity prototype] [speak time] [slide time] [waddle]"); return; } ActorComponent? actor = null; if (args[0] != "broadcast" && args[0] != "all") { if (!EntityUid.TryParse(args[0], out var uid) || !TryComp(uid, out actor)) { shell.WriteError($"Could not find player {args[0]}"); return; } } var ev = new ClippyEvent(args[1]); string proto; if (args.Length > 2) { ev.Proto = args[2]; if (!_prototype.HasIndex(args[2])) { shell.WriteError($"Unknown prototype: {args[2]}"); return; } } if (args.Length > 3) ev.SpeakTime = float.Parse(args[3]); if (args.Length > 4) ev.SlideTime = float.Parse(args[4]); if (args.Length > 5) ev.WaddleInterval = float.Parse(args[5]); if (actor != null) RaiseNetworkEvent(ev, actor.PlayerSession); else RaiseNetworkEvent(ev); } 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 = $"Tippy the Clown says:\n\n{tip}"; var ev = new ClippyEvent(msg); ev.SpeakTime = 1 + tip.Length * 0.05f; RaiseNetworkEvent(ev); } 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(); } } }