/* =========================================================================== Copyright (C) 2000 - 2013, Raven Software, Inc. Copyright (C) 2001 - 2013, Activision, Inc. Copyright (C) 2013 - 2015, OpenJK contributors This file is part of the OpenJK source code. OpenJK is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation. 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, see . =========================================================================== */ #include #include "g_local.h" #include "../Rufl/hstring.h" #include "qcommon/ojk_saved_game_helper.h" #define MAX_GTIMERS 16384 typedef struct gtimer_s { hstring id; // Use handle strings, so that things work after loading int time; struct gtimer_s *next; // In either free list or current list } gtimer_t; gtimer_t g_timerPool[ MAX_GTIMERS ]; gtimer_t *g_timers[ MAX_GENTITIES ]; gtimer_t *g_timerFreeList; static int TIMER_GetCount(int num) { gtimer_t *p = g_timers[num]; int count = 0; while (p) { count++; p = p->next; } return count; } /* ------------------------- TIMER_RemoveHelper Scans an entities timer list to remove a given timer from the list and put it on the free list Doesn't do much error checking, only called below ------------------------- */ static void TIMER_RemoveHelper( int num, gtimer_t *timer ) { gtimer_t *p = g_timers[num]; // Special case: first timer in list if (p == timer) { g_timers[num] = g_timers[num]->next; p->next = g_timerFreeList; g_timerFreeList = p; return; } // Find the predecessor while (p->next != timer) { p = p->next; } // Rewire p->next = p->next->next; timer->next = g_timerFreeList; g_timerFreeList = timer; return; } /* ------------------------- TIMER_Clear ------------------------- */ void TIMER_Clear( void ) { int i; for (i = 0; i < MAX_GENTITIES; i++) { g_timers[i] = NULL; } for (i = 0; i < MAX_GTIMERS - 1; i++) { g_timerPool[i].next = &g_timerPool[i+1]; } g_timerPool[MAX_GTIMERS-1].next = NULL; g_timerFreeList = &g_timerPool[0]; } /* ------------------------- TIMER_Clear ------------------------- */ void TIMER_Clear( int idx ) { // rudimentary safety checks, might be other things to check? if ( idx >= 0 && idx < MAX_GENTITIES ) { gtimer_t *p = g_timers[idx]; // No timers at all -> do nothing if (!p) { return; } // Find the end of this ents timer list while (p->next) { p = p->next; } // Splice the lists p->next = g_timerFreeList; g_timerFreeList = g_timers[idx]; g_timers[idx] = NULL; return; } } /* ------------------------- TIMER_Save ------------------------- */ void TIMER_Save( void ) { int j; gentity_t *ent; ojk::SavedGameHelper saved_game( ::gi.saved_game); for ( j = 0, ent = &g_entities[0]; j < MAX_GENTITIES; j++, ent++ ) { unsigned char numTimers = TIMER_GetCount(j); if ( !ent->inuse && numTimers) { // Com_Printf( "WARNING: ent with timers not inuse\n" ); assert(numTimers); TIMER_Clear( j ); numTimers = 0; } //Write out the timer information saved_game.write_chunk( INT_ID('T', 'I', 'M', 'E'), numTimers); gtimer_t *p = g_timers[j]; assert ((numTimers && p) || (!numTimers && !p)); while(p) { const char *timerID = p->id.c_str(); const int length = strlen(timerID) + 1; const int time = p->time - level.time; //convert this back to delta so we can use SET after loading assert( length < 1024 );//This will cause problems when loading the timer if longer //Write out the id string saved_game.write_chunk( INT_ID('T', 'M', 'I', 'D'), timerID, length); //Write out the timer data saved_game.write_chunk( INT_ID('T', 'D', 'T', 'A'), time); p = p->next; } } } /* ------------------------- TIMER_Load ------------------------- */ void TIMER_Load( void ) { int j; gentity_t *ent; ojk::SavedGameHelper saved_game( ::gi.saved_game); for ( j = 0, ent = &g_entities[0]; j < MAX_GENTITIES; j++, ent++ ) { unsigned char numTimers = 0; saved_game.read_chunk( INT_ID('T', 'I', 'M', 'E'), numTimers); //Read back all entries for ( int i = 0; i < numTimers; i++ ) { int time = 0; char tempBuffer[1024]; // Still ugly. Setting ourselves up for 007 AUF all over again. =) assert (sizeof(g_timers[0]->time) == sizeof(time) );//make sure we're reading the same size as we wrote //Read the id string and time saved_game.read_chunk( INT_ID('T', 'M', 'I', 'D')); const char* sg_buffer_data = static_cast( saved_game.get_buffer_data()); int sg_buffer_size = saved_game.get_buffer_size(); if (sg_buffer_size < 0 || static_cast(sg_buffer_size) >= sizeof(tempBuffer)) { ::G_Error("invalid length for TMID string in saved game: %d\n", sg_buffer_size); } std::uninitialized_copy_n( sg_buffer_data, sg_buffer_size, tempBuffer); tempBuffer[sg_buffer_size] = '\0'; saved_game.read_chunk( INT_ID('T', 'D', 'T', 'A'), time); //this is odd, we saved all the timers in the autosave, but not all the ents are spawned yet from an auto load, so skip it if (ent->inuse) { //Restore it TIMER_Set(ent, tempBuffer, time); } } } } static gtimer_t *TIMER_GetNew(int num, const char *identifier) { assert(num < ENTITYNUM_MAX_NORMAL);//don't want timers on NONE or the WORLD gtimer_t *p = g_timers[num]; // Search for an existing timer with this name while (p) { if (p->id == identifier) { // Found it return p; } p = p->next; } // No existing timer with this name was found, so grab one from the free list if (!g_timerFreeList) {//oh no, none free! assert(g_timerFreeList); return NULL; } p = g_timerFreeList; g_timerFreeList = g_timerFreeList->next; p->next = g_timers[num]; g_timers[num] = p; return p; } gtimer_t *TIMER_GetExisting(int num, const char *identifier) { gtimer_t *p = g_timers[num]; while (p) { if (p->id == identifier) { // Found it return p; } p = p->next; } return NULL; } /* ------------------------- TIMER_Set ------------------------- */ void TIMER_Set( gentity_t *ent, const char *identifier, int duration ) { assert(ent->inuse); gtimer_t *timer = TIMER_GetNew(ent->s.number, identifier); if (timer) { timer->id = identifier; timer->time = level.time + duration; } } /* ------------------------- TIMER_Get ------------------------- */ int TIMER_Get( gentity_t *ent, const char *identifier ) { gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier); if (!timer) { return -1; } return timer->time; } /* ------------------------- TIMER_Done ------------------------- */ qboolean TIMER_Done( gentity_t *ent, const char *identifier ) { gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier); if (!timer) { return qtrue; } return (qboolean)(timer->time < level.time); } /* ------------------------- TIMER_Done2 Returns false if timer has been started but is not done...or if timer was never started ------------------------- */ qboolean TIMER_Done2( gentity_t *ent, const char *identifier, qboolean remove ) { gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier); qboolean res; if (!timer) { return qfalse; } res = (qboolean)(timer->time < level.time); if (res && remove) { // Put it back on the free list TIMER_RemoveHelper(ent->s.number, timer); } return res; } /* ------------------------- TIMER_Exists ------------------------- */ qboolean TIMER_Exists( gentity_t *ent, const char *identifier ) { return (qboolean)(TIMER_GetExisting(ent->s.number, identifier) != NULL); } /* ------------------------- TIMER_Remove Utility to get rid of any timer ------------------------- */ void TIMER_Remove( gentity_t *ent, const char *identifier ) { gtimer_t *timer = TIMER_GetExisting(ent->s.number, identifier); if (!timer) { return; } // Put it back on the free list TIMER_RemoveHelper(ent->s.number, timer); } /* ------------------------- TIMER_Start ------------------------- */ qboolean TIMER_Start( gentity_t *self, const char *identifier, int duration ) { if ( TIMER_Done( self, identifier ) ) { TIMER_Set( self, identifier, duration ); return qtrue; } return qfalse; }