diff --git a/include/bzfsAPI.h b/include/bzfsAPI.h index 839d596dd4..993c708209 100644 --- a/include/bzfsAPI.h +++ b/include/bzfsAPI.h @@ -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; @@ -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 { @@ -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, ... ); @@ -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 ); @@ -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, ...); diff --git a/src/bzfs/RecordReplay.cxx b/src/bzfs/RecordReplay.cxx index f2838420f6..19bb841c0f 100644 --- a/src/bzfs/RecordReplay.cxx +++ b/src/bzfs/RecordReplay.cxx @@ -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" @@ -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; } @@ -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(); @@ -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; } @@ -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) { @@ -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) { diff --git a/src/bzfs/RecordReplay.h b/src/bzfs/RecordReplay.h index bf0a977d34..77843457d2 100644 --- a/src/bzfs/RecordReplay.h +++ b/src/bzfs/RecordReplay.h @@ -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); diff --git a/src/bzfs/bzfsAPI.cxx b/src/bzfs/bzfsAPI.cxx index 8bca408bc1..4654fa0fe3 100644 --- a/src/bzfs/bzfsAPI.cxx +++ b/src/bzfs/bzfsAPI.cxx @@ -1936,6 +1936,20 @@ BZF_API void bz_getUTCtime ( bz_Time *ts ) &ts->daylightSavings); } +BZF_API void bz_makeApiTime(time_t time, bz_Time* apiTime) +{ + tm *tsStruct = gmtime(&time); + + apiTime->year = tsStruct->tm_year; + apiTime->month = tsStruct->tm_mon; + apiTime->day = tsStruct->tm_mday; + apiTime->hour = tsStruct->tm_hour; + apiTime->minute = tsStruct->tm_min; + apiTime->second = tsStruct->tm_sec; + apiTime->dayofweek = tsStruct->tm_wday; + apiTime->daylightSavings = tsStruct->tm_isdst; +} + // info BZF_API double bz_getBZDBDouble ( const char* variable ) { @@ -4149,6 +4163,35 @@ BZF_API bool bz_stopRecBuf( void ) return Record::stop(ServerPlayer); } +BZF_API bool bz_isReplayServer( void ) +{ + return Replay::enabled(); +} + +BZF_API bool bz_loadReplay( const char* _filename, int playerIndex ) +{ + if (!Replay::enabled()) + return false; + + return Replay::loadFile(playerIndex, _filename); +} + +BZF_API bool bz_replayExists( const char* _filename ) +{ + if (!Replay::enabled()) + return false; + + return Replay::exists(_filename); +} + +BZF_API bool bz_unloadReplay( int playerIndex ) +{ + if (!Replay::enabled()) + return false; + + return Replay::unloadFile(playerIndex); +} + BZF_API const char *bz_format(const char* fmt, ...) { static std::string result;