From 19a10d2495a5eb5ac00f8d8fbb65b1dba7c302c2 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 11 Mar 2026 20:21:24 -0700
Subject: [PATCH] Added Steam game recording timeline features
---
TODO | 1 -
game/game.cpp | 4 ++
game/main.cpp | 2 +
game/make.cpp | 8 +++-
game/player.cpp | 1 +
game/steam.cpp | 110 ++++++++++++++++++++++++++++++++++++++++++++++++
game/steam.h | 20 +++++++++
7 files changed, 143 insertions(+), 3 deletions(-)
diff --git a/TODO b/TODO
index a995f1e6..35279a22 100644
--- a/TODO
+++ b/TODO
@@ -1,5 +1,4 @@
* Add Steam achievements
-* Add Steam game recording highlights
* Add Steam Remote Play touch control layout
* Improve touch control (automatically show controls on tap)
* Add Android build to GitHub actions
diff --git a/game/game.cpp b/game/game.cpp
index 8d42445d..55ad52bd 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -190,6 +190,8 @@ GamePanelDelegate::OnShow()
{
int i;
+ SetSteamTimelineMode(STEAM_TIMELINE_PLAYING);
+
/* Initialize some game variables */
gGameOn = 1;
gPaused = 0;
@@ -1050,6 +1052,8 @@ GamePanelDelegate::NextWave()
MakeLargeRock(x, y);
}
+ SetSteamTimelineLevelStarted(gWave);
+
} /* -- NextWave */
/* ----------------------------------------------------------------- */
diff --git a/game/main.cpp b/game/main.cpp
index 3557dac6..e508bd1c 100644
--- a/game/main.cpp
+++ b/game/main.cpp
@@ -275,6 +275,8 @@ MainPanelDelegate::OnLoad()
void
MainPanelDelegate::OnShow()
{
+ SetSteamTimelineMode(STEAM_TIMELINE_MENUS);
+
gUpdateBuffer = true;
}
diff --git a/game/make.cpp b/game/make.cpp
index 1601bac4..1006ecc1 100644
--- a/game/make.cpp
+++ b/game/make.cpp
@@ -45,6 +45,8 @@ void MakeEnemy(void)
gSprites[newsprite] = new LittleShinobi(x, y);
else
gSprites[newsprite] = new BigShinobi(x, y);
+
+ SetSteamTimelineEvent(STEAM_TIMELINE_EVENT_ENEMY);
} /* -- MakeEnemy */
@@ -88,7 +90,6 @@ void MakePrize(void)
newsprite = gNumSprites;
gSprites[newsprite] = new Prize(x, y, xVel, yVel);
}
- return;
} /* -- MakePrize */
@@ -107,7 +108,6 @@ void MakeMultiplier(void)
newsprite = gNumSprites;
gSprites[newsprite] = new Multiplier(x, y, FastRandom(4)+2);
- return;
} /* -- MakeMultiplier */
@@ -153,6 +153,8 @@ void MakeNova(void)
newsprite = gNumSprites;
gSprites[newsprite] = new Nova(x, y);
+
+ SetSteamTimelineEvent(STEAM_TIMELINE_EVENT_NOVA);
} /* -- MakeNova */
@@ -284,6 +286,7 @@ void MakeGravity(void)
newsprite = gNumSprites;
gSprites[newsprite] = new Gravity(x, y);
}
+ SetSteamTimelineEvent(STEAM_TIMELINE_EVENT_GRAVITY);
} /* -- MakeGravity */
@@ -328,6 +331,7 @@ void MakeHoming(void)
newsprite = gNumSprites;
gSprites[newsprite] = new Homing(x, y, xVel, yVel);
}
+ SetSteamTimelineEvent(STEAM_TIMELINE_EVENT_MINE);
} /* -- MakeHoming */
diff --git a/game/player.cpp b/game/player.cpp
index 66914dbc..b9235792 100644
--- a/game/player.cpp
+++ b/game/player.cpp
@@ -352,6 +352,7 @@ Player::Explode(void)
Set_Blit(gShipExplosion);
Set_TTL(myblit->numFrames*phasetime);
ExplodeSound();
+ SetSteamTimelineEvent(STEAM_TIMELINE_EVENT_DEATH);
return(0);
}
diff --git a/game/steam.cpp b/game/steam.cpp
index 0da74810..71adfddb 100644
--- a/game/steam.cpp
+++ b/game/steam.cpp
@@ -44,6 +44,10 @@ class SteamInterface
bool Init();
void Quit();
+ void SetSteamTimelineMode(STEAM_TIMELINE_MODE mode);
+ void SetSteamTimelineLevelStarted(int level);
+ void SetSteamTimelineEvent(STEAM_TIMELINE_EVENT event);
+
void Update();
Uint8 GetControlForSession(RemotePlaySessionID_t sessionID);
@@ -74,6 +78,7 @@ class SteamInterface
private:
bool m_initialized = false;
+ STEAM_TIMELINE_MODE m_gameMode = STEAM_TIMELINE_NONE;
CSteamID m_steamID;
array<RemoteSession_t *> m_sessions;
@@ -99,6 +104,8 @@ bool SteamInterface::Init()
m_steamID = SteamUser()->GetSteamID();
SteamController()->Init();
+ SetSteamTimelineMode(STEAM_TIMELINE_LOADING);
+
return true;
}
@@ -120,6 +127,82 @@ void SteamInterface::Quit()
m_initialized = false;
}
+void SteamInterface::SetSteamTimelineMode(STEAM_TIMELINE_MODE mode)
+{
+ if (!m_initialized) {
+ return;
+ }
+
+ if (mode != m_gameMode) {
+ switch (mode) {
+ case STEAM_TIMELINE_LOADING:
+ SteamTimeline()->SetTimelineGameMode(k_ETimelineGameMode_LoadingScreen);
+ break;
+ case STEAM_TIMELINE_MENUS:
+ SteamTimeline()->ClearTimelineTooltip(0.0f);
+ SteamTimeline()->SetTimelineGameMode(k_ETimelineGameMode_Menus);
+ break;
+ case STEAM_TIMELINE_PLAYING:
+ SteamTimeline()->SetTimelineGameMode(k_ETimelineGameMode_Playing);
+ break;
+ default:
+ break;
+ }
+ m_gameMode = mode;
+ }
+}
+
+void SteamInterface::SetSteamTimelineLevelStarted(int level)
+{
+ if (!m_initialized) {
+ return;
+ }
+
+ char icon[32];
+ SDL_snprintf(icon, sizeof(icon), "steam_%d", level);
+
+ char wave[32];
+ SDL_snprintf(wave, sizeof(wave), "Wave %d", level);
+
+ SteamTimeline()->AddInstantaneousTimelineEvent("Next Wave", nullptr, icon, 0, 0.0f, k_ETimelineEventClipPriority_None);
+ SteamTimeline()->SetTimelineTooltip(wave, 0.0f);
+}
+
+void SteamInterface::SetSteamTimelineEvent(STEAM_TIMELINE_EVENT event)
+{
+ if (!m_initialized) {
+ return;
+ }
+
+ const char *title = nullptr;
+ const char *icon = nullptr;
+ switch (event) {
+ case STEAM_TIMELINE_EVENT_DEATH:
+ title = "Death";
+ icon = "steam_death";
+ break;
+ case STEAM_TIMELINE_EVENT_ENEMY:
+ title = "Aliens";
+ icon = "steam_caution";
+ break;
+ case STEAM_TIMELINE_EVENT_MINE:
+ title = "Homing Mine";
+ icon = "steam_caution";
+ break;
+ case STEAM_TIMELINE_EVENT_GRAVITY:
+ title = "Gravity Well";
+ icon = "steam_caution";
+ break;
+ case STEAM_TIMELINE_EVENT_NOVA:
+ title = "Supernova";
+ icon = "steam_explosion";
+ break;
+ default:
+ break;
+ }
+ SteamTimeline()->AddInstantaneousTimelineEvent(title, nullptr, icon, 0, 0.0f, k_ETimelineEventClipPriority_Standard);
+}
+
void SteamInterface::Update()
{
if (!m_initialized) {
@@ -425,6 +508,21 @@ void DisableRemoteInput()
steam.DisableRemoteInput();
}
+void SetSteamTimelineMode(STEAM_TIMELINE_MODE mode)
+{
+ steam.SetSteamTimelineMode(mode);
+}
+
+void SetSteamTimelineLevelStarted(int level)
+{
+ steam.SetSteamTimelineLevelStarted(level);
+}
+
+void SetSteamTimelineEvent(STEAM_TIMELINE_EVENT event)
+{
+ steam.SetSteamTimelineEvent(event);
+}
+
void UpdateSteam()
{
steam.Update();
@@ -475,6 +573,18 @@ void DisableRemoteInput()
{
}
+void SetSteamTimelineMode(STEAM_TIMELINE_MODE mode)
+{
+}
+
+void SetSteamTimelineLevelStarted(int level)
+{
+}
+
+void SetSteamTimelineEvent(STEAM_TIMELINE_EVENT event)
+{
+}
+
void UpdateSteam()
{
}
diff --git a/game/steam.h b/game/steam.h
index 47d8e79d..966ad9f6 100644
--- a/game/steam.h
+++ b/game/steam.h
@@ -25,6 +25,23 @@
#define SDL_EVENT_REMOTE_PLAYERS_CHANGED (SDL_EVENT_USER + 0)
#define SDL_EVENT_REMOTE_INPUT (SDL_EVENT_USER + 1)
+enum STEAM_TIMELINE_MODE
+{
+ STEAM_TIMELINE_NONE,
+ STEAM_TIMELINE_LOADING,
+ STEAM_TIMELINE_MENUS,
+ STEAM_TIMELINE_PLAYING
+};
+
+enum STEAM_TIMELINE_EVENT
+{
+ STEAM_TIMELINE_EVENT_DEATH,
+ STEAM_TIMELINE_EVENT_ENEMY,
+ STEAM_TIMELINE_EVENT_MINE,
+ STEAM_TIMELINE_EVENT_GRAVITY,
+ STEAM_TIMELINE_EVENT_NOVA,
+};
+
typedef Uint32 RemotePlaySessionID_t;
extern bool InitSteam();
@@ -35,6 +52,9 @@ extern SDL_Surface* GetRemotePlayerAvatar(Uint8 controlType);
extern const bool *GetRemotePlayerKeyboardState(Uint8 controlType);
extern void EnableRemoteInput();
extern void DisableRemoteInput();
+extern void SetSteamTimelineMode(STEAM_TIMELINE_MODE mode);
+extern void SetSteamTimelineLevelStarted(int level);
+extern void SetSteamTimelineEvent(STEAM_TIMELINE_EVENT event);
extern void UpdateSteam();
extern void QuitSteam();