Skip to content

Commit

Permalink
Introduce API events & functions to work w/replays
Browse files Browse the repository at this point in the history
- Add the following new event types:
  - `bz_eReplayRequestedEvent`
  - `bz_eReplayLoadedEvent`
  - `bz_eRecordingStartedEvent`
  - `bz_eRecordingEndedEvent`
- Add the following new API functions:
  - `bz_isReplayServer()`
  - `bz_loadReplay(const char* _filename, int playerIndex)`
  - `bz_replayExists(const char* _filename)`
  - `bz_unloadReplay(int playerIndex)`
  - `bz_makeApiTime(time_t time, bz_Time* apiTime)`
  • Loading branch information
allejo committed Jun 18, 2024
1 parent 2711356 commit 1f403dc
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 47 deletions.
99 changes: 84 additions & 15 deletions include/bzfsAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,10 @@ typedef enum
bz_ePermissionModificationEvent,
bz_eAllowServerShotFiredEvent,
bz_ePlayerDeathFinalizedEvent,
bz_eReplayRequestedEvent,
bz_eReplayLoadedEvent,
bz_eRecordingStartedEvent,
bz_eRecordingEndedEvent,
bz_eLastEvent //this is never used as an event, just show it's the last one
} bz_eEventType;

Expand Down Expand Up @@ -489,6 +493,23 @@ typedef struct bz_PlayerUpdateState

BZF_API bool bz_freePlayerRecord ( bz_BasePlayerRecord *playerRecord );

// Time utilities
typedef struct
{
int year;
int month;
int day;
int hour;
int minute;
int second;
int dayofweek;
bool daylightSavings;
} bz_Time;

BZF_API void bz_getLocaltime(bz_Time *ts);
BZF_API void bz_getUTCtime(bz_Time *ts);
BZF_API void bz_makeApiTime(time_t time, bz_Time* apiTime);

// event data types
class BZF_API bz_EventData
{
Expand Down Expand Up @@ -1465,6 +1486,65 @@ class BZF_API bz_PermissionModificationData_V1 : public bz_EventData
bool customPerm;
};

class BZF_API bz_RecordingStartedEventData_V1 : public bz_EventData
{
public:
bz_RecordingStartedEventData_V1() : bz_EventData(bz_eRecordingStartedEvent)
, playerID(-1)
{}

int playerID;
};

class BZF_API bz_RecordingEndedEventData_V1 : public bz_EventData
{
public:
bz_RecordingEndedEventData_V1() : bz_EventData(bz_eRecordingEndedEvent)
, playerID(-1)
{}

int playerID;
};

class BZF_API bz_ReplayRequestedEventData_V1 : public bz_EventData
{
public:
bz_ReplayRequestedEventData_V1() : bz_EventData(bz_eReplayRequestedEvent)
, playerID(-1)
, success(true)
, errorMsg("")
, filename("")
{}

int playerID;
bool success;
const char* errorMsg;
const char* filename;
};

class BZF_API bz_ReplayLoadedEventData_V1 : public bz_EventData
{
public:
bz_ReplayLoadedEventData_V1() : bz_EventData(bz_eReplayLoadedEvent)
, filename("")
, authorCallsign("")
, authorMotto("")
, serverVersion("")
, seconds(-1.0)
, start(NULL)
, end(NULL)
{}

const char* filename;
const char* authorCallsign;
const char* authorMotto;
const char* protocol;
const char* serverVersion;
float seconds;
bz_Time* start;
bz_Time* end;
};

// logging
BZF_API void bz_debugMessage ( int debugLevel, const char* message );
BZF_API void bz_debugMessagef( int debugLevel, const char* fmt, ... );
Expand Down Expand Up @@ -1742,21 +1822,6 @@ BZF_API uint32_t bz_getShotGUID (int fromPlayer, int shotID);
BZF_API bool bz_vectorFromPoints(const float p1[3], const float p2[3], float outVec[3]);
BZF_API bool bz_vectorFromRotations(const float tilt, const float rotation, float outVec[3]);

typedef struct
{
int year;
int month;
int day;
int hour;
int minute;
int second;
int dayofweek;
bool daylightSavings;
} bz_Time;

BZF_API void bz_getLocaltime(bz_Time *ts);
BZF_API void bz_getUTCtime(bz_Time *ts);

// BZDB API
BZF_API double bz_getBZDBDouble ( const char* variable );
BZF_API bz_ApiString bz_getBZDBString( const char* variable );
Expand Down Expand Up @@ -2145,6 +2210,10 @@ BZF_API bz_ApiString bz_filterPath ( const char* path );
BZF_API bool bz_saveRecBuf( const char * _filename, int seconds = 0);
BZF_API bool bz_startRecBuf( void );
BZF_API bool bz_stopRecBuf( void );
BZF_API bool bz_isReplayServer( void );
BZF_API bool bz_loadReplay( const char* _filename, int playerIndex = BZ_SERVERPLAYER );
BZF_API bool bz_replayExists( const char* _filename );
BZF_API bool bz_unloadReplay( int playerIndex = BZ_SERVERPLAYER );

// cheap Text Utils
BZF_API const char *bz_format(const char* fmt, ...);
Expand Down
155 changes: 123 additions & 32 deletions src/bzfs/RecordReplay.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
#include "StateDatabase.h"
#include "DirectoryNames.h"
#include "NetHandler.h"
#include "WorldEventManager.h"
#include "md5.h"
#include "Score.h"
#include "version.h"
Expand Down Expand Up @@ -249,6 +250,11 @@ bool Record::start(int playerIndex)
saveStates();
sendMessage(ServerPlayer, playerIndex, "Recording started");

bz_RecordingStartedEventData_V1 eventData;
eventData.playerID = playerIndex;

worldEventManager.callEvents(bz_eRecordingStartedEvent, &eventData);

return true;
}

Expand All @@ -261,6 +267,11 @@ bool Record::stop(int playerIndex)
return false;
}

bz_RecordingEndedEventData_V1 eventData;
eventData.playerID = playerIndex;

worldEventManager.callEvents(bz_eRecordingEndedEvent, &eventData);

sendMessage(ServerPlayer, playerIndex, "Recording stopped");

recordReset();
Expand Down Expand Up @@ -748,32 +759,40 @@ bool Replay::loadFile(int playerIndex, const char *filename)
std::string name = RecordDir;
name += filename;

replayReset();
resetStates();
unloadFile(playerIndex);

bz_ReplayRequestedEventData_V1 requestedEventData;
requestedEventData.filename = filename;
requestedEventData.playerID = playerIndex;

ReplayFile = openFile(filename, "rb");

if (ReplayFile == NULL)
{
snprintf(buffer, MessageLen, "Could not open: %s", name.c_str());
sendMessage(ServerPlayer, playerIndex, buffer);
return false;
requestedEventData.success = false;
}

if (!loadHeader(&header, ReplayFile))
else if (!loadHeader(&header, ReplayFile))
{
snprintf(buffer, MessageLen, "Could not open header: %s", name.c_str());
sendMessage(ServerPlayer, playerIndex, buffer);
fclose(ReplayFile);
ReplayFile = NULL;
return false;
requestedEventData.success = false;
}

if (header.magic != ReplayMagic)
else if (header.magic != ReplayMagic)
{
snprintf(buffer, MessageLen, "Not a bzflag replay file: %s", name.c_str());
sendMessage(ServerPlayer, playerIndex, buffer);
fclose(ReplayFile);
ReplayFile = NULL;
requestedEventData.success = false;
}

if (!requestedEventData.success)
{
sendMessage(ServerPlayer, playerIndex, requestedEventData.errorMsg = buffer);

worldEventManager.callEvents(bz_eReplayRequestedEvent, &requestedEventData);

return false;
}

Expand All @@ -790,51 +809,74 @@ bool Replay::loadFile(int playerIndex, const char *filename)
if (ReplayBuf.tail == NULL)
{
snprintf(buffer, MessageLen, "No valid data: %s", name.c_str());
sendMessage(ServerPlayer, playerIndex, buffer);
replayReset();
return false;

requestedEventData.success = false;
}
else
{
ReplayPos = ReplayBuf.tail; // setup the initial position
ReplayFileTime = header.filetime;
ReplayStartTime = ReplayPos->timestamp;

ReplayPos = ReplayBuf.tail; // setup the initial position
ReplayFileTime = header.filetime;
ReplayStartTime = ReplayPos->timestamp;
if (!preloadVariables())
{
snprintf(buffer, MessageLen, "Could not preload variables: %s", name.c_str());
replayReset();

if (!preloadVariables())
{
snprintf(buffer, MessageLen, "Could not preload variables: %s",
name.c_str());
sendMessage(ServerPlayer, playerIndex, buffer);
replayReset();
return false;
requestedEventData.success = false;
}
}

if (strlen(requestedEventData.errorMsg = buffer) > 0)
sendMessage(ServerPlayer, playerIndex, requestedEventData.errorMsg);

worldEventManager.callEvents(bz_eReplayRequestedEvent, &requestedEventData);

if (!requestedEventData.success)
return false;

ReplayFilename = filename;

snprintf(buffer, MessageLen, "Loaded file: %s", name.c_str());
bz_ReplayLoadedEventData_V1 loadedEventData;
loadedEventData.filename = name.c_str();
loadedEventData.authorCallsign = header.callSign;
loadedEventData.authorMotto = header.motto;
loadedEventData.protocol = header.ServerVersion;
loadedEventData.serverVersion = header.appVersion;
loadedEventData.seconds = (float)header.filetime / 1000000.0f;

snprintf(buffer, MessageLen, "Loaded file: %s", loadedEventData.filename);
sendMessage(ServerPlayer, playerIndex, buffer);
snprintf(buffer, MessageLen, " author: %s (%.79s)",
header.callSign, header.motto);
snprintf(buffer, MessageLen, " author: %s (%.79s)", loadedEventData.authorCallsign, loadedEventData.authorMotto);
sendMessage(ServerPlayer, playerIndex, buffer);
snprintf(buffer, MessageLen, " protocol: %.8s", header.ServerVersion);
snprintf(buffer, MessageLen, " protocol: %.8s", loadedEventData.protocol);
sendMessage(ServerPlayer, playerIndex, buffer);
snprintf(buffer, MessageLen, " server: %.113s", header.appVersion);
snprintf(buffer, MessageLen, " server: %.113s", loadedEventData.serverVersion);
sendMessage(ServerPlayer, playerIndex, buffer);
snprintf(buffer, MessageLen, " seconds: %.1f",
(float)header.filetime/1000000.0f);
snprintf(buffer, MessageLen, " seconds: %.1f", loadedEventData.seconds);
sendMessage(ServerPlayer, playerIndex, buffer);

time_t startTime = (time_t)(ReplayPos->timestamp / 1000000);
bz_makeApiTime(startTime, loadedEventData.start);
snprintf(buffer, MessageLen, " start: %s", ctime(&startTime));
sendMessage(ServerPlayer, playerIndex, buffer);

time_t endTime =
(time_t)((header.filetime + ReplayPos->timestamp) / 1000000);
time_t endTime = (time_t)((header.filetime + ReplayPos->timestamp) / 1000000);
bz_makeApiTime(endTime, loadedEventData.end);
snprintf(buffer, MessageLen, " end: %s", ctime(&endTime));
sendMessage(ServerPlayer, playerIndex, buffer);

worldEventManager.callEvents(bz_eReplayLoadedEvent, &loadedEventData);

return true;
}

bool Replay::unloadFile(int playerIndex)
{
return replayReset() && resetStates();
}


static FILE *getRecordFile(const char *filename)
{
Expand Down Expand Up @@ -1053,6 +1095,55 @@ bool Replay::sendFileList(int playerIndex, const char* options)
return true;
}

bool Replay::exists(const char* filename)
{
#ifndef _MSC_VER

DIR *dir;
struct dirent *de;

if (!makeDirExist(RecordDir.c_str()))
return false;

dir = opendir(RecordDir.c_str());
if (dir == NULL)
return false;

while ((de = readdir(dir)) != NULL)
{
if (strcmp(de->d_name, filename) == 0)
return true;
}

closedir(dir);

return false;

#else // _MSC_VER

if (!makeDirExist(RecordDir.c_str()))
return false;

std::string pattern = RecordDir;
pattern += "*";
WIN32_FIND_DATA findData;
HANDLE h = FindFirstFile(pattern.c_str(), &findData);
if (h != INVALID_HANDLE_VALUE)
{
do
{
if (strcmp(findData.cFileName, filename))
return true;
}
while (FindNextFile(h, &findData));

FindClose(h);
}

return false

#endif // _MSC_VER
}

bool Replay::play(int playerIndex)
{
Expand Down
1 change: 1 addition & 0 deletions src/bzfs/RecordReplay.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ extern bool init (); // must be done before any players join
extern bool kill ();

extern bool sendFileList (int playerIndex, const char* options);
extern bool exists(const char* filename);
extern bool loadFile (int playerIndex, const char *filename);
extern bool unloadFile (int playerIndex);
extern bool play (int playerIndex);
Expand Down
Loading

0 comments on commit 1f403dc

Please sign in to comment.