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."