https://github.com/libsdl-org/Maelstrom/commit/8564da98ce28fa3a36e5d511e9888f83f7b017ee
From 8564da98ce28fa3a36e5d511e9888f83f7b017ee Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Sun, 20 Nov 2011 03:06:54 -0500
Subject: [PATCH] Added first pass at a replay system (it works!) :)
---
game/Maelstrom_Globals.h | 2 +
game/Makefile.am | 2 +
game/Makefile.in | 5 +-
game/game.cpp | 12 +++-
game/gameinfo.cpp | 17 ++++++
game/gameinfo.h | 2 +
game/packet.h | 16 ++++--
game/replay.cpp | 115 +++++++++++++++++++++++++++++++++++++++
game/replay.h | 71 ++++++++++++++++++++++++
9 files changed, 234 insertions(+), 8 deletions(-)
create mode 100644 game/replay.cpp
create mode 100644 game/replay.h
diff --git a/game/Maelstrom_Globals.h b/game/Maelstrom_Globals.h
index a44d2195..8bfd4fcb 100644
--- a/game/Maelstrom_Globals.h
+++ b/game/Maelstrom_Globals.h
@@ -38,6 +38,7 @@
#include "myerror.h"
#include "fastrand.h"
+#include "replay.h"
#include "scores.h"
#include "controls.h"
#include "gameinfo.h"
@@ -135,6 +136,7 @@ extern int gWave;
class Object;
extern Object *gEnemySprite;
extern int gWhenEnemy;
+extern Replay gReplay;
// in controls.cpp :
extern Controls controls;
diff --git a/game/Makefile.am b/game/Makefile.am
index 1b8bd0e4..89a513a7 100644
--- a/game/Makefile.am
+++ b/game/Makefile.am
@@ -43,6 +43,8 @@ libgame_a_SOURCES = \
protocol.h \
rect.cpp \
rect.h \
+ replay.cpp \
+ replay.h \
scores.cpp \
scores.h \
shinobi.h
diff --git a/game/Makefile.in b/game/Makefile.in
index 09650f80..6d03b94c 100644
--- a/game/Makefile.in
+++ b/game/Makefile.in
@@ -53,7 +53,7 @@ am_libgame_a_OBJECTS = MacDialog.$(OBJEXT) MaelstromUI.$(OBJEXT) \
load.$(OBJEXT) lobby.$(OBJEXT) main.$(OBJEXT) make.$(OBJEXT) \
myerror.$(OBJEXT) netplay.$(OBJEXT) object.$(OBJEXT) \
objects.$(OBJEXT) player.$(OBJEXT) rect.$(OBJEXT) \
- scores.$(OBJEXT)
+ replay.$(OBJEXT) scores.$(OBJEXT)
libgame_a_OBJECTS = $(am_libgame_a_OBJECTS)
DEFAULT_INCLUDES = -I.@am__isrc@
depcomp = $(SHELL) $(top_srcdir)/build-scripts/depcomp
@@ -220,6 +220,8 @@ libgame_a_SOURCES = \
protocol.h \
rect.cpp \
rect.h \
+ replay.cpp \
+ replay.h \
scores.cpp \
scores.h \
shinobi.h
@@ -289,6 +291,7 @@ distclean-compile:
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/objects.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/player.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rect.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/replay.Po@am__quote@
@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/scores.Po@am__quote@
.cpp.o:
diff --git a/game/game.cpp b/game/game.cpp
index 6c1a6b50..7518e1f4 100644
--- a/game/game.cpp
+++ b/game/game.cpp
@@ -31,6 +31,7 @@
// Global variables set in this file...
GameInfo gGameInfo;
+Replay gReplay;
int gScore;
int gGameOn;
int gPaused;
@@ -88,6 +89,9 @@ static bool SetupPlayers(void)
void NewGame(void)
{
+ /* Start the replay */
+ gReplay.HandleNewGame();
+
/* Start up the random number generator */
SeedRandom(gGameInfo.seed);
@@ -238,11 +242,15 @@ GamePanelDelegate::OnTick()
/* -- Read in keyboard input for our ship */
HandleEvents(0);
- if ( m_showingBonus ) {
+ if ( m_showingBonus || !gGameOn ) {
return;
}
/* -- Send Sync! signal to all players, and handle keyboard. */
+ if (!gReplay.HandlePlayback()) {
+ gGameOn = 0;
+ return;
+ }
if ( SyncNetwork() < 0 ) {
if ( gPaused & ~0x1 ) {
/* One of the other players is minimized and may not
@@ -254,6 +262,8 @@ GamePanelDelegate::OnTick()
gGameOn = 0;
return;
}
+ gReplay.HandleRecording();
+
OBJ_LOOP(i, MAX_PLAYERS) {
if (!gPlayers[i]->IsValid()) {
continue;
diff --git a/game/gameinfo.cpp b/game/gameinfo.cpp
index 2059b22a..3d9d6905 100644
--- a/game/gameinfo.cpp
+++ b/game/gameinfo.cpp
@@ -254,6 +254,23 @@ GameInfo::WriteToPacket(DynamicPacket &packet)
}
}
+void
+GameInfo::PrepareForReplay()
+{
+ // Clean up the game info for privacy and so replay works
+ gameID = localID;
+
+ SDL_zero(nodes);
+ numNodes = 0;
+
+ for (int i = 0; i < MAX_PLAYERS; ++i) {
+ if (IsValidPlayer(i)) {
+ players[i].nodeID = localID;
+ players[i].controlMask = CONTROL_REPLAY;
+ }
+ }
+}
+
bool
GameInfo::HasNode(Uint32 nodeID) const
{
diff --git a/game/gameinfo.h b/game/gameinfo.h
index 51d08cd9..42f04c88 100644
--- a/game/gameinfo.h
+++ b/game/gameinfo.h
@@ -115,6 +115,8 @@ class GameInfo
bool ReadFromPacket(DynamicPacket &packet);
void WriteToPacket(DynamicPacket &packet);
+ void PrepareForReplay();
+
int GetNumNodes() const {
return numNodes;
}
diff --git a/game/packet.h b/game/packet.h
index 1c27e08b..c358aa59 100644
--- a/game/packet.h
+++ b/game/packet.h
@@ -69,6 +69,9 @@ class DynamicPacket : public UDPpacket
int Tell() {
return pos;
}
+ int Size() {
+ return len;
+ }
void Write(Uint8 value) {
Grow(sizeof(value));
@@ -94,16 +97,17 @@ class DynamicPacket : public UDPpacket
amount = 255;
}
Write((Uint8)amount);
- Grow(amount);
- SDL_memcpy(&data[pos], value, amount);
- pos += amount;
+ Write(value, amount);
}
void Write(DynamicPacket &packet) {
size_t amount = packet.len - packet.pos;
- Grow(amount);
- SDL_memcpy(&data[pos], &packet.data[packet.pos], amount);
+ Write(&packet.data[packet.pos], amount);
packet.pos += amount;
- pos += amount;
+ }
+ void Write(const void *value, size_t size) {
+ Grow(size);
+ SDL_memcpy(&data[pos], value, size);
+ pos += size;
}
bool Read(Uint8 &value) {
diff --git a/game/replay.cpp b/game/replay.cpp
new file mode 100644
index 00000000..855de2fd
--- /dev/null
+++ b/game/replay.cpp
@@ -0,0 +1,115 @@
+/*
+ Maelstrom: Open Source version of the classic game by Ambrosia Software
+ Copyright (C) 1997-2011 Sam Lantinga
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ Sam Lantinga
+ slouken@libsdl.org
+*/
+
+#include "Maelstrom_Globals.h"
+#include "netplay.h"
+#include "replay.h"
+
+
+Replay::Replay()
+{
+ m_mode = REPLAY_IDLE;
+}
+
+void
+Replay::SetMode(REPLAY_MODE mode)
+{
+ m_mode = mode;
+}
+
+bool
+Replay::Load(const char *file)
+{
+ assert(!"Not yet implemented");
+ return false;
+}
+
+bool
+Replay::Save(const char *file)
+{
+ assert(!"Not yet implemented");
+ return false;
+}
+
+void
+Replay::HandleNewGame()
+{
+ if (m_mode == REPLAY_RECORDING) {
+ m_game.CopyFrom(gGameInfo);
+ m_game.PrepareForReplay();
+ } else if (m_mode == REPLAY_PLAYBACK) {
+ m_game.PrepareForReplay();
+ gGameInfo.localID = m_game.localID;
+ gGameInfo.CopyFrom(m_game);
+ }
+ m_data.Seek(0);
+}
+
+bool
+Replay::HandlePlayback()
+{
+ if (m_mode != REPLAY_PLAYBACK) {
+ return true;
+ }
+
+ // Check to make sure we haven't gotten a consistency error
+ Uint32 seed;
+ if (!m_data.Read(seed)) {
+ // That's it, end of recording
+ return false;
+ }
+ if (seed != GetRandSeed()) {
+ error("Error!! \a Frame consistency problem, aborting!!\r\n");
+ return false;
+ }
+
+ // Add the input for this frame
+ Uint16 size;
+ Uint8 value;
+ if (!m_data.Read(size) || (m_data.Size() < (int)size)) {
+ error("Error in replay, missing data\r\n");
+ return false;
+ }
+ while (size--) {
+ m_data.Read(value);
+ QueueInput(value);
+ }
+ return true;
+}
+
+void
+Replay::HandleRecording()
+{
+ if (m_mode != REPLAY_RECORDING) {
+ return;
+ }
+
+ // Get the input for this frame
+ Uint8 *data;
+ int len = GetSyncBuf(&data);
+
+ // Add it to our data buffer
+ m_data.Write(GetRandSeed());
+ assert(len <= 0xFFFF);
+ m_data.Write((Uint16)len);
+ m_data.Write(data, len);
+}
diff --git a/game/replay.h b/game/replay.h
new file mode 100644
index 00000000..eb948e6d
--- /dev/null
+++ b/game/replay.h
@@ -0,0 +1,71 @@
+/*
+ Maelstrom: Open Source version of the classic game by Ambrosia Software
+ Copyright (C) 1997-2011 Sam Lantinga
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
+ Sam Lantinga
+ slouken@libsdl.org
+*/
+
+#ifndef _replay_h
+#define _replay_h
+
+#include "gameinfo.h"
+#include "packet.h"
+
+// You should increment this every time game code changes in ways that would
+// affect the random number sequence for a game.
+//
+// Examples of this would be changing the game play area, game info structure,
+// game logic, etc.
+//
+#define REPLAY_VERSION 1
+
+enum REPLAY_MODE {
+ REPLAY_IDLE,
+ REPLAY_RECORDING,
+ REPLAY_PLAYBACK
+};
+
+class Replay
+{
+public:
+ Replay();
+
+ // You should never change this while the game is running
+ void SetMode(REPLAY_MODE mode);
+
+ bool IsPlaying() {
+ return m_mode == REPLAY_PLAYBACK;
+ }
+ bool IsRecording() {
+ return m_mode == REPLAY_RECORDING;
+ }
+
+ bool Load(const char *file);
+ bool Save(const char *file);
+
+ void HandleNewGame();
+ bool HandlePlayback();
+ void HandleRecording();
+
+protected:
+ REPLAY_MODE m_mode;
+ GameInfo m_game;
+ DynamicPacket m_data;
+};
+
+#endif // _replay_h