diff -Naur openarena-engine-source-0.8.x-28/code//game/g_public.h openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//game/g_public.h --- openarena-engine-source-0.8.x-28/code//game/g_public.h 2011-12-24 12:29:40 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//game/g_public.h 2012-01-28 20:16:58 +0000 @@ -62,9 +62,9 @@ int svFlags; // SVF_NOCLIENT, SVF_BROADCAST, etc - // only send to this client when SVF_SINGLECLIENT is set + // only send to this client when SVF_SINGLECLIENT is set // if SVF_CLIENTMASK is set, use bitmask for clients to send to (maxclients must be <= 32, up to the mod to enforce this) - int singleClient; + int singleClient; qboolean bmodel; // if false, assume an explicit mins / maxs bounding box // only set by trap_SetBrushModel @@ -196,7 +196,7 @@ // if it is not passed to linkentity. If the size, position, or // solidity changes, it must be relinked. - G_UNLINKENTITY, // ( gentity_t *ent ); + G_UNLINKENTITY, // ( gentity_t *ent ); // call before removing an interactive entity G_ENTITIES_IN_BOX, // ( const vec3_t mins, const vec3_t maxs, gentity_t **list, int maxcount ); @@ -226,7 +226,7 @@ G_TRACECAPSULE, // ( trace_t *results, const vec3_t start, const vec3_t mins, const vec3_t maxs, const vec3_t end, int passEntityNum, int contentmask ); G_ENTITY_CONTACTCAPSULE, // ( const vec3_t mins, const vec3_t maxs, const gentity_t *ent ); - + // 1.32 G_FS_SEEK, @@ -387,7 +387,9 @@ BOTLIB_PC_LOAD_SOURCE, BOTLIB_PC_FREE_SOURCE, BOTLIB_PC_READ_TOKEN, - BOTLIB_PC_SOURCE_FILE_AND_LINE + BOTLIB_PC_SOURCE_FILE_AND_LINE, + + G_DEMO_COMMAND } gameImport_t; @@ -425,6 +427,7 @@ // The game can issue trap_argc() / trap_argv() commands to get the command // and parameters. Return qfalse if the game doesn't recognize it as a command. - BOTAI_START_FRAME // ( int time ); -} gameExport_t; + BOTAI_START_FRAME, // ( int time ); + GAME_DEMO_COMMAND // ( int cmd, const char *string ); +} gameExport_t; diff -Naur openarena-engine-source-0.8.x-28/code//qcommon/cmd.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/cmd.c --- openarena-engine-source-0.8.x-28/code//qcommon/cmd.c 2011-12-24 12:29:32 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/cmd.c 2012-01-31 23:11:05 +0000 @@ -39,6 +39,29 @@ byte cmd_text_buf[MAX_CMD_BUFFER]; +// Delay stuff + +#define MAX_DELAYED_COMMANDS 64 +#define CMD_DELAY_FRAME_FIRE 1 +#define CMD_DELAY_UNUSED 0 + +typedef enum +{ + CMD_DELAY_MSEC, + CMD_DELAY_FRAME +} cmdDelayType_t; + +typedef struct +{ + char name[MAX_CMD_LINE]; + char text[MAX_CMD_LINE]; + int delay; + cmdDelayType_t type; +} delayed_cmd_s; + +delayed_cmd_s delayed_cmd[MAX_DELAYED_COMMANDS]; + + //============================================================================= /* @@ -90,7 +113,7 @@ */ void Cbuf_AddText( const char *text ) { int l; - + l = strlen (text); if (cmd_text.cursize + l >= cmd_text.maxsize) @@ -203,10 +226,10 @@ if( i >= (MAX_CMD_LINE - 1)) { i = MAX_CMD_LINE - 1; } - + Com_Memcpy (line, text, i); line[i] = 0; - + // delete the text from the command buffer and move remaining commands down // this is necessary because commands (exec) can insert data at the // beginning of the text buffer @@ -222,7 +245,53 @@ // execute the command line - Cmd_ExecuteString (line); + Cmd_ExecuteString (line); + } +} + + +/* +============================================================================== + + COMMANDS DELAYING + +============================================================================== +*/ + +/* +=============== +Cdelay_Frame +=============== +*/ + +void Cdelay_Frame( void ) { + int i; + int sys_time = Sys_Milliseconds(); + qboolean run_it; + + for(i=0; (i (command)\nremoves all commands with in them.\nif (command) is specified, the removal will be limited only to delays whose commands contain (command).\n"); + return; + } + + find = Cmd_Argv(1); + limit = Cmd_Argv(2); + + for(i=0; (i \ndelay f \nexecutes after the delay\n"); + return; + } + + raw_delay = Cmd_Argv(1); + if(!isdigit(raw_delay[0])) + { + name = raw_delay; + raw_delay = Cmd_Argv(2); + cmd = Cmd_ArgsFrom(3); + } + else + { + name = ""; + cmd = Cmd_ArgsFrom(2); + } + delay = atoi(raw_delay); + + if(delay < 1) + { + Com_Printf ("delay: the delay must be a positive integer"); + return; + } + + //search for an unused slot + for(i=0; (i= cmd_argc ) { return ""; } - return cmd_argv[arg]; + return cmd_argv[arg]; } /* @@ -446,10 +681,10 @@ for(i = 1; i < cmd_argc; i++) { char *c = cmd_argv[i]; - + if(strlen(c) > MAX_CVAR_VALUE_STRING - 1) c[MAX_CVAR_VALUE_STRING - 1] = '\0'; - + while ((c = strpbrk(c, "\n\r;"))) { *c = ' '; ++c; @@ -484,7 +719,7 @@ if ( !text_in ) { return; } - + Q_strncpyz( cmd_cmd, text_in, sizeof(cmd_cmd) ); text = text_in; @@ -568,7 +803,7 @@ return; // all tokens parsed } } - + } /* @@ -596,7 +831,7 @@ */ void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) { cmd_function_t *cmd; - + // fail if the command already exists for ( cmd = cmd_functions ; cmd ; cmd=cmd->next ) { if ( !strcmp( cmd_name, cmd->name ) ) { @@ -667,7 +902,7 @@ */ void Cmd_CommandCompletion( void(*callback)(const char *s) ) { cmd_function_t *cmd; - + for (cmd=cmd_functions ; cmd ; cmd=cmd->next) { callback( cmd->name ); } @@ -696,16 +931,16 @@ A complete command line has been parsed, so try to execute it ============ */ -void Cmd_ExecuteString( const char *text ) { +void Cmd_ExecuteString( const char *text ) { cmd_function_t *cmd, **prev; // execute the command line - Cmd_TokenizeString( text ); + Cmd_TokenizeString( text ); if ( !Cmd_Argc() ) { return; // no tokens } - // check registered command functions + // check registered command functions for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) { cmd = *prev; if ( !Q_stricmp( cmd_argv[0],cmd->name ) ) { @@ -725,7 +960,7 @@ return; } } - + // check cvars if ( Cvar_Command() ) { return; @@ -790,6 +1025,34 @@ } /* +================== +Cmd_CompleteDelay +================== +*/ +void Cmd_CompleteDelay( char *args, int argNum ) +{ + if( argNum == 3 || argNum == 4 ) + { + // Skip "delay " + char *p = Com_SkipTokens( args, 1, " " ); + + if( p > args ) + Field_CompleteCommand( p, qtrue, qtrue ); + } +} + +/* +================== +Cmd_CompleteUnDelay +================== +*/ +void Cmd_CompleteUnDelay( char *args, int argNum ) { + if( argNum == 2 ) { + Field_CompleteDelay( ); + } +} + +/* ============ Cmd_Init ============ @@ -802,5 +1065,9 @@ Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName ); Cmd_AddCommand ("echo",Cmd_Echo_f); Cmd_AddCommand ("wait", Cmd_Wait_f); + Cmd_AddCommand ("delay", Cmd_Delay_f); + Cmd_SetCommandCompletionFunc( "delay", Cmd_CompleteDelay ); + Cmd_AddCommand ("undelay", Cmd_Undelay_f); + Cmd_SetCommandCompletionFunc( "undelay", Cmd_CompleteUnDelay ); + Cmd_AddCommand ("undelayAll", Cmd_UndelayAll_f); } - diff -Naur openarena-engine-source-0.8.x-28/code//qcommon/common.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/common.c --- openarena-engine-source-0.8.x-28/code//qcommon/common.c 2011-12-24 12:29:32 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/common.c 2012-01-31 23:16:04 +0000 @@ -160,7 +160,7 @@ } Q_strcat(rd_buffer, rd_buffersize, msg); // TTimo nooo .. that would defeat the purpose - //rd_flush(rd_buffer); + //rd_flush(rd_buffer); //*rd_buffer = 0; return; } @@ -186,11 +186,11 @@ newtime = localtime( &aclock ); logfile = FS_FOpenFileWrite( "qconsole.log" ); - + if(logfile) { Com_Printf( "logfile opened on %s\n", asctime( newtime ) ); - + if ( com_logfile->integer > 1 ) { // force it to not buffer so we get valid @@ -223,15 +223,15 @@ void QDECL Com_DPrintf( const char *fmt, ...) { va_list argptr; char msg[MAXPRINTMSG]; - + if ( !com_developer || !com_developer->integer ) { return; // don't confuse non-developers with techie stuff... } - va_start (argptr,fmt); + va_start (argptr,fmt); Q_vsnprintf (msg, sizeof(msg), fmt, argptr); va_end (argptr); - + Com_Printf ("%s", msg); } @@ -257,7 +257,7 @@ calledSysError = qtrue; Sys_Error("recursive error after: %s", com_errorMessage); } - + return; } @@ -776,7 +776,7 @@ */ void Z_ClearZone( memzone_t *zone, int size ) { memblock_t *block; - + // set the entire zone to one free block zone->blocklist.next = zone->blocklist.prev = block = @@ -787,7 +787,7 @@ zone->rover = block; zone->size = size; zone->used = 0; - + block->prev = block->next = &zone->blocklist; block->tag = 0; // free block block->id = ZONEID; @@ -820,7 +820,7 @@ void Z_Free( void *ptr ) { memblock_t *block, *other; memzone_t *zone; - + if (!ptr) { Com_Error( ERR_DROP, "Z_Free: NULL pointer" ); } @@ -855,7 +855,7 @@ Com_Memset( ptr, 0xaa, block->size - sizeof( *block ) ); block->tag = 0; // mark as free - + other = block->prev; if (!other->tag) { // merge with previous free block @@ -946,10 +946,10 @@ size += sizeof(memblock_t); // account for size of block header size += 4; // space for memory trash tester size = PAD(size, sizeof(intptr_t)); // align to 32/64 bit boundary - + base = rover = zone->rover; start = base->prev; - + do { if (rover == start) { #ifdef ZONE_DEBUG @@ -966,7 +966,7 @@ rover = rover->next; } } while (base->tag || base->size < size); - + // // found a block big enough // @@ -983,12 +983,12 @@ base->next = new; base->size = size; } - + base->tag = tag; // no longer a free block - + zone->rover = base->next; // next allocation will start looking here zone->used += base->size; // - + base->id = ZONEID; #ifdef ZONE_DEBUG @@ -1015,7 +1015,7 @@ void *Z_Malloc( int size ) { #endif void *buf; - + //Z_CheckHeap (); // DEBUG #ifdef ZONE_DEBUG @@ -1045,7 +1045,7 @@ */ void Z_CheckHeap( void ) { memblock_t *block; - + for (block = mainzone->blocklist.next ; ; block = block->next) { if (block->next == &mainzone->blocklist) { break; // all blocks have been hit @@ -1141,7 +1141,7 @@ { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'5', '\0'} }, { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'6', '\0'} }, { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'7', '\0'} }, - { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} }, + { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'8', '\0'} }, { {(sizeof(memstatic_t) + 3) & ~3, TAG_STATIC, NULL, NULL, ZONEID}, {'9', '\0'} } }; @@ -1272,7 +1272,7 @@ } if (block->next == &mainzone->blocklist) { - break; // all blocks have been hit + break; // all blocks have been hit } if ( (byte *)block + block->size != (byte *)block->next) { Com_Printf ("ERROR: block size does not touch the next block\n"); @@ -1294,7 +1294,7 @@ } if (block->next == &smallzone->blocklist) { - break; // all blocks have been hit + break; // all blocks have been hit } } @@ -1370,7 +1370,7 @@ } } if ( block->next == &mainzone->blocklist ) { - break; // all blocks have been hit + break; // all blocks have been hit } } @@ -1393,7 +1393,7 @@ Com_Error( ERR_FATAL, "Small zone data failed to allocate %1.1f megs", (float)s_smallZoneTotal / (1024*1024) ); } Z_ClearZone( smallzone, s_smallZoneTotal ); - + return; } @@ -1513,7 +1513,7 @@ // make sure the file system has allocated and "not" freed any temp blocks // this allows the config and product id files ( journal files too ) to be loaded - // by the file system without redunant routines in the file system utilizing different + // by the file system without redunant routines in the file system utilizing different // memory systems if (FS_LoadStack() != 0) { Com_Error( ERR_FATAL, "Hunk initialization failed. File system load stack not zero"); @@ -1755,7 +1755,7 @@ // return a Z_Malloc'd block if the hunk has not been initialized // this allows the config and product id files ( journal files too ) to be loaded - // by the file system without redunant routines in the file system utilizing different + // by the file system without redunant routines in the file system utilizing different // memory systems if ( s_hunkData == NULL ) { @@ -1803,7 +1803,7 @@ // free with Z_Free if the hunk has not been initialized // this allows the config and product id files ( journal files too ) to be loaded - // by the file system without redunant routines in the file system utilizing different + // by the file system without redunant routines in the file system utilizing different // memory systems if ( s_hunkData == NULL ) { @@ -2253,7 +2253,7 @@ Com_PushEvent( &ev ); } } while ( ev.evType != SE_NONE ); - + return ev.evTime; } @@ -2330,13 +2330,13 @@ if(argc > 2) { char *arg2 = Cmd_ArgsFrom(2); - + Sys_SetEnv(arg1, arg2); } else if(argc == 2) { char *env = getenv(arg1); - + if(env) Com_Printf("%s=%s\n", arg1, env); else @@ -2381,7 +2381,7 @@ if(!com_gameRestarting && com_fullyInitialized) { com_gameRestarting = qtrue; - + if(clientRestart) { CL_Disconnect(qfalse); @@ -2393,17 +2393,17 @@ SV_Shutdown("Game directory changed"); FS_Restart(checksumFeed); - + // Clean out any user and VM created cvars Cvar_Restart(qtrue); Com_ExecuteCfg(); - + // Restart sound subsystem so old handles are flushed CL_Snd_Restart(); if(clientRestart) CL_StartHunkUsers(qfalse); - + com_gameRestarting = qfalse; } } @@ -2627,7 +2627,7 @@ com_standalone = Cvar_Get("com_standalone", "0", CVAR_ROM); com_basegame = Cvar_Get("com_basegame", BASEGAME, CVAR_INIT); com_homepath = Cvar_Get("com_homepath", "", CVAR_INIT); - + if(!com_basegame->string[0]) Cvar_ForceReset("com_basegame"); @@ -2867,7 +2867,7 @@ } else if (com_cameraMode->integer) { msec *= com_timescale->value; } - + // don't let it scale below 1 msec if ( msec < 1 && com_timescale->value) { msec = 1; @@ -2881,7 +2881,7 @@ Com_Printf( "Hitch warning: %i msec frame time\n", msec ); clampTime = 5000; - } else + } else if ( !com_sv_running->integer ) { // clients of remote servers do not want to clamp time, because // it would skew their view of the server's time temporarily @@ -2910,13 +2910,13 @@ int msec, minMsec; int timeVal; static int lastTime = 0, bias = 0; - + int timeBeforeFirstEvents; int timeBeforeServer; int timeBeforeEvents; int timeBeforeClient; int timeAfter; - + if ( setjmp (abortframe) ) { return; // an ERR_DROP was thrown @@ -2929,7 +2929,7 @@ timeAfter = 0; // write config file if anything changed - Com_WriteConfiguration(); + Com_WriteConfiguration(); // // main event loop @@ -2953,13 +2953,13 @@ minMsec = 1000 / com_maxfps->integer; else minMsec = 1; - + timeVal = com_frameTime - lastTime; bias += timeVal - minMsec; - + if(bias > minMsec) bias = minMsec; - + // Adjust minMsec if previous frame took too long to render so // that framerate is stable at the requested value. minMsec -= bias; @@ -2978,20 +2978,21 @@ NET_Sleep(timeVal - 1); msec = Sys_Milliseconds() - com_frameTime; - + if(msec >= minMsec) timeVal = 0; else timeVal = minMsec - msec; } while(timeVal > 0); - + lastTime = com_frameTime; com_frameTime = Com_EventLoop(); - + msec = com_frameTime - lastTime; Cbuf_Execute (); + Cdelay_Frame (); if (com_altivec->modified) { @@ -3076,15 +3077,15 @@ sv -= time_game; cl -= time_frontend + time_backend; - Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n", + Com_Printf ("frame:%i all:%3i sv:%3i ev:%3i cl:%3i gm:%3i rf:%3i bk:%3i\n", com_frameNumber, all, sv, ev, cl, time_game, time_frontend, time_backend ); - } + } // // trace optimization tracking // if ( com_showtrace->integer ) { - + extern int c_traces, c_brush_traces, c_patch_traces; extern int c_pointcontents; @@ -3315,6 +3316,22 @@ /* =============== +Field_CompleteDelay +=============== +*/ +void Field_CompleteDelay( void ) +{ + matchCount = 0; + shortestMatch[ 0 ] = 0; + + Cmd_DelayCompletion( FindMatches ); + + if( !Field_Complete( ) ) + Cmd_DelayCompletion( PrintMatches ); +} + +/* +=============== Field_CompleteCommand =============== */ @@ -3374,7 +3391,7 @@ if( ( p = Field_FindFirstSeparator( cmd ) ) ) Field_CompleteCommand( p + 1, qtrue, qtrue ); // Compound command else - Cmd_CompleteArgument( baseCmd, cmd, completionArgument ); + Cmd_CompleteArgument( baseCmd, cmd, completionArgument ); } else { @@ -3437,4 +3454,3 @@ for( i = 0; i < len; i++ ) string[i] = (unsigned char)( rand() % 255 ); } - diff -Naur openarena-engine-source-0.8.x-28/code//qcommon/cvar.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/cvar.c --- openarena-engine-source-0.8.x-28/code//qcommon/cvar.c 2011-12-24 12:29:32 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/cvar.c 2012-02-04 17:45:21 +0000 @@ -89,7 +89,7 @@ long hash; hash = generateHashValue(var_name); - + for (var=hashTable[hash] ; var ; var=var->hashNext) { if (!Q_stricmp(var_name, var->name)) { return var; @@ -106,7 +106,7 @@ */ float Cvar_VariableValue( const char *var_name ) { cvar_t *var; - + var = Cvar_FindVar (var_name); if (!var) return 0; @@ -121,7 +121,7 @@ */ int Cvar_VariableIntegerValue( const char *var_name ) { cvar_t *var; - + var = Cvar_FindVar (var_name); if (!var) return 0; @@ -136,7 +136,7 @@ */ char *Cvar_VariableString( const char *var_name ) { cvar_t *var; - + var = Cvar_FindVar (var_name); if (!var) return ""; @@ -151,7 +151,7 @@ */ void Cvar_VariableStringBuffer( const char *var_name, char *buffer, int bufsize ) { cvar_t *var; - + var = Cvar_FindVar (var_name); if (!var) { *buffer = 0; @@ -169,7 +169,7 @@ int Cvar_Flags(const char *var_name) { cvar_t *var; - + if(! (var = Cvar_FindVar(var_name)) ) return CVAR_NONEXISTENT; else @@ -184,7 +184,7 @@ void Cvar_CommandCompletion(void (*callback)(const char *s)) { cvar_t *cvar; - + for(cvar = cvar_vars; cvar; cvar = cvar->next) { if(cvar->name) @@ -326,7 +326,7 @@ #endif var = Cvar_FindVar (var_name); - + if(var) { var_value = Cvar_Validate(var, var_value, qfalse); @@ -346,11 +346,11 @@ if(var->latchedString) Z_Free(var->latchedString); - + var->latchedString = CopyString(var_value); } } - + // Make sure the game code cannot mark engine-added variables as gamecode vars if(var->flags & CVAR_VM_CREATED) { @@ -362,7 +362,7 @@ if(flags & CVAR_VM_CREATED) flags &= ~CVAR_VM_CREATED; } - + var->flags |= flags; // only allow one non-empty reset string without a warning @@ -384,7 +384,7 @@ Z_Free( s ); } - // ZOID--needs to be set so that cvars the game sets as + // ZOID--needs to be set so that cvars the game sets as // SERVERINFO get sent to clients cvar_modifiedFlags |= flags; @@ -409,12 +409,12 @@ return NULL; } - + var = &cvar_indexes[index]; - + if(index >= cvar_numIndexes) cvar_numIndexes = index + 1; - + var->name = CopyString (var_name); var->string = CopyString (var_value); var->modified = qtrue; @@ -591,9 +591,9 @@ var->modified = qtrue; var->modificationCount++; - + Z_Free (var->string); // free the old value string - + var->string = CopyString(value); var->value = atof (var->string); var->integer = atoi (var->string); @@ -635,6 +635,21 @@ Cvar_Set (var_name, val); } +/* +============ +Cvar_SetValueLatched +============ +*/ +void Cvar_SetValueLatched( const char *var_name, float value) { + char val[32]; + + if ( value == (int)value ) { + Com_sprintf (val, sizeof(val), "%i",(int)value); + } else { + Com_sprintf (val, sizeof(val), "%f",value); + } + Cvar_SetLatched (var_name, val); +} /* ============ @@ -671,7 +686,7 @@ { if(var->flags & CVAR_CHEAT) { - // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here + // the CVAR_LATCHED|CVAR_CHEAT vars might escape the reset here // because of a different var->latchedString if (var->latchedString) { @@ -716,7 +731,7 @@ ============ Cvar_Print_f -Prints the contents of a cvar +Prints the contents of a cvar (preferred over Cvar_Command where cvar names and commands conflict) ============ */ @@ -724,7 +739,7 @@ { char *name; cvar_t *cv; - + if(Cmd_Argc() != 2) { Com_Printf ("usage: print \n"); @@ -734,7 +749,7 @@ name = Cmd_Argv(1); cv = Cvar_FindVar(name); - + if(cv) Cvar_Print(cv); else @@ -759,8 +774,8 @@ } if(c == 2) { - Cvar_Set2(Cmd_Argv(1), va("%d", - !Cvar_VariableValue(Cmd_Argv(1))), + Cvar_Set2(Cmd_Argv(1), va("%d", + !Cvar_VariableValue(Cmd_Argv(1))), qfalse); return; } @@ -1000,7 +1015,7 @@ cv->hashNext->hashPrev = cv->hashPrev; Com_Memset(cv, '\0', sizeof(*cv)); - + return next; } @@ -1015,18 +1030,18 @@ void Cvar_Unset_f(void) { cvar_t *cv; - + if(Cmd_Argc() != 2) { Com_Printf("Usage: %s \n", Cmd_Argv(0)); return; } - + cv = Cvar_FindVar(Cmd_Argv(1)); if(!cv) return; - + if(cv->flags & CVAR_USER_CREATED) Cvar_Unset(cv); else @@ -1059,13 +1074,13 @@ curvar = Cvar_Unset(curvar); continue; } - + if(!(curvar->flags & (CVAR_ROM | CVAR_INIT | CVAR_NORESTART))) { // Just reset the rest to their default values. Cvar_Set2(curvar->name, curvar->resetString, qfalse); } - + curvar = curvar->next; } } @@ -1209,11 +1224,11 @@ return; // variable might have been cleared by a cvar_restart } vmCvar->modificationCount = cv->modificationCount; - if ( strlen(cv->string)+1 > MAX_CVAR_VALUE_STRING ) + if ( strlen(cv->string)+1 > MAX_CVAR_VALUE_STRING ) Com_Error( ERR_DROP, "Cvar_Update: src %s length %u exceeds MAX_CVAR_VALUE_STRING", - cv->string, + cv->string, (unsigned int) strlen(cv->string)); - Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING ); + Q_strncpyz( vmCvar->string, cv->string, MAX_CVAR_VALUE_STRING ); vmCvar->value = cv->value; vmCvar->integer = cv->integer; diff -Naur openarena-engine-source-0.8.x-28/code//qcommon/files.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/files.c --- openarena-engine-source-0.8.x-28/code//qcommon/files.c 2011-12-24 12:29:32 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/files.c 2012-01-30 19:36:26 +0000 @@ -22,7 +22,7 @@ /***************************************************************************** * name: files.c * - * desc: handle based filesystem for Quake III Arena + * desc: handle based filesystem for Quake III Arena * * $Archive: /MissionPack/code/qcommon/files.c $ * @@ -38,7 +38,7 @@ QUAKE3 FILESYSTEM -All of Quake's data access is through a hierarchical file system, but the contents of +All of Quake's data access is through a hierarchical file system, but the contents of the file system can be transparently merged from several sources. A "qpath" is a reference to game file data. MAX_ZPATH is 256 characters, which must include @@ -402,7 +402,7 @@ if ( ! fsh[f].handleFiles.file.o ) { Com_Error( ERR_DROP, "FS_FileForHandle: NULL" ); } - + return fsh[f].handleFiles.file.o; } @@ -472,7 +472,7 @@ char temp[MAX_OSPATH]; static char ospath[2][MAX_OSPATH]; static int toggle; - + toggle ^= 1; // flip-flop to allow two returns without clash if( !game || !game[0] ) { @@ -482,7 +482,7 @@ Com_sprintf( temp, sizeof(temp), "/%s/%s", game, qpath ); FS_ReplaceSeparators( temp ); Com_sprintf( ospath[toggle], sizeof( ospath[0] ), "%s%s", base, temp ); - + return ospath[toggle]; } @@ -497,7 +497,7 @@ qboolean FS_CreatePath (char *OSPath) { char *ofs; char path[MAX_OSPATH]; - + // make absolutely sure that it can't back up the path // FIXME: is c: allowed??? if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) { @@ -599,7 +599,7 @@ ================ FS_SV_FileExists -Tests if the file exists +Tests if the file exists ================ */ qboolean FS_SV_FileExists( const char *file ) @@ -914,7 +914,7 @@ */ qboolean FS_FilenameCompare( const char *s1, const char *s2 ) { int c1, c2; - + do { c1 = *s1++; c2 = *s2++; @@ -932,12 +932,12 @@ if ( c2 == '\\' || c2 == ':' ) { c2 = '/'; } - + if (c1 != c2) { return qtrue; // strings not equal } } while (c1); - + return qfalse; // strings are equal } @@ -992,7 +992,7 @@ } while(pakFile != NULL); } else if ( search->dir ) { dir = search->dir; - + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); temp = fopen (netpath, "rb"); if ( !temp ) { @@ -1017,7 +1017,7 @@ // make absolutely sure that it can't back up the path. // The searchpaths do guarantee that something will always - // be prepended, so we don't need to worry about "c:" or "//limbo" + // be prepended, so we don't need to worry about "c:" or "//limbo" if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { *file = 0; return -1; @@ -1044,6 +1044,7 @@ } // is the element a pak file? if ( search->pack && search->pack->hashTable[hash] ) { + // disregard if it doesn't match one of the allowed pure pak files if ( !FS_PakIsPure(search->pack) ) { continue; @@ -1058,9 +1059,9 @@ // found it! // mark the pak as having been referenced and mark specifics on cgame and ui - // shaders, txt, arena files by themselves do not count as a reference as - // these are loaded from all pk3s - // from every pk3 file.. + // shaders, txt, arena files by themselves do not count as a reference as + // these are loaded from all pk3s + // from every pk3 file.. l = strlen( filename ); if ( !(pak->referenced & FS_GENERAL_REF)) { if ( Q_stricmp(filename + l - 7, ".shader") != 0 && @@ -1105,7 +1106,7 @@ fsh[*file].zipFilePos = pakFile->pos; if ( fs_debug->integer ) { - Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", + Com_Printf( "FS_FOpenFileRead: %s (found in '%s')\n", filename, pak->pakFilename ); } return pakFile->len; @@ -1113,6 +1114,7 @@ pakFile = pakFile->next; } while(pakFile != NULL); } else if ( search->dir ) { + // check a file in the directory tree // if we are running restricted, the only files we @@ -1123,6 +1125,7 @@ // this test can make the search fail although the file is in the directory // I had the problem on https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=8 // turned out I used FS_FileExists instead + /* if ( fs_numServerPaks ) { if ( Q_stricmp( filename + l - 4, ".cfg" ) // for config files @@ -1133,9 +1136,10 @@ continue; } } + */ dir = search->dir; - + netpath = FS_BuildOSPath( dir->path, dir->gamedir, filename ); fsh[*file].handleFiles.file.o = fopen (netpath, "rb"); if ( !fsh[*file].handleFiles.file.o ) { @@ -1150,9 +1154,9 @@ } return FS_filelength (*file); - } + } } - + #ifdef FS_MISSING if (missingFiles) { fprintf(missingFiles, "%s\n", filename); @@ -1432,7 +1436,7 @@ // make absolutely sure that it can't back up the path. // The searchpaths do guarantee that something will always - // be prepended, so we don't need to worry about "c:" or "//limbo" + // be prepended, so we don't need to worry about "c:" or "//limbo" if ( strstr( filename, ".." ) || strstr( filename, "::" ) ) { return -1; } @@ -1555,7 +1559,7 @@ } return -1; } - + if ( !buffer ) { if ( isConfig && com_journal && com_journal->integer == 1 ) { Com_DPrintf( "Writing len for %s to journal file.\n", qpath ); @@ -1783,21 +1787,21 @@ { pack_t *thepak; int index, checksum; - + thepak = FS_LoadZipFile(zipfile, ""); - + if(!thepak) return qfalse; - + checksum = thepak->checksum; FS_FreePak(thepak); - + for(index = 0; index < fs_numServerReferencedPaks; index++) { if(checksum == fs_serverReferencedPaks[index]) return qtrue; } - + return qfalse; } @@ -1972,7 +1976,7 @@ } Sys_FreeFileList( sysFiles ); } - } + } } // return a copy of the list @@ -2180,7 +2184,7 @@ // we will try each three of them here (yes, it's a bit messy) path = FS_BuildOSPath( fs_basepath->string, name, "" ); nPaks = 0; - pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); + pPaks = Sys_ListFiles(path, ".pk3", NULL, &nPaks, qfalse); Sys_FreeFileList( pPaks ); // we only use Sys_ListFiles to check wether .pk3 files are present /* try on home path */ @@ -2315,7 +2319,7 @@ if ( c2 == '\\' || c2 == ':' ) { c2 = '/'; } - + if (c1 < c2) { return -1; // strings not equal } @@ -2323,7 +2327,7 @@ return 1; } } while (c1); - + return 0; // strings are equal } @@ -2545,7 +2549,7 @@ return; // we've already got this one } } - + Q_strncpyz( fs_gamedir, dir, sizeof( fs_gamedir ) ); // @@ -2573,7 +2577,7 @@ continue; // store the game name for downloading strcpy(pak->pakGamename, dir); - + fs_packFiles += pak->numfiles; search = Z_Malloc (sizeof(searchpath_t)); @@ -2619,7 +2623,7 @@ { if(strstr(checkdir, "../") || strstr(checkdir, "..\\")) return qtrue; - + return qfalse; } @@ -2690,7 +2694,7 @@ } } - if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { + if ( !havepak && fs_serverReferencedPakNames[i] && *fs_serverReferencedPakNames[i] ) { // Don't got it if (dlstring) @@ -2871,14 +2875,14 @@ FS_AddGameDirectory( fs_basepath->string, gameName ); } // fs_homepath is somewhat particular to *nix systems, only add if relevant - + #ifdef MACOS_X fs_apppath = Cvar_Get ("fs_apppath", Sys_DefaultAppPath(), CVAR_INIT ); // Make MacOSX also include the base path included with the .app bundle if (fs_apppath->string[0]) FS_AddGameDirectory(fs_apppath->string, gameName); #endif - + // NOTE: same filtering below for mods and basegame if (fs_homepath->string[0] && Q_stricmp(fs_homepath->string,fs_basepath->string)) { FS_CreatePath ( fs_homepath->string ); @@ -2985,7 +2989,7 @@ info[0] = 0; for ( search = fs_searchpaths ; search ; search = search->next ) { - // is the element a pak file? + // is the element a pak file? if ( !search->pack ) { continue; } @@ -3041,7 +3045,7 @@ info[0] = 0; for ( search = fs_searchpaths ; search ; search = search->next ) { - // is the element a pak file? + // is the element a pak file? if ( !search->pack ) { continue; } @@ -3057,7 +3061,7 @@ FS_ReferencedPakChecksums Returns a space separated string containing the checksums of all referenced pk3 files. -The server will send this to the clients so they can check which files should be auto-downloaded. +The server will send this to the clients so they can check which files should be auto-downloaded. ===================== */ const char *FS_ReferencedPakChecksums( void ) { @@ -3084,7 +3088,7 @@ FS_ReferencedPakPureChecksums Returns a space separated string containing the pure checksums of all referenced pk3 files. -Servers with sv_pure set will get this string back from clients for pure validation +Servers with sv_pure set will get this string back from clients for pure validation The string has a specific order, "cgame ui @ ref1 ref2 ref3 ..." ===================== @@ -3131,7 +3135,7 @@ FS_ReferencedPakNames Returns a space separated string containing the names of all referenced pk3 files. -The server will send this to the clients so they can check which files should be auto-downloaded. +The server will send this to the clients so they can check which files should be auto-downloaded. ===================== */ const char *FS_ReferencedPakNames( void ) { @@ -3246,7 +3250,7 @@ The checksums and names of the pk3 files referenced at the server are sent to the client and stored here. The client will use these -checksums to see if any pk3 files need to be auto-downloaded. +checksums to see if any pk3 files need to be auto-downloaded. ===================== */ void FS_PureServerSetReferencedPaks( const char *pakSums, const char *pakNames ) { @@ -3283,12 +3287,12 @@ fs_serverReferencedPakNames[i] = CopyString( Cmd_Argv( i ) ); } } - + // ensure that there are as many checksums as there are pak names. if(d < c) c = d; - - fs_numServerReferencedPaks = c; + + fs_numServerReferencedPaks = c; } /* @@ -3400,7 +3404,7 @@ FS_Restart(checksumFeed); return qtrue; } - + return qfalse; } diff -Naur openarena-engine-source-0.8.x-28/code//qcommon/msg.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/msg.c --- openarena-engine-source-0.8.x-28/code//qcommon/msg.c 2011-12-24 12:29:32 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/msg.c 2012-01-30 18:42:46 +0000 @@ -21,6 +21,7 @@ */ #include "q_shared.h" #include "qcommon.h" +#include "../game/g_public.h" static huffman_t msgHuff; @@ -45,19 +46,28 @@ if (!msgInit) { MSG_initHuffman(); } - Com_Memset (buf, 0, sizeof(*buf)); + buf->allowoverflow = qfalse; + buf->overflowed = qfalse; + buf->oob = qfalse; buf->data = data; buf->maxsize = length; + buf->cursize = 0; + buf->readcount = 0; + buf->bit = 0; } void MSG_InitOOB( msg_t *buf, byte *data, int length ) { if (!msgInit) { MSG_initHuffman(); } - Com_Memset (buf, 0, sizeof(*buf)); + buf->allowoverflow = qfalse; + buf->overflowed = qfalse; + buf->oob = qtrue; buf->data = data; buf->maxsize = length; - buf->oob = qtrue; + buf->cursize = 0; + buf->readcount = 0; + buf->bit = 0; } void MSG_Clear( msg_t *buf ) { @@ -97,7 +107,7 @@ ============================================================================= bit functions - + ============================================================================= */ @@ -300,7 +310,7 @@ if ( !s ) { MSG_WriteData (sb, "", 1); } else { - int l,i; + int l; char string[MAX_STRING_CHARS]; l = strlen( s ); @@ -311,13 +321,6 @@ } Q_strncpyz( string, s, sizeof( string ) ); - // get rid of 0x80+ and '%' chars, because old clients don't like them - for ( i = 0 ; i < l ; i++ ) { - if ( ((byte *)string)[i] > 127 || string[i] == '%' ) { - string[i] = '.'; - } - } - MSG_WriteData (sb, string, l+1); } } @@ -326,7 +329,7 @@ if ( !s ) { MSG_WriteData (sb, "", 1); } else { - int l,i; + int l; char string[BIG_INFO_STRING]; l = strlen( s ); @@ -337,13 +340,6 @@ } Q_strncpyz( string, s, sizeof( string ) ); - // get rid of 0x80+ and '%' chars, because old clients don't like them - for ( i = 0 ; i < l ; i++ ) { - if ( ((byte *)string)[i] > 127 || string[i] == '%' ) { - string[i] = '.'; - } - } - MSG_WriteData (sb, string, l+1); } } @@ -439,14 +435,6 @@ if ( c == -1 || c == 0 ) { break; } - // translate all fmt spec to avoid crash bugs - if ( c == '%' ) { - c = '.'; - } - // don't allow higher ascii values - if ( c > 127 ) { - c = '.'; - } string[l] = c; l++; @@ -467,14 +455,6 @@ if ( c == -1 || c == 0 ) { break; } - // translate all fmt spec to avoid crash bugs - if ( c == '%' ) { - c = '.'; - } - // don't allow higher ascii values - if ( c > 127 ) { - c = '.'; - } string[l] = c; l++; @@ -495,14 +475,6 @@ if (c == -1 || c == 0 || c == '\n') { break; } - // translate all fmt spec to avoid crash bugs - if ( c == '%' ) { - c = '.'; - } - // don't allow higher ascii values - if ( c > 127 ) { - c = '.'; - } string[l] = c; l++; @@ -551,7 +523,7 @@ extern cvar_t *cl_shownet; -#define LOG(x) if( cl_shownet->integer == 4 ) { Com_Printf("%s ", x ); }; +#define LOG(x) if( cl_shownet && cl_shownet->integer == 4 ) { Com_Printf("%s ", x ); }; void MSG_WriteDelta( msg_t *msg, int oldV, int newV, int bits ) { if ( oldV == newV ) { @@ -895,7 +867,7 @@ float fullFloat; int *fromF, *toF; - numFields = ARRAY_LEN( entityStateFields ); + numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]); // all fields should be 32 bits to avoid any compiler packing issues // the "number" field is not part of the field list @@ -1027,7 +999,7 @@ if ( MSG_ReadBits( msg, 1 ) == 1 ) { Com_Memset( to, 0, sizeof( *to ) ); to->number = MAX_GENTITIES - 1; - if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { + if ( cl_shownet && ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) ) { Com_Printf( "%3i: #%-3i remove\n", msg->readcount, number ); } return; @@ -1040,16 +1012,12 @@ return; } - numFields = ARRAY_LEN( entityStateFields ); + numFields = sizeof(entityStateFields)/sizeof(entityStateFields[0]); lc = MSG_ReadByte(msg); - if ( lc > numFields || lc < 0 ) { - Com_Error( ERR_DROP, "invalid entityState field count" ); - } - // shownet 2/3 will interleave with other printed info, -1 will // just print the delta records` - if ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) { + if ( cl_shownet && ( cl_shownet->integer >= 2 || cl_shownet->integer == -1 ) ) { print = 1; Com_Printf( "%3i: #%-3i ", msg->readcount, to->number ); } else { @@ -1123,6 +1091,207 @@ /* ============================================================================ +enitityShared_t communication + +============================================================================ +*/ + +// using the stringizing operator to save typing... +#define ESF(x) #x,(size_t)&((entityShared_t*)0)->x + +netField_t entitySharedFields[] = +{ +{ ESF(linked), 1 }, +{ ESF(linkcount), 8 }, // enough to see whether the linkcount has changed + // (assuming it doesn't change 256 times in 1 frame) +{ ESF(bmodel), 1 }, +{ ESF(svFlags), 12 }, +{ ESF(singleClient), CLIENTNUM_BITS }, +{ ESF(contents), 32 }, +{ ESF(ownerNum), GENTITYNUM_BITS }, +{ ESF(mins[0]), 0 }, +{ ESF(mins[1]), 0 }, +{ ESF(mins[2]), 0 }, +{ ESF(maxs[0]), 0 }, +{ ESF(maxs[1]), 0 }, +{ ESF(maxs[2]), 0 }, +{ ESF(absmin[0]), 0 }, +{ ESF(absmin[1]), 0 }, +{ ESF(absmin[2]), 0 }, +{ ESF(absmax[0]), 0 }, +{ ESF(absmax[1]), 0 }, +{ ESF(absmax[2]), 0 }, +{ ESF(currentOrigin[0]), 0 }, +{ ESF(currentOrigin[1]), 0 }, +{ ESF(currentOrigin[2]), 0 }, +{ ESF(currentAngles[0]), 0 }, +{ ESF(currentAngles[1]), 0 }, +{ ESF(currentAngles[2]), 0 } +}; + + +/* +================== +MSG_WriteDeltaSharedEntity +================== +*/ +void MSG_WriteDeltaSharedEntity( msg_t *msg, void *from, void *to, + qboolean force, int number ) { + int i, lc; + int numFields; + netField_t *field; + int trunc; + float fullFloat; + int *fromF, *toF; + + numFields = sizeof(entitySharedFields)/sizeof(entitySharedFields[0]); + + // all fields should be 32 bits to avoid any compiler packing issues + // if this assert fails, someone added a field to the entityShared_t + // struct without updating the message fields + assert( numFields == (sizeof( entityShared_t )-sizeof( entityState_t ))/4 ); + + lc = 0; + // build the change vector as bytes so it is endien independent + for ( i = 0, field = entitySharedFields ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + if ( *fromF != *toF ) { + lc = i+1; + } + } + + if ( lc == 0 ) { + // nothing at all changed + if ( !force ) { + return; // nothing at all + } + // write a bits for no change + MSG_WriteBits( msg, number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 0, 1 ); // no delta + return; + } + + MSG_WriteBits( msg, number, GENTITYNUM_BITS ); + MSG_WriteBits( msg, 1, 1 ); // we have a delta + + MSG_WriteByte( msg, lc ); // # of changes + + oldsize += numFields; + + for ( i = 0, field = entitySharedFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( *fromF == *toF ) { + MSG_WriteBits( msg, 0, 1 ); // no change + continue; + } + + MSG_WriteBits( msg, 1, 1 ); // changed + + if ( field->bits == 0 ) { + // float + fullFloat = *(float *)toF; + trunc = (int)fullFloat; + + if (fullFloat == 0.0f) { + MSG_WriteBits( msg, 0, 1 ); + oldsize += FLOAT_INT_BITS; + } else { + MSG_WriteBits( msg, 1, 1 ); + if ( trunc == fullFloat && trunc + FLOAT_INT_BIAS >= 0 && + trunc + FLOAT_INT_BIAS < ( 1 << FLOAT_INT_BITS ) ) { + // send as small integer + MSG_WriteBits( msg, 0, 1 ); + MSG_WriteBits( msg, trunc + FLOAT_INT_BIAS, FLOAT_INT_BITS ); + } else { + // send as full floating point value + MSG_WriteBits( msg, 1, 1 ); + MSG_WriteBits( msg, *toF, 32 ); + } + } + } else { + if (*toF == 0) { + MSG_WriteBits( msg, 0, 1 ); + } else { + MSG_WriteBits( msg, 1, 1 ); + // integer + MSG_WriteBits( msg, *toF, field->bits ); + } + } + } +} + +/* +================== +MSG_ReadDeltaSharedEntity +================== +*/ +void MSG_ReadDeltaSharedEntity( msg_t *msg, void *from, void *to, + int number) { + int i, lc; + int numFields; + netField_t *field; + int *fromF, *toF; + int trunc; + + // check for no delta + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *(entityShared_t*)to = *(entityShared_t*)from; + return; + } + + numFields = sizeof(entitySharedFields)/sizeof(entitySharedFields[0]); + lc = MSG_ReadByte(msg); + + for ( i = 0, field = entitySharedFields ; i < lc ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + + if ( ! MSG_ReadBits( msg, 1 ) ) { + // no change + *toF = *fromF; + } else { + if ( field->bits == 0 ) { + // float + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *(float *)toF = 0.0f; + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + // integral float + trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); + // bias to allow equal parts positive and negative + trunc -= FLOAT_INT_BIAS; + *(float *)toF = trunc; + } else { + // full floating point value + *toF = MSG_ReadBits( msg, 32 ); + } + } + } else { + if ( MSG_ReadBits( msg, 1 ) == 0 ) { + *toF = 0; + } else { + // integer + *toF = MSG_ReadBits( msg, field->bits ); + } + } +// pcount[i]++; + } + } + for ( i = lc, field = &entitySharedFields[lc] ; i < numFields ; i++, field++ ) { + fromF = (int *)( (byte *)from + field->offset ); + toF = (int *)( (byte *)to + field->offset ); + // no change + *toF = *fromF; + } +} + + +/* +============================================================================ + plyer_state_t communication ============================================================================ @@ -1167,7 +1336,7 @@ { PSF(damagePitch), 8 }, { PSF(damageCount), 8 }, { PSF(generic1), 8 }, -{ PSF(pm_type), 8 }, +{ PSF(pm_type), 8 }, { PSF(delta_angles[0]), 16 }, { PSF(delta_angles[2]), 16 }, { PSF(torsoTimer), 12 }, @@ -1210,7 +1379,7 @@ c = msg->cursize; - numFields = ARRAY_LEN( playerStateFields ); + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); lc = 0; for ( i = 0, field = playerStateFields ; i < numFields ; i++, field++ ) { @@ -1370,20 +1539,16 @@ // shownet 2/3 will interleave with other printed info, -2 will // just print the delta records - if ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) { + if ( cl_shownet && ( cl_shownet->integer >= 2 || cl_shownet->integer == -2 ) ) { print = 1; Com_Printf( "%3i: playerstate ", msg->readcount ); } else { print = 0; } - numFields = ARRAY_LEN( playerStateFields ); + numFields = sizeof( playerStateFields ) / sizeof( playerStateFields[0] ); lc = MSG_ReadByte(msg); - if ( lc > numFields || lc < 0 ) { - Com_Error( ERR_DROP, "invalid playerState field count" ); - } - for ( i = 0, field = playerStateFields ; i < lc ; i++, field++ ) { fromF = (int *)( (byte *)from + field->offset ); toF = (int *)( (byte *)to + field->offset ); @@ -1399,7 +1564,8 @@ trunc = MSG_ReadBits( msg, FLOAT_INT_BITS ); // bias to allow equal parts positive and negative trunc -= FLOAT_INT_BIAS; - *(float *)toF = trunc; + + *(float *)toF = trunc; if ( print ) { Com_Printf( "%s:%i ", field->name, trunc ); } diff -Naur openarena-engine-source-0.8.x-28/code//qcommon/q_shared.h openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/q_shared.h --- openarena-engine-source-0.8.x-28/code//qcommon/q_shared.h 2011-12-24 12:29:32 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//qcommon/q_shared.h 2012-01-28 22:24:06 +0000 @@ -408,7 +408,7 @@ static ID_INLINE float Q_rsqrt( float number ) { float x = 0.5f * number; float y; -#ifdef __GNUC__ +#ifdef __GNUC__ asm("frsqrte %0,%1" : "=f" (y) : "f" (number)); #else y = __frsqrte( number ); @@ -416,10 +416,10 @@ return y * (1.5f - (x * y * y)); } -#ifdef __GNUC__ +#ifdef __GNUC__ static ID_INLINE float Q_fabs(float x) { float abs_x; - + asm("fabs %0,%1" : "=f" (abs_x) : "f" (x)); return abs_x; } @@ -499,7 +499,7 @@ static ID_INLINE int VectorCompare( const vec3_t v1, const vec3_t v2 ) { if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2]) { return 0; - } + } return 1; } @@ -962,7 +962,8 @@ // // per-level limits // -#define MAX_CLIENTS 64 // absolute limit +#define CLIENTNUM_BITS 6 +#define MAX_CLIENTS (1<value int nextFrameTime; // when time > nextFrameTime, process world @@ -86,6 +86,15 @@ int gentitySize; int num_entities; // current number, <= MAX_GENTITIES + // serverside demo recording + fileHandle_t demoFile; + demoState_t demoState; + char demoName[MAX_QPATH]; + + // serverside demo recording - previous frame for delta compression + sharedEntity_t demoEntities[MAX_GENTITIES]; + playerState_t demoPlayerStates[MAX_CLIENTS]; + playerState_t *gameClients; int gameClientSize; // will be > sizeof(playerState_t) due to game private data @@ -188,7 +197,7 @@ #endif int oldServerTime; - qboolean csUpdated[MAX_CONFIGSTRINGS+1]; + qboolean csUpdated[MAX_CONFIGSTRINGS+1]; } client_t; //============================================================================= @@ -238,7 +247,7 @@ netadr_t ip; // For a CIDR-Notation type suffix int subnet; - + qboolean isexception; } serverBan_t; @@ -255,6 +264,7 @@ extern cvar_t *sv_privatePassword; extern cvar_t *sv_allowDownload; extern cvar_t *sv_maxclients; +extern cvar_t *sv_democlients; extern cvar_t *sv_privateClients; extern cvar_t *sv_hostname; @@ -275,10 +285,12 @@ extern cvar_t *sv_pure; extern cvar_t *sv_floodProtect; extern cvar_t *sv_lanForceRate; -extern cvar_t *sv_public; +extern cvar_t *sv_public; extern cvar_t *sv_banFile; extern cvar_t *sv_heartbeat; extern cvar_t *sv_flatline; +extern cvar_t *sv_demoState; +extern cvar_t *sv_autoDemo; extern serverBan_t serverBans[SERVER_MAXBANS]; extern int serverBansCount; @@ -363,6 +375,19 @@ void SV_SendClientSnapshot( client_t *client ); // +// sv_demo.c +// +void SV_DemoStartRecord(void); +void SV_DemoStopRecord(void); +void SV_DemoStartPlayback(void); +void SV_DemoStopPlayback(void); +void SV_DemoReadFrame(void); +void SV_DemoWriteFrame(void); +void SV_DemoWriteServerCommand(const char *str); +void SV_DemoWriteGameCommand(int cmd, const char *str); +//void SV_DemoWriteConfigString(int client); + +// // sv_game.c // int SV_NumForGentity( sharedEntity_t *ent ); @@ -453,4 +478,3 @@ void SV_Netchan_Transmit( client_t *client, msg_t *msg); void SV_Netchan_TransmitNextFragment( client_t *client ); qboolean SV_Netchan_Process( client_t *client, msg_t *msg ); - diff -Naur openarena-engine-source-0.8.x-28/code//server/sv_ccmds.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_ccmds.c --- openarena-engine-source-0.8.x-28/code//server/sv_ccmds.c 2011-12-24 12:29:37 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_ccmds.c 2012-02-04 17:20:57 +0000 @@ -31,6 +31,21 @@ =============================================================================== */ +/* +==================== +SV_CompleteDemoName +==================== +*/ +static void SV_CompleteDemoName( char *args, int argNum ) +{ + if( argNum == 2 ) + { + char demoExt[ 16 ]; + + Com_sprintf( demoExt, sizeof( demoExt ), ".svdm_%d", PROTOCOL_VERSION ); + Field_CompleteFilename( "svdemos", demoExt, qtrue, qtrue ); + } +} /* ================== @@ -59,7 +74,7 @@ // Check whether this is a numeric player handle for(i = 0; s[i] >= '0' && s[i] <= '9'; i++); - + if(!s[i]) { int plid = atoi(s); @@ -68,7 +83,7 @@ if(plid >= 0 && plid < sv_maxclients->integer) { cl = &svs.clients[plid]; - + if(cl->state) return cl; } @@ -203,6 +218,12 @@ } } + // stop any demos + if (sv.demoState == DS_RECORDING) + SV_DemoStopRecord(); + if (sv.demoState == DS_PLAYBACK) + SV_DemoStopPlayback(); + // save the map name here cause on a map restart we reload the q3config.cfg // and thus nuke the arguments of the map command Q_strncpyz(mapname, map, sizeof(mapname)); @@ -264,10 +285,10 @@ } // check for changes in variables that can't just be restarted - // check for maxclients change - if ( sv_maxclients->modified || sv_gametype->modified || sv_dorestart->integer ) { + // check for maxclients and democlients change + if ( sv_maxclients->modified || sv_gametype->modified || sv_dorestart->integer || sv_democlients->modified ) { char mapname[MAX_QPATH]; - + sv_dorestart->integer = 0; Com_Printf( "variable change -- restarting.\n" ); // restart the map the slow way @@ -277,11 +298,17 @@ return; } + // stop any demos + if (sv.demoState == DS_RECORDING) + SV_DemoStopRecord(); + if (sv.demoState == DS_PLAYBACK) + SV_DemoStopPlayback(); + // toggle the server bit so clients can detect that a // map_restart has happened svs.snapFlagServerBit ^= SNAPFLAG_SERVERCOUNT; - // generate a new serverid + // generate a new serverid // TTimo - don't update restartedserverId there, otherwise we won't deal correctly with multiple map_restart sv.serverId = com_frameTime; Cvar_Set( "sv_serverid", va("%i", sv.serverId ) ); @@ -351,12 +378,26 @@ // which is wrong obviously. SV_ClientEnterWorld(client, NULL); } - } + } // run another frame to allow things to look at all the players VM_Call (gvm, GAME_RUN_FRAME, sv.time); sv.time += 100; svs.time += 100; + + // start recording a demo + if ( sv_autoDemo->integer ) { + qtime_t now; + Com_RealTime( &now ); + Cbuf_AddText( va( "demo_record %04d%02d%02d%02d%02d%02d-%s\n", + 1900 + now.tm_year, + 1 + now.tm_mon, + now.tm_mday, + now.tm_hour, + now.tm_min, + now.tm_sec, + Cvar_VariableString( "mapname" ) ) ); + } } //=============================================================== @@ -473,7 +514,7 @@ // otherwise send their ip to the authorize server if ( svs.authorizeAddress.type != NA_BAD ) { NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, - "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], + "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); Com_Printf("%s was banned from coming back\n", cl->name); } @@ -527,7 +568,7 @@ // otherwise send their ip to the authorize server if ( svs.authorizeAddress.type != NA_BAD ) { NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, - "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], + "banUser %i.%i.%i.%i", cl->netchan.remoteAddress.ip[0], cl->netchan.remoteAddress.ip[1], cl->netchan.remoteAddress.ip[2], cl->netchan.remoteAddress.ip[3] ); Com_Printf("%s was banned from coming back\n", cl->name); } @@ -547,9 +588,9 @@ fileHandle_t readfrom; char *textbuf, *curpos, *maskpos, *newlinepos, *endpos; char filepath[MAX_QPATH]; - + serverBansCount = 0; - + if(!sv_banFile->string || !*sv_banFile->string) return; @@ -565,36 +606,36 @@ } curpos = textbuf = Z_Malloc(filelen); - + filelen = FS_Read(textbuf, filelen, readfrom); FS_FCloseFile(readfrom); - + endpos = textbuf + filelen; - + for(index = 0; index < SERVER_MAXBANS && curpos + 2 < endpos; index++) { // find the end of the address string for(maskpos = curpos + 2; maskpos < endpos && *maskpos != ' '; maskpos++); - + if(maskpos + 1 >= endpos) break; *maskpos = '\0'; maskpos++; - + // find the end of the subnet specifier for(newlinepos = maskpos; newlinepos < endpos && *newlinepos != '\n'; newlinepos++); - + if(newlinepos >= endpos) break; - + *newlinepos = '\0'; - + if(NET_StringToAdr(curpos + 2, &serverBans[index].ip, NA_UNSPEC)) { serverBans[index].isexception = (curpos[0] != '0'); serverBans[index].subnet = atoi(maskpos); - + if(serverBans[index].ip.type == NA_IP && (serverBans[index].subnet < 1 || serverBans[index].subnet > 32)) { @@ -606,12 +647,12 @@ serverBans[index].subnet = 128; } } - + curpos = newlinepos + 1; } - + serverBansCount = index; - + Z_Free(textbuf); } } @@ -628,21 +669,21 @@ int index; fileHandle_t writeto; char filepath[MAX_QPATH]; - + if(!sv_banFile->string || !*sv_banFile->string) return; - + Com_sprintf(filepath, sizeof(filepath), "%s/%s", FS_GetCurrentGameDir(), sv_banFile->string); if((writeto = FS_SV_FOpenFileWrite(filepath))) { char writebuf[128]; serverBan_t *curban; - + for(index = 0; index < serverBansCount; index++) { curban = &serverBans[index]; - + Com_sprintf(writebuf, sizeof(writebuf), "%d %s %d\n", curban->isexception, NET_AdrToString(curban->ip), curban->subnet); FS_Write(writebuf, strlen(writebuf), writeto); @@ -686,7 +727,7 @@ static qboolean SV_ParseCIDRNotation(netadr_t *dest, int *mask, char *adrstr) { char *suffix; - + suffix = strchr(adrstr, '/'); if(suffix) { @@ -700,7 +741,7 @@ if(suffix) { *mask = atoi(suffix); - + if(dest->type == NA_IP) { if(*mask < 1 || *mask > 32) @@ -716,7 +757,7 @@ *mask = 32; else *mask = 128; - + return qfalse; } @@ -735,9 +776,9 @@ netadr_t ip; int index, argc, mask; serverBan_t *curban; - + argc = Cmd_Argc(); - + if(argc < 2 || argc > 3) { Com_Printf ("Usage: %s (ip[/subnet] | clientnum [subnet])\n", Cmd_Argv(0)); @@ -751,11 +792,11 @@ } banstring = Cmd_Argv(1); - + if(strchr(banstring, '.') || strchr(banstring, ':')) { // This is an ip address, not a client num. - + if(SV_ParseCIDRNotation(&ip, &mask, banstring)) { Com_Printf("Error: Invalid address %s\n", banstring); @@ -765,14 +806,14 @@ else { client_t *cl; - + // client num. if(!com_sv_running->integer) { Com_Printf("Server is not running.\n"); return; } - + cl = SV_GetPlayerByNum(); if(!cl) @@ -780,13 +821,13 @@ Com_Printf("Error: Playernum %s does not exist.\n", Cmd_Argv(1)); return; } - + ip = cl->netchan.remoteAddress; - + if(argc == 3) { mask = atoi(Cmd_Argv(2)); - + if(ip.type == NA_IP) { if(mask < 1 || mask > 32) @@ -812,13 +853,13 @@ for(index = 0; index < serverBansCount; index++) { curban = &serverBans[index]; - + if(curban->subnet <= mask) { if((curban->isexception || !isexception) && NET_CompareBaseAdrMask(curban->ip, ip, curban->subnet)) { Q_strncpyz(addy2, NET_AdrToString(ip), sizeof(addy2)); - + Com_Printf("Error: %s %s/%d supersedes %s %s/%d\n", curban->isexception ? "Exception" : "Ban", NET_AdrToString(curban->ip), curban->subnet, isexception ? "exception" : "ban", addy2, mask); @@ -830,7 +871,7 @@ if(!curban->isexception && isexception && NET_CompareBaseAdrMask(curban->ip, ip, mask)) { Q_strncpyz(addy2, NET_AdrToString(curban->ip), sizeof(addy2)); - + Com_Printf("Error: %s %s/%d supersedes already existing %s %s/%d\n", isexception ? "Exception" : "Ban", NET_AdrToString(ip), mask, curban->isexception ? "exception" : "ban", addy2, curban->subnet); @@ -844,7 +885,7 @@ while(index < serverBansCount) { curban = &serverBans[index]; - + if(curban->subnet > mask && (!curban->isexception || isexception) && NET_CompareBaseAdrMask(curban->ip, ip, mask)) SV_DelBanEntryFromList(index); else @@ -854,9 +895,9 @@ serverBans[serverBansCount].ip = ip; serverBans[serverBansCount].subnet = mask; serverBans[serverBansCount].isexception = isexception; - + serverBansCount++; - + SV_WriteBans(); Com_Printf("Added %s: %s/%d\n", isexception ? "ban exception" : "ban", @@ -876,7 +917,7 @@ int index, count = 0, todel, mask; netadr_t ip; char *banstring; - + if(Cmd_Argc() != 2) { Com_Printf ("Usage: %s (ip[/subnet] | num)\n", Cmd_Argv(0)); @@ -884,23 +925,23 @@ } banstring = Cmd_Argv(1); - + if(strchr(banstring, '.') || strchr(banstring, ':')) { serverBan_t *curban; - + if(SV_ParseCIDRNotation(&ip, &mask, banstring)) { Com_Printf("Error: Invalid address %s\n", banstring); return; } - + index = 0; - + while(index < serverBansCount) { curban = &serverBans[index]; - + if(curban->isexception == isexception && curban->subnet >= mask && NET_CompareBaseAdrMask(curban->ip, ip, mask)) @@ -908,7 +949,7 @@ Com_Printf("Deleting %s %s/%d\n", isexception ? "exception" : "ban", NET_AdrToString(curban->ip), curban->subnet); - + SV_DelBanEntryFromList(index); } else @@ -924,13 +965,13 @@ Com_Printf("Error: Invalid ban number given\n"); return; } - + for(index = 0; index < serverBansCount; index++) { if(serverBans[index].isexception == isexception) { count++; - + if(count == todel) { Com_Printf("Deleting %s %s/%d\n", @@ -944,7 +985,7 @@ } } } - + SV_WriteBans(); } @@ -961,7 +1002,7 @@ { int index, count; serverBan_t *ban; - + // List all bans for(index = count = 0; index < serverBansCount; index++) { @@ -999,10 +1040,10 @@ static void SV_FlushBans_f(void) { serverBansCount = 0; - + // empty the ban file. SV_WriteBans(); - + Com_Printf("All bans and exceptions have been deleted.\n"); } @@ -1101,13 +1142,13 @@ } Com_Printf ("%s", cl->name); - + // TTimo adding a ^7 to reset the color // NOTE: colored names in status breaks the padding (WONTFIX) Com_Printf ("^7"); l = 14 - strlen(cl->name); j = 0; - + do { Com_Printf (" "); @@ -1120,13 +1161,13 @@ Com_Printf ("%s", s); l = 22 - strlen(s); j = 0; - + do { Com_Printf(" "); j++; } while(j < l); - + Com_Printf ("%5i", cl->netchan.qport); Com_Printf (" %5i", cl->rate); @@ -1248,6 +1289,120 @@ SV_Shutdown( "killserver" ); } + +/* +================= +SV_Demo_Record_f +================= +*/ +static void SV_Demo_Record_f( void ) { + // make sure server is running + if (!com_sv_running->integer) { + Com_Printf("Server is not running.\n"); + return; + } + + if (Cmd_Argc() > 2) { + Com_Printf("Usage: demo_record \n"); + return; + } + + if (sv.demoState != DS_NONE) { + Com_Printf("A demo is already being recorded/played.\n"); + return; + } + + if (sv_maxclients->integer == MAX_CLIENTS) { + Com_Printf("Too many slots, reduce sv_maxclients.\n"); + return; + } + + if (Cmd_Argc() == 2) + sprintf(sv.demoName, "svdemos/%s.svdm_%d", Cmd_Argv(1), PROTOCOL_VERSION); + else { + int number; + // scan for a free demo name + for (number = 0 ; number >= 0 ; number++) { + Com_sprintf(sv.demoName, sizeof(sv.demoName), "svdemos/%d.svdm_%d", number, PROTOCOL_VERSION); + if (!FS_FileExists(sv.demoName)) + break; // file doesn't exist + } + if (number < 0) { + Com_Printf("Couldn't generate a filename for the demo, try deleting some old ones.\n"); + return; + } + } + + sv.demoFile = FS_FOpenFileWrite(sv.demoName); + if (!sv.demoFile) { + Com_Printf("ERROR: Couldn't open %s for writing.\n", sv.demoName); + return; + } + SV_DemoStartRecord(); +} + + +/* +================= +SV_Demo_Play_f +================= +*/ +static void SV_Demo_Play_f( void ) { + char *arg; + + if (Cmd_Argc() != 2) { + Com_Printf("Usage: demo_play \n"); + return; + } + + if (sv.demoState != DS_NONE) { + Com_Printf("A demo is already being recorded/played.\n"); + return; + } + + //if (sv_democlients->integer <= 0) { + //Com_Printf("You need to set sv_democlients to a value greater than 0.\n"); + //return; + //} + + // check for an extension .svdm_?? (?? is protocol) + arg = Cmd_Argv(1); + if (!strcmp(arg + strlen(arg) - 6, va(".svdm_%d", PROTOCOL_VERSION))) + Com_sprintf(sv.demoName, sizeof(sv.demoName), "svdemos/%s", arg); + else + Com_sprintf(sv.demoName, sizeof(sv.demoName), "svdemos/%s.svdm_%d", arg, PROTOCOL_VERSION); + + + //FS_FileExists(sv.demoName); + FS_FOpenFileRead(sv.demoName, &sv.demoFile, qtrue); + if (!sv.demoFile) { + Com_Printf("ERROR: Couldn't open %s for reading.\n", sv.demoName); + return; + } + + //sv.demoFile = fopen ("C:\\Users\\LRQ\\DOWNLO~1\\oa081\\OPENAR~1.1\\AppData\\OpenArena\\baseoa\\svdemos\\20120128234426-ps37ctf.svdm_71", "rb"); + SV_DemoStartPlayback(); +} + + +/* +================= +SV_Demo_Stop_f +================= +*/ +static void SV_Demo_Stop_f( void ) { + if (sv.demoState == DS_NONE) { + Com_Printf("No demo is currently being recorded or played.\n"); + return; + } + + // Close the demo file + if (sv.demoState == DS_PLAYBACK) + SV_DemoStopPlayback(); + else + SV_DemoStopRecord(); +} + //=========================================================== /* @@ -1301,10 +1456,14 @@ Cmd_SetCommandCompletionFunc( "spdevmap", SV_CompleteMapName ); #endif Cmd_AddCommand ("killserver", SV_KillServer_f); + Cmd_AddCommand ("demo_record", SV_Demo_Record_f); + Cmd_AddCommand ("demo_play", SV_Demo_Play_f); + Cmd_SetCommandCompletionFunc( "demo_play", SV_CompleteDemoName ); + Cmd_AddCommand ("demo_stop", SV_Demo_Stop_f); if( com_dedicated->integer ) { Cmd_AddCommand ("say", SV_ConSay_f); } - + Cmd_AddCommand("rehashbans", SV_RehashBans_f); Cmd_AddCommand("listbans", SV_ListBans_f); Cmd_AddCommand("banaddr", SV_BanAddr_f); @@ -1335,4 +1494,3 @@ Cmd_RemoveCommand ("say"); #endif } - diff -Naur openarena-engine-source-0.8.x-28/code//server/sv_client.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_client.c --- openarena-engine-source-0.8.x-28/code//server/sv_client.c 2011-12-24 12:29:37 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_client.c 2012-01-28 21:06:59 +0000 @@ -106,7 +106,7 @@ if (svs.authorizeAddress.type == NA_BAD) { Com_Printf( "Resolving %s\n", AUTHORIZE_SERVER_NAME ); - + if (NET_StringToAdr(AUTHORIZE_SERVER_NAME, &svs.authorizeAddress, NA_IP)) { svs.authorizeAddress.port = BigShort( PORT_AUTHORIZE ); @@ -135,21 +135,21 @@ // If the client provided us with a client challenge, store it... if(*clientChallenge) challenge->clientChallenge = atoi(clientChallenge); - + Com_DPrintf( "sending getIpAuthorize for %s\n", NET_AdrToString( from )); - + strcpy(game, BASEGAME); fs = Cvar_Get ("fs_game", "", CVAR_INIT|CVAR_SYSTEMINFO ); if (fs && fs->string[0] != 0) { strcpy(game, fs->string); } - + // the 0 is for backwards compatibility with obsolete sv_allowanonymous flags // getIpAuthorize 0 NET_OutOfBandPrint( NS_SERVER, svs.authorizeAddress, "getIpAuthorize %i %i.%i.%i.%i %s 0 %s", challenge->challenge, from.ip[0], from.ip[1], from.ip[2], from.ip[3], game, sv_strictAuth->string ); - + return; } } @@ -192,7 +192,7 @@ Com_Printf( "SV_AuthorizeIpPacket: challenge not found\n" ); return; } - + challengeptr = &svs.challenges[i]; // send a packet back to the original client @@ -247,25 +247,25 @@ { int index; serverBan_t *curban; - + if(!isexception) { // If this is a query for a ban, first check whether the client is excepted if(SV_IsBanned(from, qtrue)) return qfalse; } - + for(index = 0; index < serverBansCount; index++) { curban = &serverBans[index]; - + if(curban->isexception == isexception) { if(NET_CompareBaseAdrMask(curban->ip, *from, curban->subnet)) return qtrue; } } - + return qfalse; } @@ -294,7 +294,7 @@ char *ip; Com_DPrintf ("SVC_DirectConnect ()\n"); - + // Check whether this client is banned. if(SV_IsBanned(&from, qfalse)) { @@ -320,9 +320,9 @@ continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) - && ( cl->netchan.qport == qport + && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { - if (( svs.time - cl->lastConnectTime) + if (( svs.time - cl->lastConnectTime) < (sv_reconnectlimit->integer * 1000)) { Com_DPrintf ("%s:reconnect rejected : too soon\n", NET_AdrToString (from)); return; @@ -330,7 +330,7 @@ break; } } - + // don't let "ip" overflow userinfo string if ( NET_IsLocalAddress (from) ) ip = "localhost"; @@ -364,9 +364,9 @@ NET_OutOfBandPrint( NS_SERVER, from, "print\nNo or bad challenge for your address.\n" ); return; } - + challengeptr = &svs.challenges[i]; - + if(challengeptr->wasrefused) { // Return silently, so that error messages written by the server keep being displayed. @@ -404,7 +404,7 @@ continue; } if ( NET_CompareBaseAdr( from, cl->netchan.remoteAddress ) - && ( cl->netchan.qport == qport + && ( cl->netchan.qport == qport || from.port == cl->netchan.remoteAddress.port ) ) { Com_Printf ("%s:reconnect\n", NET_AdrToString (from)); newcl = cl; @@ -432,10 +432,10 @@ // check for privateClient password password = Info_ValueForKey( userinfo, "password" ); if ( !strcmp( password, sv_privatePassword->string ) ) { - startIndex = 0; + startIndex = sv_democlients->integer; } else { // skip past the reserved slots - startIndex = sv_privateClients->integer; + startIndex = sv_privateClients->integer + sv_democlients->integer; } newcl = NULL; @@ -477,7 +477,7 @@ cl->reliableAcknowledge = 0; cl->reliableSequence = 0; -gotnewcl: +gotnewcl: // build a new connection // accept the new client // this is the only place a client_t is ever initialized @@ -519,7 +519,7 @@ newcl->nextSnapshotTime = svs.time; newcl->lastPacketTime = svs.time; newcl->lastConnectTime = svs.time; - + // when we receive the first packet from the client, we will // notice that it is from a different serverid and that the // gamestate message was not just sent, forcing a retransmit @@ -595,7 +595,7 @@ // nuke user info SV_SetUserinfo( drop - svs.clients, "" ); - + if ( isBot ) { // bots shouldn't go zombie, as there's no real net connection. drop->state = CS_FREE; @@ -842,7 +842,7 @@ SV_WriteDownloadToClient Check to see if the client wants a file, open it if needed and start pumping the client -Fill up msg with data +Fill up msg with data ================== */ void SV_WriteDownloadToClient( client_t *cl , msg_t *msg ) @@ -864,11 +864,11 @@ #ifndef STANDALONE qboolean missionPack = qfalse; #endif - + // Chop off filename extension. Com_sprintf(pakbuf, sizeof(pakbuf), "%s", cl->downloadName); pakptr = Q_strrchr(pakbuf, '.'); - + if(pakptr) { *pakptr = '\0'; @@ -956,15 +956,15 @@ MSG_WriteString( msg, errorMessage ); *cl->downloadName = 0; - + if(cl->download) FS_FCloseFile(cl->download); - + return; } - + Com_Printf( "clientDownload: %d : beginning \"%s\"\n", (int) (cl - svs.clients), cl->downloadName ); - + // Init cl->downloadCurrentBlock = cl->downloadClientBlock = cl->downloadXmitBlock = 0; cl->downloadCount = 0; @@ -1064,7 +1064,7 @@ // block zero is special, contains file size if ( cl->downloadXmitBlock == 0 ) MSG_WriteLong( msg, cl->downloadSize ); - + MSG_WriteShort( msg, cl->downloadBlockSize[curindex] ); // Write the block @@ -1165,7 +1165,7 @@ const char *pPaks, *pArg; qboolean bGood = qtrue; - // if we are pure, we "expect" the client to load certain things from + // if we are pure, we "expect" the client to load certain things from // certain pk3 files, namely we want the client to have loaded the // ui and cgame that we think should be loaded based on the pure setting // @@ -1198,7 +1198,7 @@ return; } } - + // we basically use this while loop to avoid using 'goto' :) while (bGood) { @@ -1297,7 +1297,7 @@ if (bGood) { cl->pureAuthentic = 1; - } + } else { cl->pureAuthentic = 0; cl->nextSnapshotTime = -1; @@ -1376,7 +1376,7 @@ } else { cl->snapshotMsec = 50; } - + #ifdef USE_VOIP // in the future, (val) will be a protocol version string, so only // accept explicitly 1, not generally non-zero. @@ -1484,7 +1484,7 @@ void SV_ExecuteClientCommand( client_t *cl, const char *s, qboolean clientOK ) { ucmd_t *u; qboolean bProcessed = qfalse; - + Cmd_TokenizeString( s ); // see if it is a server level command @@ -1530,7 +1530,7 @@ // drop the connection if we have somehow lost commands if ( seq > cl->lastClientCommand + 1 ) { - Com_Printf( "Client %s lost %i clientCommands\n", cl->name, + Com_Printf( "Client %s lost %i clientCommands\n", cl->name, seq - cl->lastClientCommand + 1 ); SV_DropClient( cl, "Lost reliable commands" ); return qfalse; @@ -1543,14 +1543,14 @@ // but not other people // We don't do this when the client hasn't been active yet since its // normal to spam a lot of commands when downloading - if ( !com_cl_running->integer && + if ( !com_cl_running->integer && cl->state >= CS_ACTIVE && - sv_floodProtect->integer && + sv_floodProtect->integer && svs.time < cl->nextReliableTime ) { // ignore any other text messages from this client but let them keep playing // TTimo - moved the ignored verbose to the actual processing in SV_ExecuteClientCommand, only printing if the core doesn't intercept clientOk = qfalse; - } + } // don't allow another command for one second cl->nextReliableTime = svs.time + 1000; @@ -1588,7 +1588,7 @@ ================== SV_UserMove -The message usually contains all the movement commands +The message usually contains all the movement commands that were in the last three packets, so that the information in dropped packets can be recovered. @@ -1651,17 +1651,17 @@ SV_SendClientGameState( cl ); } return; - } - + } + // if this is the first usercmd we have received // this gamestate, put the client into the world if ( cl->state == CS_PRIMED ) { SV_ClientEnterWorld( cl, &cmds[0] ); // the moves can be processed normaly } - + // a bad cp command was sent, drop the client - if (sv_pure->integer != 0 && cl->pureAuthentic == 0) { + if (sv_pure->integer != 0 && cl->pureAuthentic == 0) { SV_DropClient( cl, "Cannot validate pure client!"); return; } @@ -1701,7 +1701,7 @@ return qtrue; // VoIP disabled on this server. else if (!cl->hasVoip) // client doesn't have VoIP support?! return qtrue; - + // !!! FIXME: implement player blacklist. return qfalse; // don't ignore. @@ -1854,7 +1854,7 @@ } // if this is a usercmd from a previous gamestate, // ignore it or retransmit the current gamestate - // + // // if the client was downloading, let it stay at whatever serverId and // gamestate it was at. This allows it to keep downloading even when // the gamestate changes. After the download is finished, we'll diff -Naur openarena-engine-source-0.8.x-28/code//server/sv_demo.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_demo.c --- openarena-engine-source-0.8.x-28/code//server/sv_demo.c 1970-01-01 00:00:00 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_demo.c 2012-02-05 15:42:03 +0000 @@ -0,0 +1,522 @@ +/* +=========================================================================== +Copyright (C) 2008 Amanieu d'Antras + +This file is part of Tremulous. + +Tremulous 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. + +Tremulous 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 Tremulous; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +=========================================================================== +*/ + +// sv_demo.c -- Server side demo recording + +#include "server.h" + +// Headers for demo messages +typedef enum { + demo_endFrame, + demo_configString, + demo_serverCommand, + demo_gameCommand, + demo_entityState, + demo_entityShared, + demo_playerState, + demo_endDemo, + demo_EOF +} demo_ops_e; + +// Big fat buffer to store all our stuff +static byte buf[0x400000]; + +// Save maxclients and democlients and restore them after the demo +static int savedMaxClients = -1; +static int savedDemoClients = -1; +static int savedBotMinPlayers = -1; +static int keepSaved = 0; // var that memorizes if we keep the new maxclients and democlients values (in the case that we restart the map/server for these cvars to be affected since they are latched) or if we can restore them + +/* +==================== +SV_DemoWriteMessage + +Write a message to the demo file +==================== +*/ +static void SV_DemoWriteMessage(msg_t *msg) +{ + int len; + + // Write the entire message to the file, prefixed by the length + MSG_WriteByte(msg, demo_EOF); + len = LittleLong(msg->cursize); + FS_Write(&len, 4, sv.demoFile); + FS_Write(msg->data, msg->cursize, sv.demoFile); + MSG_Clear(msg); +} + +/* +==================== +SV_DemoWriteServerCommand + +Write a server command to the demo file +==================== +*/ +void SV_DemoWriteServerCommand(const char *str) +{ + msg_t msg; + + MSG_Init(&msg, buf, sizeof(buf)); + MSG_WriteByte(&msg, demo_serverCommand); + MSG_WriteString(&msg, str); + SV_DemoWriteMessage(&msg); +} + +/* +==================== +SV_DemoWriteGameCommand + +Write a game command to the demo file +==================== +*/ +void SV_DemoWriteGameCommand(int cmd, const char *str) +{ + msg_t msg; + + MSG_Init(&msg, buf, sizeof(buf)); + MSG_WriteByte(&msg, demo_gameCommand); + MSG_WriteByte(&msg, cmd); + MSG_WriteString(&msg, str); + SV_DemoWriteMessage(&msg); +} + +/* +==================== +SV_DemoWriteConfigString + +Write a client configstring to the demo file +==================== +*/ +void SV_DemoWriteConfigString(int client) +{ + msg_t msg; + + MSG_Init(&msg, buf, sizeof(buf)); + MSG_WriteByte(&msg, demo_configString); + MSG_WriteBits(&msg, client, CLIENTNUM_BITS); + MSG_WriteString(&msg, sv.configstrings[CS_PLAYERS + client]); + SV_DemoWriteMessage(&msg); +} + +/* +==================== +SV_DemoWriteFrame + +Record all the entities and players at the end the frame +==================== +*/ +void SV_DemoWriteFrame(void) +{ + msg_t msg; + playerState_t *player; + sharedEntity_t *entity; + int i; + + MSG_Init(&msg, buf, sizeof(buf)); + + // Write entities + MSG_WriteByte(&msg, demo_entityState); + for (i = 0; i < sv.num_entities; i++) + { + if (i >= sv_maxclients->integer && i < MAX_CLIENTS) + continue; + entity = SV_GentityNum(i); + entity->s.number = i; + MSG_WriteDeltaEntity(&msg, &sv.demoEntities[i].s, &entity->s, qfalse); + sv.demoEntities[i].s = entity->s; + } + MSG_WriteBits(&msg, ENTITYNUM_NONE, GENTITYNUM_BITS); + MSG_WriteByte(&msg, demo_entityShared); + for (i = 0; i < sv.num_entities; i++) + { + if (i >= sv_maxclients->integer && i < MAX_CLIENTS) + continue; + entity = SV_GentityNum(i); + MSG_WriteDeltaSharedEntity(&msg, &sv.demoEntities[i].r, &entity->r, qfalse, i); + sv.demoEntities[i].r = entity->r; + } + MSG_WriteBits(&msg, ENTITYNUM_NONE, GENTITYNUM_BITS); + SV_DemoWriteMessage(&msg); + + // Write clients + for (i = 0; i < sv_maxclients->integer; i++) + { + if (svs.clients[i].state < CS_ACTIVE) + continue; + player = SV_GameClientNum(i); + MSG_WriteByte(&msg, demo_playerState); + MSG_WriteBits(&msg, i, CLIENTNUM_BITS); + MSG_WriteDeltaPlayerstate(&msg, &sv.demoPlayerStates[i], player); + sv.demoPlayerStates[i] = *player; + } + MSG_WriteByte(&msg, demo_endFrame); + MSG_WriteLong(&msg, sv.time); + SV_DemoWriteMessage(&msg); +} + +/* +==================== +SV_DemoReadFrame + +Play a frame from the demo file +==================== +*/ +void SV_DemoReadFrame(void) +{ + msg_t msg; + int cmd, r, num, i; + playerState_t *player; + sharedEntity_t *entity; + + MSG_Init(&msg, buf, sizeof(buf)); + + while (1) + { +exit_loop: + // Get a message + r = FS_Read(&msg.cursize, 4, sv.demoFile); + if (r != 4) + { + SV_DemoStopPlayback(); + return; + } + msg.cursize = LittleLong(msg.cursize); + if (msg.cursize > msg.maxsize) + Com_Error(ERR_DROP, "SV_DemoReadFrame: demo message too long"); + r = FS_Read(msg.data, msg.cursize, sv.demoFile); + if (r != msg.cursize) + { + Com_Printf("Demo file was truncated.\n"); + SV_DemoStopPlayback(); + return; + } + + // Parse the message + while (1) + { + cmd = MSG_ReadByte(&msg); + switch (cmd) + { + default: + Com_Error(ERR_DROP, "SV_DemoReadFrame: Illegible demo message\n"); + return; + case demo_EOF: + MSG_Clear(&msg); + goto exit_loop; + case demo_endDemo: + SV_DemoStopPlayback(); + return; + case demo_endFrame: + // Overwrite anything the game may have changed + for (i = 0; i < sv.num_entities; i++) + { + if (i >= sv_democlients->integer && i < MAX_CLIENTS) + continue; + *SV_GentityNum(i) = sv.demoEntities[i]; + } + for (i = 0; i < sv_democlients->integer; i++) + *SV_GameClientNum(i) = sv.demoPlayerStates[i]; + // Set the server time + sv.time = MSG_ReadLong(&msg); + return; + case demo_configString: + num = MSG_ReadBits(&msg, CLIENTNUM_BITS); + SV_SetConfigstring(CS_PLAYERS + num, MSG_ReadString(&msg)); //, qtrue + break; + case demo_serverCommand: + Cmd_SaveCmdContext(); + Cmd_TokenizeString(MSG_ReadString(&msg)); + SV_SendServerCommand(NULL, "%s \"^3[DEMO] ^7%s\"", Cmd_Argv(0), Cmd_ArgsFrom(1)); + Cmd_RestoreCmdContext(); + break; + case demo_gameCommand: + num = MSG_ReadByte(&msg); + Cmd_SaveCmdContext(); + Cmd_TokenizeString(MSG_ReadString(&msg)); + VM_Call(gvm, GAME_DEMO_COMMAND, num); + Cmd_RestoreCmdContext(); + break; + case demo_playerState: + num = MSG_ReadBits(&msg, CLIENTNUM_BITS); + player = SV_GameClientNum(num); + MSG_ReadDeltaPlayerstate(&msg, &sv.demoPlayerStates[num], player); + sv.demoPlayerStates[num] = *player; + break; + case demo_entityState: + while (1) + { + num = MSG_ReadBits(&msg, GENTITYNUM_BITS); + if (num == ENTITYNUM_NONE) + break; + entity = SV_GentityNum(num); + MSG_ReadDeltaEntity(&msg, &sv.demoEntities[num].s, &entity->s, num); + sv.demoEntities[num].s = entity->s; + } + break; + case demo_entityShared: + while (1) + { + num = MSG_ReadBits(&msg, GENTITYNUM_BITS); + if (num == ENTITYNUM_NONE) + break; + entity = SV_GentityNum(num); + MSG_ReadDeltaSharedEntity(&msg, &sv.demoEntities[num].r, &entity->r, num); + + // Link/unlink the entity + if (entity->r.linked && (!sv.demoEntities[num].r.linked || + entity->r.linkcount != sv.demoEntities[num].r.linkcount)) + SV_LinkEntity(entity); + else if (!entity->r.linked && sv.demoEntities[num].r.linked) + SV_UnlinkEntity(entity); + + sv.demoEntities[num].r = entity->r; + if (num > sv.num_entities) + sv.num_entities = num; + } + break; + } + } + } +} + +/* +==================== +SV_DemoStartRecord + +sv.demo* have already been set and the demo file opened, start writing gamestate info +==================== +*/ +void SV_DemoStartRecord(void) +{ + msg_t msg; + int i; + + // Set democlients to 0 since it's only used for replaying demo + Cvar_SetValueLatched("sv_democlients", 0); + + MSG_Init(&msg, buf, sizeof(buf)); + + // Write number of clients (sv_maxclients < MAX_CLIENTS or else we can't playback) + MSG_WriteBits(&msg, sv_maxclients->integer, CLIENTNUM_BITS); + // Write current time + MSG_WriteLong(&msg, sv.time); + // Write map name + MSG_WriteString(&msg, sv_mapname->string); + // Write number of clients (sv_maxclients < MAX_CLIENTS or else we can't playback) + //MSG_WriteBits(&msg, sv_maxclients->integer, CLIENTNUM_BITS); + SV_DemoWriteMessage(&msg); + + // Write client configstrings + for (i = 0; i < sv_maxclients->integer; i++) + { + if (svs.clients[i].state == CS_ACTIVE && sv.configstrings[CS_PLAYERS + i]) + SV_DemoWriteConfigString(i); + } + SV_DemoWriteMessage(&msg); + + // Write entities and players + Com_Memset(sv.demoEntities, 0, sizeof(sv.demoEntities)); + Com_Memset(sv.demoPlayerStates, 0, sizeof(sv.demoPlayerStates)); + SV_DemoWriteFrame(); + Com_Printf("Recording demo %s.\n", sv.demoName); + sv.demoState = DS_RECORDING; + Cvar_SetValue("sv_demoState", DS_RECORDING); +} + +/* +==================== +SV_DemoStopRecord + +Write end of file and close the demo file +==================== +*/ +void SV_DemoStopRecord(void) +{ + msg_t msg; + + // End the demo + MSG_Init(&msg, buf, sizeof(buf)); + MSG_WriteByte(&msg, demo_endDemo); + SV_DemoWriteMessage(&msg); + + FS_FCloseFile(sv.demoFile); + sv.demoState = DS_NONE; + Cvar_SetValue("sv_demoState", DS_NONE); + Com_Printf("Stopped recording demo %s.\n", sv.demoName); +} + +/* +==================== +SV_DemoStartPlayback + +sv.demo* have already been set and the demo file opened, start reading gamestate info +==================== +*/ +void SV_DemoStartPlayback(void) +{ + msg_t msg; + int r, i, clients; + char *s; + + if (keepSaved > 0) { // restore keepSaved to 0 (because this is the second time we launch this function, so now there's no need to keep the cvars further) + keepSaved--; + } + + MSG_Init(&msg, buf, sizeof(buf)); + + // Get the demo header + r = FS_Read(&msg.cursize, 4, sv.demoFile); + if (r != 4) + { + SV_DemoStopPlayback(); + return; + } + msg.cursize = LittleLong(msg.cursize); + if (msg.cursize == -1) + { + SV_DemoStopPlayback(); + return; + } + if (msg.cursize > msg.maxsize) + Com_Error(ERR_DROP, "SV_DemoReadFrame: demo message too long"); + r = FS_Read(msg.data, msg.cursize, sv.demoFile); + if (r != msg.cursize) + { + Com_Printf("Demo file was truncated.\n"); + SV_DemoStopPlayback(); + return; + } + + // Check slots, time and map + clients = MSG_ReadBits(&msg, CLIENTNUM_BITS); + if (sv_democlients->integer < clients) { + Com_Printf("Not enough demo slots, automatically increasing sv_democlients to %d and sv_maxclients to %d.\n", clients, sv_maxclients->integer + clients); + + // save the old values of sv_maxclients, sv_democlients and bot_minplayers to later restore them + savedMaxClients = sv_maxclients->integer; + savedDemoClients = sv_democlients->integer; + savedBotMinPlayers = Cvar_VariableIntegerValue("bot_minplayers"); + keepSaved = 1; + + // automatically adjusting sv_democlients, sv_maxclients and bot_minplayers + Cvar_SetValueLatched("sv_democlients", clients); + Cvar_SetValueLatched("sv_maxclients", sv_maxclients->integer + clients); + Cvar_SetValue("bot_minplayers", 0); // if we have bots that autoconnect, this will make up for a very weird demo! + //SV_DemoStopPlayback(); + //return; + } + + r = MSG_ReadLong(&msg); + if (r < 400) + { + Com_Printf("Demo time too small: %d.\n", r); + SV_DemoStopPlayback(); + return; + } + s = MSG_ReadString(&msg); + if (!FS_FOpenFileRead(va("maps/%s.bsp", s), NULL, qfalse)) + { + Com_Printf("Map does not exist: %s.\n", s); + SV_DemoStopPlayback(); + return; + } + + if (!com_sv_running->integer || strcmp(sv_mapname->string, s) || + !Cvar_VariableIntegerValue("sv_cheats") || r < sv.time || + sv_maxclients->modified || sv_democlients->modified) + { + /// Change to the right map and start the demo with a hardcoded 10 seconds delay + // FIXME: this should not be a hardcoded value, there should be a way to ensure that the map fully restarted before continuing. And this can NOT be based on warmup, because if warmup is set to 0 (disabled), then you'll have no delay, and a delay is necessary! If the demo starts replaying before the map is restarted, it will simply do nothing. + // delay command is here used as a workaround for waiting until the map is fully restarted + + SV_DemoStopPlayback(); + Cbuf_AddText(va("devmap %s\ndelay 10000 %s\n", s, Cmd_Cmd())); + //Cmd_ExecuteString(va("devmap %s\ndelay 10000 %s\n", s, Cmd_Cmd())); // another way to do it, I think it would be preferable to use cmd_executestring, but it doesn't work (dunno why) + //Cbuf_AddText(va("devmap %s\ndelay %d %s\n", s, Cvar_VariableIntegerValue("g_warmup") * 1000, Cmd_Cmd())); // Old tremfusion way to do it, which is bad way when g_warmup is 0, you get no delay + + return; + } + + + // Initialize our stuff + Com_Memset(sv.demoEntities, 0, sizeof(sv.demoEntities)); + Com_Memset(sv.demoPlayerStates, 0, sizeof(sv.demoPlayerStates)); + Cvar_SetValue("sv_democlients", clients); + for (i = 0; i < sv_democlients->integer; i++) + SV_SetConfigstring(CS_PLAYERS + i, NULL); //qtrue + SV_DemoReadFrame(); + Com_Printf("Playing demo %s.\n", sv.demoName); + sv.demoState = DS_PLAYBACK; + Cvar_SetValue("sv_demoState", DS_PLAYBACK); +} + +/* +==================== +SV_DemoStopPlayback + +Close the demo file and restart the map +==================== +*/ +void SV_DemoStopPlayback(void) +{ + int olddemostate; + olddemostate = sv.demoState; + // Clear client configstrings + int i; + for (i = 0; i < sv_democlients->integer; i++) + SV_SetConfigstring(CS_PLAYERS + i, NULL); //qtrue + + FS_FCloseFile(sv.demoFile); + sv.demoState = DS_NONE; + Cvar_SetValue("sv_demoState", DS_NONE); + Com_Printf("Stopped playing demo %s.\n", sv.demoName); + + // restore maxclients and democlients + // Note: must do it before the map_restart! so that it takes effect (because it's latched) + if (keepSaved == 0 && savedMaxClients >= 0 && savedDemoClients >= 0) { + Cvar_SetValueLatched("sv_maxclients", savedMaxClients); + Cvar_SetValueLatched("sv_democlients", savedDemoClients); + if (savedBotMinPlayers >= 0) { + Cvar_SetValue("bot_minplayers", savedBotMinPlayers); + } + } + + // demo hasn't actually started yet + if (olddemostate == DS_NONE && keepSaved == 0) { +#ifdef DEDICATED + //Cbuf_AddText("map_restart 0\n"); +#else + Com_Error (ERR_DROP,"An error happened while replaying the demo, please check the log for more info\n"); + Cbuf_AddText("killserver\n"); +#endif + } else if (olddemostate == DS_PLAYBACK) { +#ifdef DEDICATED + Cbuf_AddText("map_restart 0\n"); +#else + Cbuf_AddText("map_restart 0\ndelay 2000 killserver\n"); // we have to do a map_restart before killing the client-side local server that was used to replay the demo, for the old restored values for sv_democlients and sv_maxclients to be updated (else, if you directly try to launch another demo just after, it will crash - it seems that 2 consecutive latching without an update makes the engine crash) + we need to have a delay between restarting map and killing the server else it will produce a bug +#endif + } + + return; + +} diff -Naur openarena-engine-source-0.8.x-28/code//server/sv_game.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_game.c --- openarena-engine-source-0.8.x-28/code//server/sv_game.c 2011-12-24 12:29:37 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_game.c 2012-01-29 18:12:18 +0000 @@ -89,7 +89,7 @@ if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { return; } - SV_SendServerCommand( svs.clients + clientNum, "%s", text ); + SV_SendServerCommand( svs.clients + clientNum, "%s", text ); } } @@ -105,7 +105,7 @@ if ( clientNum < 0 || clientNum >= sv_maxclients->integer ) { return; } - SV_DropClient( svs.clients + clientNum, reason ); + SV_DropClient( svs.clients + clientNum, reason ); } @@ -262,7 +262,7 @@ playerState_t *clients, int sizeofGameClient ) { sv.gentities = gEnts; sv.gentitySize = sizeofGEntity_t; - sv.num_entities = numGEntities; + if ( sv.num_entities < numGEntities ) sv.num_entities = numGEntities; sv.gameClients = clients; sv.gameClientSize = sizeofGameClient; @@ -308,7 +308,7 @@ case G_MILLISECONDS: return Sys_Milliseconds(); case G_CVAR_REGISTER: - Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); + Cvar_Register( VMA(1), VMA(2), VMA(3), args[4] ); return 0; case G_CVAR_UPDATE: Cvar_Update( VMA(1) ); @@ -437,6 +437,16 @@ Sys_SnapVector( VMA(1) ); return 0; + case G_DEMO_COMMAND: + if ( sv.demoState == DS_RECORDING ) + { + if ( args[1] == -1 ) + SV_DemoWriteServerCommand( VMA(2) ); + else + SV_DemoWriteGameCommand( args[1], VMA(2) ); + } + return 0; + //==================================== case BOTLIB_SETUP: @@ -887,7 +897,7 @@ for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { svs.clients[i].gentity = NULL; } - + // use the current msec count for a random seed // init for this gamestate VM_Call (gvm, GAME_INIT, sv.time, Com_Milliseconds(), restart); @@ -962,4 +972,3 @@ return VM_Call( gvm, GAME_CONSOLE_COMMAND ); } - diff -Naur openarena-engine-source-0.8.x-28/code//server/sv_init.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_init.c --- openarena-engine-source-0.8.x-28/code//server/sv_init.c 2011-12-24 12:29:37 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_init.c 2012-02-05 14:10:09 +0000 @@ -111,6 +111,11 @@ Com_Error (ERR_DROP, "SV_SetConfigstring: bad index %i\n", index); } + // Don't allow the game to overwrite demo player configstrings + if ( sv.demoState == DS_PLAYBACK && index >= CS_PLAYERS && index < CS_PLAYERS + sv_democlients->integer ) { // if (!force && sv.demoState == DS_PLAYBACK && ... + return; + } + if ( !val ) { val = ""; } @@ -124,6 +129,11 @@ Z_Free( sv.configstrings[index] ); sv.configstrings[index] = CopyString( val ); + // save client strings to demo + if ( index >= CS_PLAYERS && index < CS_PLAYERS + sv_maxclients->integer && sv.demoState == DS_RECORDING ) { + SV_DemoWriteConfigString( index - CS_PLAYERS ); + } + // send it to all the clients if we aren't // spawning a new server if ( sv.state == SS_GAME || sv.restarting ) { @@ -139,7 +149,7 @@ if ( index == CS_SERVERINFO && client->gentity && (client->gentity->r.svFlags & SVF_NOSERVERINFO) ) { continue; } - + SV_SendConfigstring(client, index); } } @@ -216,7 +226,7 @@ */ static void SV_CreateBaseline( void ) { sharedEntity_t *svent; - int entnum; + int entnum; for ( entnum = 1; entnum < sv.num_entities ; entnum++ ) { svent = SV_GentityNum(entnum); @@ -267,15 +277,9 @@ if ( svs.initialized ) { Com_Error( ERR_FATAL, "SV_Startup: svs.initialized" ); } - SV_BoundMaxClients( 1 ); - svs.clients = Z_Malloc (sizeof(client_t) * sv_maxclients->integer ); - if ( com_dedicated->integer ) { - svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; - } else { - // we don't need nearly as many when playing locally - svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; - } + SV_ChangeMaxClients(); + svs.initialized = qtrue; // Don't respect sv_killserver unless a server is actually running @@ -284,7 +288,7 @@ } Cvar_Set( "sv_running", "1" ); - + // Join the ipv6 multicast group now that a map is running so clients can scan for us on the local network. NET_JoinMulticast6(); } @@ -296,64 +300,84 @@ ================== */ void SV_ChangeMaxClients( void ) { - int oldMaxClients; - int i; - client_t *oldClients; - int count; - - // get the highest client number in use - count = 0; - for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { - if ( svs.clients[i].state >= CS_CONNECTED ) { - if (i > count) - count = i; - } - } - count++; - - oldMaxClients = sv_maxclients->integer; - // never go below the highest client number in use - SV_BoundMaxClients( count ); - // if still the same - if ( sv_maxclients->integer == oldMaxClients ) { - return; - } - - oldClients = Hunk_AllocateTempMemory( count * sizeof(client_t) ); - // copy the clients to hunk memory - for ( i = 0 ; i < count ; i++ ) { - if ( svs.clients[i].state >= CS_CONNECTED ) { - oldClients[i] = svs.clients[i]; - } - else { - Com_Memset(&oldClients[i], 0, sizeof(client_t)); - } - } - - // free old clients arrays - Z_Free( svs.clients ); - - // allocate new clients - svs.clients = Z_Malloc ( sv_maxclients->integer * sizeof(client_t) ); - Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) ); - - // copy the clients over - for ( i = 0 ; i < count ; i++ ) { - if ( oldClients[i].state >= CS_CONNECTED ) { - svs.clients[i] = oldClients[i]; - } - } - - // free the old clients on the hunk - Hunk_FreeTempMemory( oldClients ); - - // allocate new snapshot entities - if ( com_dedicated->integer ) { - svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; - } else { - // we don't need nearly as many when playing locally - svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; - } + int oldMaxClients; + int i, j; + client_t *oldClients = NULL; + int count = 0; + qboolean firstTime = svs.clients == NULL; + + if ( !firstTime ) { + // get the number of clients in use + for ( i = 0 ; i < sv_maxclients->integer ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + count++; + } + } + } + + oldMaxClients = sv_maxclients->integer; + // update the cvars + Cvar_Get( "sv_maxclients", "8", 0 ); + Cvar_Get( "sv_democlients", "0", 0 ); + + // make sure we have enough room for all clients + // FIXME: is it really necessary? + if ( sv_democlients->integer + count > MAX_CLIENTS ) + Cvar_SetValue( "sv_democlients", MAX_CLIENTS - count ); + if ( sv_maxclients->integer < sv_democlients->integer + count ) { + Cvar_SetValueLatched( "sv_maxclients", sv_democlients->integer + count ); + } + sv_maxclients->modified = qfalse; + sv_democlients->modified = qfalse; + // if still the same + if ( !firstTime && sv_maxclients->integer == oldMaxClients ) { + // move people who are below sv_democlients up + for ( i = 0; i < sv_democlients->integer; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + for ( j = sv_democlients->integer; j < sv_maxclients->integer; j++ ) { + if ( svs.clients[j].state < CS_CONNECTED ) { + svs.clients[j] = svs.clients[i]; + break; + } + } + Com_Memset( svs.clients + i, 0, sizeof(client_t) ); + } + } + return; + } + + if ( !firstTime ) { + // copy the clients to hunk memory + oldClients = Hunk_AllocateTempMemory( count * sizeof(client_t) ); + for ( i = 0, j = 0 ; i < oldMaxClients ; i++ ) { + if ( svs.clients[i].state >= CS_CONNECTED ) { + oldClients[j++] = svs.clients[i]; + } + } + + // free old clients arrays + Z_Free( svs.clients ); + } + + // allocate new clients + svs.clients = Z_Malloc( sv_maxclients->integer * sizeof(client_t) ); + Com_Memset( svs.clients, 0, sv_maxclients->integer * sizeof(client_t) ); + + if ( !firstTime ) { + // copy the clients over + Com_Memcpy( svs.clients + sv_democlients->integer, oldClients, count * sizeof(client_t) ); + + // free the old clients on the hunk + Hunk_FreeTempMemory( oldClients ); + } + + // allocate new snapshot entities + if ( com_dedicated->integer ) { + svs.numSnapshotEntities = sv_maxclients->integer * PACKET_BACKUP * 64; + } else { + // we don't need nearly as many when playing locally + svs.numSnapshotEntities = sv_maxclients->integer * 4 * 64; + } } /* @@ -376,7 +400,7 @@ ================ SV_TouchCGame - touch the cgame.vm so that a pure client can load it if it's in a seperate pk3 +Touch the cgame.qvm and ui.qvm so that a pure client can load it if it's in a seperate pk3, and so it gets on the download list ================ */ static void SV_TouchCGame(void) { @@ -430,12 +454,12 @@ // clear collision map data CM_ClearMap(); - // init client structures and svs.numSnapshotEntities + // init client structures and svs.numSnapshotEntities if ( !Cvar_VariableValue("sv_running") ) { SV_Startup(); } else { - // check for maxclients change - if ( sv_maxclients->modified ) { + // check for maxclients or democlients change + if ( sv_maxclients->modified || sv_democlients->modified ) { SV_ChangeMaxClients(); } } @@ -491,7 +515,7 @@ // clear physics interaction links SV_ClearWorld (); - + // media configstring setting should be done during // the loading stage, so connected clients don't have // to load during actual gameplay @@ -560,7 +584,7 @@ } } } - } + } // run another frame to allow things to look at all the players VM_Call (gvm, GAME_RUN_FRAME, sv.time); @@ -615,6 +639,20 @@ Hunk_SetMark(); Com_Printf ("-----------------------------------\n"); + + // start recording a demo + if ( sv_autoDemo->integer ) { + qtime_t now; + Com_RealTime( &now ); + Cbuf_AddText( va( "demo_record %04d%02d%02d%02d%02d%02d-%s\n", + 1900 + now.tm_year, + 1 + now.tm_mon, + now.tm_mday, + now.tm_hour, + now.tm_min, + now.tm_sec, + server ) ); + } } /* @@ -671,7 +709,7 @@ sv_allowDownload = Cvar_Get ("sv_allowDownload", "0", CVAR_SERVERINFO); Cvar_Get ("sv_dlURL", "", CVAR_SERVERINFO | CVAR_ARCHIVE); - + sv_master[0] = Cvar_Get("sv_master1", MASTER_SERVER_NAME, 0); for(index = 1; index < MAX_MASTER_SERVERS; index++) sv_master[index] = Cvar_Get(va("sv_master%d", index + 1), "", CVAR_ARCHIVE); @@ -687,12 +725,17 @@ sv_heartbeat = Cvar_Get("sv_heartbeat", HEARTBEAT_FOR_MASTER, CVAR_INIT); sv_flatline = Cvar_Get("sv_flatline", FLATLINE_FOR_MASTER, CVAR_INIT); + // serverside demo recording variables + sv_demoState = Cvar_Get ("sv_demoState", "0", CVAR_ROM ); + sv_democlients = Cvar_Get ("sv_democlients", "0", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE ); + sv_autoDemo = Cvar_Get ("sv_autoDemo", "0", CVAR_ARCHIVE ); + // initialize bot cvars so they are listed and can be set before loading the botlib SV_BotInitCvars(); // init the botlib here because we need the pre-compiler in the UI SV_BotInitBotLib(); - + // Load saved bans Cbuf_AddText("rehashbans\n"); } @@ -711,7 +754,7 @@ void SV_FinalMessage( char *message ) { int i, j; client_t *cl; - + // send it twice, ignoring rate for ( j = 0 ; j < 2 ; j++ ) { for (i=0, cl = svs.clients ; i < sv_maxclients->integer ; i++, cl++) { @@ -755,6 +798,12 @@ SV_MasterShutdown(); SV_ShutdownGameProgs(); + // stop any demos + if (sv.demoState == DS_RECORDING) + SV_DemoStopRecord(); + if (sv.demoState == DS_PLAYBACK) + SV_DemoStopPlayback(); + // free current level SV_ClearServer(); @@ -773,4 +822,3 @@ if( sv_killserver->integer != 2 ) CL_Disconnect( qfalse ); } - diff -Naur openarena-engine-source-0.8.x-28/code//server/sv_main.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_main.c --- openarena-engine-source-0.8.x-28/code//server/sv_main.c 2011-12-24 12:29:37 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//server/sv_main.c 2012-01-29 18:13:53 +0000 @@ -37,6 +37,7 @@ cvar_t *sv_privatePassword; // password for the privateClient slots cvar_t *sv_allowDownload; cvar_t *sv_maxclients; +cvar_t *sv_democlients; // number of slots reserved for playing a demo cvar_t *sv_privateClients; // number of clients reserved for password cvar_t *sv_hostname; @@ -62,6 +63,8 @@ cvar_t *sv_heartbeat; // Heartbeat string that is sent to the master cvar_t *sv_flatline; // If the master server supports it we can send a flatline // when server is killed +cvar_t *sv_demoState; +cvar_t *sv_autoDemo; serverBan_t serverBans[SERVER_MAXBANS]; int serverBansCount = 0; @@ -176,7 +179,7 @@ ================= SV_SendServerCommand -Sends a reliable command string to be interpreted by +Sends a reliable command string to be interpreted by the client game module: "cp", "print", "chat", etc A NULL client will broadcast to all clients ================= @@ -186,7 +189,7 @@ byte message[MAX_MSGLEN]; client_t *client; int j; - + va_start (argptr,fmt); Q_vsnprintf ((char *)message, sizeof(message), fmt,argptr); va_end (argptr); @@ -209,6 +212,10 @@ Com_Printf ("broadcast: %s\n", SV_ExpandNewlines((char *)message) ); } + // save broadcasts to demo + if ( sv.demoState == DS_RECORDING ) + SV_DemoWriteServerCommand( (char *)message ); + // send the data to all relevent clients for (j = 0, client = svs.clients; j < sv_maxclients->integer ; j++, client++) { SV_AddServerCommand( client, (char *)message ); @@ -267,7 +274,7 @@ if(sv_master[i]->modified || (adr[i][0].type == NA_BAD && adr[i][1].type == NA_BAD)) { sv_master[i]->modified = qfalse; - + if(netenabled & NET_ENABLEV4) { Com_Printf("Resolving %s (IPv4)\n", sv_master[i]->string); @@ -278,13 +285,13 @@ // if no port was specified, use the default master port adr[i][0].port = BigShort(PORT_MASTER); } - + if(res) Com_Printf( "%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i][0])); else Com_Printf( "%s has no IPv4 address.\n", sv_master[i]->string); } - + if(netenabled & NET_ENABLEV6) { Com_Printf("Resolving %s (IPv6)\n", sv_master[i]->string); @@ -295,7 +302,7 @@ // if no port was specified, use the default master port adr[i][1].port = BigShort(PORT_MASTER); } - + if(res) Com_Printf( "%s resolved to %s\n", sv_master[i]->string, NET_AdrToStringwPort(adr[i][1])); else @@ -452,7 +459,7 @@ } else { bucketHashes[ bucket->hash ] = bucket->next; } - + if ( bucket->next != NULL ) { bucket->next->prev = bucket->prev; } @@ -584,7 +591,7 @@ cl = &svs.clients[i]; if ( cl->state >= CS_CONNECTED ) { ps = SV_GameClientNum( i ); - Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", + Com_sprintf (player, sizeof(player), "%i %i \"%s\"\n", ps->persistant[PERS_SCORE], cl->ping, cl->name); playerLength = strlen(player); if (statusLength + playerLength >= sizeof(status) ) { @@ -647,8 +654,8 @@ Info_SetValueForKey( infostring, "hostname", sv_hostname->string ); Info_SetValueForKey( infostring, "mapname", sv_mapname->string ); Info_SetValueForKey( infostring, "clients", va("%i", count) ); - Info_SetValueForKey( infostring, "sv_maxclients", - va("%i", sv_maxclients->integer - sv_privateClients->integer ) ); + Info_SetValueForKey( infostring, "sv_maxclients", + va("%i", sv_maxclients->integer - sv_privateClients->integer - sv_democlients->integer ) ); Info_SetValueForKey( infostring, "gametype", va("%i", sv_gametype->integer ) ); Info_SetValueForKey( infostring, "pure", va("%i", sv_pure->integer ) ); //Sago's things: @@ -738,7 +745,7 @@ Com_Printf ("Bad rconpassword.\n"); } else { remaining[0] = 0; - + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543 // get the command directly, "rcon " to avoid quoting issues // extract the command by walking @@ -751,9 +758,9 @@ cmd_aux++; while(cmd_aux[0]==' ') cmd_aux++; - + Q_strcat( remaining, sizeof(remaining), cmd_aux); - + Cmd_ExecuteString (remaining); } @@ -870,7 +877,7 @@ } return; } - + // if we received a sequenced packet from an address we don't recognize, // send an out of band disconnect packet to it NET_OutOfBandPrint( NS_SERVER, from, "disconnect" ); @@ -935,7 +942,7 @@ ================== SV_CheckTimeouts -If a packet has not been received from a client for timeout->integer +If a packet has not been received from a client for timeout->integer seconds, drop the conneciton. Server time is used instead of realtime to avoid dropping the local client while debugging. @@ -970,7 +977,7 @@ // wait several frames so a debugger session doesn't // cause a timeout if ( ++cl->timeoutCount > 5 ) { - SV_DropClient (cl, "timed out"); + SV_DropClient (cl, "timed out"); cl->state = CS_FREE; // don't bother with zombie state } } else { @@ -1025,9 +1032,9 @@ if(sv_fps) { int frameMsec; - + frameMsec = 1000.0f / sv_fps->value; - + if(frameMsec < sv.timeResidual) return 0; else @@ -1140,6 +1147,11 @@ // let everything in the world think and move VM_Call (gvm, GAME_RUN_FRAME, sv.time); + + if (sv.demoState == DS_RECORDING) + SV_DemoWriteFrame(); + else if (sv.demoState == DS_PLAYBACK) + SV_DemoReadFrame(); } if ( com_speeds->integer ) { @@ -1157,4 +1169,3 @@ } //============================================================================ - diff -Naur openarena-engine-source-0.8.x-28/code//ui/ui_shared.c openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//ui/ui_shared.c --- openarena-engine-source-0.8.x-28/code//ui/ui_shared.c 2011-12-24 12:29:41 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//ui/ui_shared.c 2012-01-31 23:25:58 +0000 @@ -19,7 +19,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ -// +// // string allocation/managment #include "ui_shared.h" @@ -42,6 +42,17 @@ static scrollInfo_t scrollInfo; +#define MAX_DELAYED_COMMANDS 64 + +typedef struct delayed_cmd_s +{ + itemDef_t *item; + int time; +} +delayed_cmd_t; + +delayed_cmd_t delayed_cmd[ MAX_DELAYED_COMMANDS ]; + static void (*captureFunc) (void *p) = 0; static void *captureData = NULL; static itemDef_t *itemCapture = NULL; // item that has the mouse captured ( if any ) @@ -88,9 +99,9 @@ =============== UI_Alloc =============== -*/ +*/ void *UI_Alloc( int size ) { - char *p; + char *p; if ( allocPoint + size > MEM_POOL_SIZE ) { outOfMemory = qtrue; @@ -494,7 +505,7 @@ if (!trap_PC_ReadToken(handle, &token)) return qfalse; - + *(out) = String_Alloc(token.string); return qtrue; } @@ -509,9 +520,9 @@ pc_token_t token; memset(script, 0, sizeof(script)); - // scripts start with { and have ; separated command lists.. commands are command, arg.. + // scripts start with { and have ; separated command lists.. commands are command, arg.. // basically we want everything between the { } as it will be interpreted at run time - + if (!trap_PC_ReadToken(handle, &token)) return qfalse; if (Q_stricmp(token.string, "{") != 0) { @@ -538,7 +549,7 @@ } // display, window, menu, item code -// +// /* ================== @@ -553,7 +564,7 @@ -// type and style painting +// type and style painting void GradientBar_Paint(rectDef_t *rect, vec4_t color) { // gradient bar takes two paints @@ -568,7 +579,7 @@ Window_Init Initializes a window structure ( windowDef_t ) with defaults - + ================== */ void Window_Init(Window *w) { @@ -654,7 +665,7 @@ if (w->cinematic == -1) { w->cinematic = -2; } - } + } if (w->cinematic >= 0) { DC->runCinematicFrame(w->cinematic); DC->drawCinematic(w->cinematic, fillRect.x, fillRect.y, fillRect.w, fillRect.h); @@ -665,7 +676,7 @@ // full // HACK HACK HACK if (w->style == WINDOW_STYLE_TEAMCOLOR) { - if (color[0] > 0) { + if (color[0] > 0) { // red color[0] = 1; color[1] = color[2] = .5; @@ -702,7 +713,7 @@ void Item_SetScreenCoords(itemDef_t *item, float x, float y) { - + if (item == NULL) { return; } @@ -726,7 +737,7 @@ void Item_UpdatePosition(itemDef_t *item) { float x, y; menuDef_t *menu; - + if (item == NULL || item->parent == NULL) { return; } @@ -735,7 +746,7 @@ x = menu->window.rect.x; y = menu->window.rect.y; - + if (menu->window.border != 0) { x += menu->window.borderSize; y += menu->window.borderSize; @@ -753,7 +764,7 @@ if (menu == NULL) { return; } - + x = menu->window.rect.x; y = menu->window.rect.y; if (menu->window.border != 0) { @@ -790,13 +801,13 @@ for (i = 0; i < menu->itemCount; i++) { if (menu->items[i]->window.flags & WINDOW_HASFOCUS) { ret = menu->items[i]; - } + } menu->items[i]->window.flags &= ~WINDOW_HASFOCUS; if (menu->items[i]->leaveFocus) { Item_RunScript(menu->items[i], menu->items[i]->leaveFocus); } } - + return ret; } @@ -819,7 +830,7 @@ for (i = 0; i < menu->itemCount; i++) { if (Q_stricmp(menu->items[i]->window.name, name) == 0 || (menu->items[i]->window.group && Q_stricmp(menu->items[i]->window.group, name) == 0)) { count++; - } + } } return count; } @@ -833,7 +844,7 @@ return menu->items[i]; } count++; - } + } } return NULL; } @@ -873,7 +884,7 @@ const char *name; // expecting name to set asset to if (String_Parse(args, &name)) { - // check for a model + // check for a model if (item->type == ITEM_TYPE_MODEL) { } } @@ -1000,7 +1011,7 @@ for (i = 0; i < menuCount; i++) { if (Q_stricmp(Menus[i].window.name, p) == 0) { return &Menus[i]; - } + } } return NULL; } @@ -1207,7 +1218,7 @@ if (String_Parse(args, &cvar) && String_Parse(args, &val)) { DC->setCVar(cvar, val); } - + } void Script_Exec(itemDef_t *item, char **args) { @@ -1232,6 +1243,43 @@ } } +void Menu_DelayItemByName( menuDef_t *menu, const char *p, int time ) +{ + itemDef_t *item; + int i, j = 0; + int count = Menu_ItemsMatchingGroup( menu, p ); + + for( i = 0; i < count; i++ ) + { + item = Menu_GetMatchingItemByNumber( menu, i, p ); + + if( item != NULL && item->delayEvent && item->delayEvent[ 0 ] ) + { + for( ; j < MAX_DELAYED_COMMANDS; j++ ) + { + if( !delayed_cmd[ j ].time ) + { + delayed_cmd[ j ].item = item; + delayed_cmd[ j ].time = DC->realTime + time; + j++; + break; + } + } + } + } +} + +void Script_Delay( itemDef_t *item, char **args ) +{ + const char *name; + int time; + + if( Int_Parse( args, &time ) && String_Parse( args, &name ) ) + { + Menu_DelayItemByName( item->parent, name, time ); + } +} + commandDef_t commandList[] = { @@ -1255,7 +1303,8 @@ {"exec", &Script_Exec}, // group/name {"play", &Script_Play}, // group/name {"playlooped", &Script_playLooped}, // group/name - {"orbit", &Script_Orbit} // group/name + {"orbit", &Script_Orbit}, // group/name + {"delay", &Script_Delay}, // group/name }; int scriptCommandCount = ARRAY_LEN(commandList); @@ -1336,7 +1385,7 @@ } -// will optionaly set focus to this item +// will optionaly set focus to this item qboolean Item_SetFocus(itemDef_t *item, float x, float y) { int i; itemDef_t *oldFocus; @@ -1349,8 +1398,8 @@ } // this can be NULL - parent = (menuDef_t*)item->parent; - + parent = (menuDef_t*)item->parent; + // items can be enabled and disabled based on cvars if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { return qfalse; @@ -1594,11 +1643,11 @@ } -void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y) +void Item_ListBox_MouseEnter(itemDef_t *item, float x, float y) { rectDef_t r; listBoxDef_t *listPtr = (listBoxDef_t*)item->typeData; - + item->window.flags &= ~(WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN); item->window.flags |= Item_ListBox_OverLB(item, x, y); @@ -1617,7 +1666,7 @@ } } } else { - // text hit.. + // text hit.. } } } else if (!(item->window.flags & (WINDOW_LB_LEFTARROW | WINDOW_LB_RIGHTARROW | WINDOW_LB_THUMB | WINDOW_LB_PGUP | WINDOW_LB_PGDN))) { @@ -1727,7 +1776,7 @@ max = Item_ListBox_MaxScroll(item); if (item->window.flags & WINDOW_HORIZONTAL) { viewmax = (item->window.rect.w / listPtr->elementWidth); - if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) + if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) { if (!listPtr->notselectable) { listPtr->cursorPos--; @@ -1750,7 +1799,7 @@ } return qtrue; } - if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) + if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) { if (!listPtr->notselectable) { listPtr->cursorPos++; @@ -1776,7 +1825,7 @@ } else { viewmax = (item->window.rect.h / listPtr->elementHeight); - if ( key == K_UPARROW || key == K_KP_UPARROW ) + if ( key == K_UPARROW || key == K_KP_UPARROW ) { if (!listPtr->notselectable) { listPtr->cursorPos--; @@ -1799,7 +1848,7 @@ } return qtrue; } - if ( key == K_DOWNARROW || key == K_KP_DOWNARROW ) + if ( key == K_DOWNARROW || key == K_KP_DOWNARROW ) { if (!listPtr->notselectable) { listPtr->cursorPos++; @@ -2102,7 +2151,7 @@ return qtrue; } - if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) + if ( key == K_RIGHTARROW || key == K_KP_RIGHTARROW ) { if (editPtr->maxPaintChars && item->cursorPos >= editPtr->maxPaintChars && item->cursorPos < len) { item->cursorPos++; @@ -2111,11 +2160,11 @@ } if (item->cursorPos < len) { item->cursorPos++; - } + } return qtrue; } - if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) + if ( key == K_LEFTARROW || key == K_KP_LEFTARROW ) { if ( item->cursorPos > 0 ) { item->cursorPos--; @@ -2172,12 +2221,12 @@ static void Scroll_ListBox_AutoFunc(void *p) { scrollInfo_t *si = (scrollInfo_t*)p; - if (DC->realTime > si->nextScrollTime) { + if (DC->realTime > si->nextScrollTime) { // need to scroll which is done by simulating a click to the item // this is done a bit sideways as the autoscroll "knows" that the item is a listbox // so it calls it directly Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); - si->nextScrollTime = DC->realTime + si->adjustValue; + si->nextScrollTime = DC->realTime + si->adjustValue; } if (DC->realTime > si->nextAdjustTime) { @@ -2233,12 +2282,12 @@ si->yStart = DC->cursory; } - if (DC->realTime > si->nextScrollTime) { + if (DC->realTime > si->nextScrollTime) { // need to scroll which is done by simulating a click to the item // this is done a bit sideways as the autoscroll "knows" that the item is a listbox // so it calls it directly Item_ListBox_HandleKey(si->item, si->scrollKey, qtrue, qfalse); - si->nextScrollTime = DC->realTime + si->adjustValue; + si->nextScrollTime = DC->realTime + si->adjustValue; } if (DC->realTime > si->nextAdjustTime) { @@ -2439,14 +2488,14 @@ itemDef_t *Menu_SetPrevCursorItem(menuDef_t *menu) { qboolean wrapped = qfalse; int oldCursor = menu->cursorItem; - + if (menu->cursorItem < 0) { menu->cursorItem = menu->itemCount-1; wrapped = qtrue; - } + } while (menu->cursorItem > -1) { - + menu->cursorItem--; if (menu->cursorItem < 0 && !wrapped) { wrapped = qtrue; @@ -2485,7 +2534,7 @@ Menu_HandleMouseMove(menu, menu->items[menu->cursorItem]->window.rect.x + 1, menu->items[menu->cursorItem]->window.rect.y + 1); return menu->items[menu->cursorItem]; } - + } menu->cursorItem = oldCursor; @@ -2550,9 +2599,9 @@ void Menus_HandleOOBClick(menuDef_t *menu, int key, qboolean down) { if (menu) { int i; - // basically the behaviour we are looking for is if there are windows in the stack.. see if - // the cursor is within any of them.. if not close them otherwise activate them and pass the - // key on.. force a mouse move to activate focus and script stuff + // basically the behaviour we are looking for is if there are windows in the stack.. see if + // the cursor is within any of them.. if not close them otherwise activate them and pass the + // key on.. force a mouse move to activate focus and script stuff if (down && menu->window.flags & WINDOW_OOB_CLICK) { Menu_RunCloseScript(menu); menu->window.flags &= ~(WINDOW_HASFOCUS | WINDOW_VISIBLE); @@ -2753,7 +2802,7 @@ if (window->border != 0) { *x += window->borderSize; *y += window->borderSize; - } + } *x += window->rect.x; *y += window->rect.y; } @@ -2807,16 +2856,16 @@ Fade(&item->window.flags, &item->window.foreColor[3], parent->fadeClamp, &item->window.nextTime, parent->fadeCycle, qtrue, parent->fadeAmount); if (item->window.flags & WINDOW_HASFOCUS) { - lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; LerpColor(parent->focusColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) { - lowLight[0] = 0.8 * item->window.foreColor[0]; - lowLight[1] = 0.8 * item->window.foreColor[1]; - lowLight[2] = 0.8 * item->window.foreColor[2]; - lowLight[3] = 0.8 * item->window.foreColor[3]; + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; LerpColor(item->window.foreColor,lowLight,*newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } else { memcpy(newColor, &item->window.foreColor, sizeof(vec4_t)); @@ -3036,15 +3085,15 @@ if (item->cvar) { DC->getCVarString(item->cvar, buff, sizeof(buff)); - } + } parent = (menuDef_t*)item->parent; if (item->window.flags & WINDOW_HASFOCUS) { - lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } else { memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); @@ -3068,10 +3117,10 @@ value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; if (item->window.flags & WINDOW_HASFOCUS) { - lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } else { memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); @@ -3091,10 +3140,10 @@ menuDef_t *parent = (menuDef_t*)item->parent; if (item->window.flags & WINDOW_HASFOCUS) { - lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } else { memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); @@ -3123,11 +3172,11 @@ { char* name; float defaultvalue; - float value; + float value; } configcvar_t; -static bind_t g_bindings[] = +static bind_t g_bindings[] = { {"+scores", K_TAB, -1, -1, -1}, {"+button2", K_ENTER, -1, -1, -1}, @@ -3283,7 +3332,7 @@ { if (g_bindings[i].bind1 != -1) - { + { DC->setBinding( g_bindings[i].bind1, g_bindings[i].command ); if (g_bindings[i].bind2 != -1) @@ -3383,10 +3432,10 @@ value = (item->cvar) ? DC->getCVarValue(item->cvar) : 0; if (item->window.flags & WINDOW_HASFOCUS) { - lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } else { memcpy(&newColor, &item->window.foreColor, sizeof(vec4_t)); @@ -3426,10 +3475,10 @@ lowLight[2] = 0.8f * 0.0f; lowLight[3] = 0.8f * 1.0f; } else { - lowLight[0] = 0.8f * parent->focusColor[0]; - lowLight[1] = 0.8f * parent->focusColor[1]; - lowLight[2] = 0.8f * parent->focusColor[2]; - lowLight[3] = 0.8f * parent->focusColor[3]; + lowLight[0] = 0.8f * parent->focusColor[0]; + lowLight[1] = 0.8f * parent->focusColor[1]; + lowLight[2] = 0.8f * parent->focusColor[2]; + lowLight[3] = 0.8f * parent->focusColor[3]; } LerpColor(parent->focusColor,lowLight,newColor,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } else { @@ -3476,7 +3525,7 @@ case K_ESCAPE: g_waitingForKey = qfalse; return qtrue; - + case K_BACKSPACE: id = BindingIDFromName(item->cvar); if (id != -1) { @@ -3536,10 +3585,10 @@ DC->setBinding( g_bindings[id].bind2, "" ); g_bindings[id].bind1 = key; g_bindings[id].bind2 = -1; - } + } } - Controls_SetConfig(qtrue); + Controls_SetConfig(qtrue); g_waitingForKey = qfalse; return qtrue; @@ -3590,7 +3639,7 @@ // calculate distance so the model nearly fills the box if (qtrue) { - float len = 0.5 * ( maxs[2] - mins[2] ); + float len = 0.5 * ( maxs[2] - mins[2] ); origin[0] = len / 0.268; // len / tan( fov/2 ) //origin[0] = len / tan(w/2); } else { @@ -3824,23 +3873,23 @@ } if (item->window.flags & WINDOW_HASFOCUS) { - lowLight[0] = 0.8 * parent->focusColor[0]; - lowLight[1] = 0.8 * parent->focusColor[1]; - lowLight[2] = 0.8 * parent->focusColor[2]; - lowLight[3] = 0.8 * parent->focusColor[3]; + lowLight[0] = 0.8 * parent->focusColor[0]; + lowLight[1] = 0.8 * parent->focusColor[1]; + lowLight[2] = 0.8 * parent->focusColor[2]; + lowLight[3] = 0.8 * parent->focusColor[3]; LerpColor(parent->focusColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } else if (item->textStyle == ITEM_TEXTSTYLE_BLINK && !((DC->realTime/BLINK_DIVISOR) & 1)) { - lowLight[0] = 0.8 * item->window.foreColor[0]; - lowLight[1] = 0.8 * item->window.foreColor[1]; - lowLight[2] = 0.8 * item->window.foreColor[2]; - lowLight[3] = 0.8 * item->window.foreColor[3]; + lowLight[0] = 0.8 * item->window.foreColor[0]; + lowLight[1] = 0.8 * item->window.foreColor[1]; + lowLight[2] = 0.8 * item->window.foreColor[2]; + lowLight[3] = 0.8 * item->window.foreColor[3]; LerpColor(item->window.foreColor,lowLight,color,0.5+0.5*sin(DC->realTime / PULSE_DIVISOR)); } if (item->cvarFlags & (CVAR_ENABLE | CVAR_DISABLE) && !Item_EnableShowViaCvar(item, CVAR_ENABLE)) { Com_Memcpy(color, parent->disableColor, sizeof(vec4_t)); } - + if (item->text) { Item_Text_Paint(item); if (item->text[0]) { @@ -3866,10 +3915,20 @@ return; } + // check for delays + for( i = 0; i < MAX_DELAYED_COMMANDS; i++ ) + { + if( delayed_cmd[ i ].item == item && delayed_cmd[ i ].time && delayed_cmd[ i ].time < DC->realTime ) + { + Item_RunScript( item, item->delayEvent ); + delayed_cmd[ i ].time = 0; + } + } + if (item->window.flags & WINDOW_ORBITING) { if (DC->realTime > item->window.nextTime) { float rx, ry, a, c, s, w, h; - + item->window.nextTime = DC->realTime + item->window.offsetTime; // translate w = item->window.rectClient.w / 2; @@ -3992,7 +4051,7 @@ return; } - // paint the rect first.. + // paint the rect first.. Window_Paint(&item->window, parent->fadeAmount , parent->fadeClamp, parent->fadeCycle); if (debugMode) { @@ -4179,7 +4238,7 @@ return; } - // FIXME: this is the whole issue of focus vs. mouse over.. + // FIXME: this is the whole issue of focus vs. mouse over.. // need a better overall solution as i don't like going through everything twice for (pass = 0; pass < 2; pass++) { for (i = 0; i < menu->itemCount; i++) { @@ -4244,7 +4303,7 @@ if (menu->window.ownerDrawFlags && DC->ownerDrawVisible && !DC->ownerDrawVisible(menu->window.ownerDrawFlags)) { return; } - + if (forcePaint) { menu->window.flags |= WINDOW_FORCED; } @@ -4598,7 +4657,7 @@ return qtrue; } -// columns sets a number of columns and an x pos and width per.. +// columns sets a number of columns and an x pos and width per.. qboolean ItemParse_columns( itemDef_t *item, int handle ) { int num, i; listBoxDef_t *listPtr; @@ -4827,6 +4886,14 @@ return qtrue; } +qboolean ItemParse_delayEvent( itemDef_t *item, int handle ) +{ + if( !PC_Script_Parse( handle, &item->delayEvent ) ) + return qfalse; + + return qtrue; +} + qboolean ItemParse_action( itemDef_t *item, int handle ) { if (!PC_Script_Parse(handle, &item->action)) { return qfalse; @@ -4918,7 +4985,7 @@ pc_token_t token; multiDef_t *multiPtr; int pass; - + Item_ValidateTypeData(item); if (!item->typeData) return qfalse; @@ -4966,7 +5033,7 @@ qboolean ItemParse_cvarFloatList( itemDef_t *item, int handle ) { pc_token_t token; multiDef_t *multiPtr; - + Item_ValidateTypeData(item); if (!item->typeData) return qfalse; @@ -5112,6 +5179,7 @@ {"mouseExit", ItemParse_mouseExit, NULL}, {"mouseEnterText", ItemParse_mouseEnterText, NULL}, {"mouseExitText", ItemParse_mouseExitText, NULL}, + {"delayEvent", ItemParse_delayEvent, NULL}, {"action", ItemParse_action, NULL}, {"special", ItemParse_special, NULL}, {"cvar", ItemParse_cvar, NULL}, @@ -5571,7 +5639,7 @@ if (*token.string != '{') { return qfalse; } - + while ( 1 ) { memset(&token, 0, sizeof(pc_token_t)); @@ -5641,7 +5709,7 @@ displayContextDef_t *Display_GetContext(void) { return DC; } - + #ifndef MISSIONPACK static float captureX; static float captureY; @@ -5661,7 +5729,7 @@ } -// FIXME: +// FIXME: qboolean Display_MouseMove(void *p, int x, int y) { int i; menuDef_t *menu = p; @@ -5703,7 +5771,7 @@ void Display_HandleKey(int key, qboolean down, int x, int y) { menuDef_t *menu = Display_CaptureItem(x, y); - if (menu == NULL) { + if (menu == NULL) { menu = Menu_GetFocused(); } if (menu) { @@ -5785,4 +5853,3 @@ } return qfalse; } - diff -Naur openarena-engine-source-0.8.x-28/code//ui/ui_shared.h openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//ui/ui_shared.h --- openarena-engine-source-0.8.x-28/code//ui/ui_shared.h 2011-12-24 12:29:41 +0000 +++ openarena-engine-source-0.8.x-28-amanieu-sv-serversidedemo-latest/code//ui/ui_shared.h 2012-01-31 23:27:25 +0000 @@ -43,7 +43,7 @@ #define WINDOW_HASFOCUS 0x00000002 // has cursor focus, exclusive #define WINDOW_VISIBLE 0x00000004 // is visible #define WINDOW_GREY 0x00000008 // is visible but grey ( non-active ) -#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc.. +#define WINDOW_DECORATION 0x00000010 // for decoration only, no mouse, keyboard, etc.. #define WINDOW_FADINGOUT 0x00000020 // fading out, non-active #define WINDOW_FADINGIN 0x00000040 // fading in #define WINDOW_MOUSEOVERTEXT 0x00000080 // mouse is over it, non exclusive @@ -61,7 +61,7 @@ #define WINDOW_AUTOWRAPPED 0x00080000 // auto wrap text #define WINDOW_FORCED 0x00100000 // forced open #define WINDOW_POPUP 0x00200000 // popup -#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set +#define WINDOW_BACKCOLORSET 0x00400000 // backcolor was explicitly set #define WINDOW_TIMEDVISIBLE 0x00800000 // visibility timing ( NOT implemented ) @@ -132,7 +132,7 @@ int border; // int ownerDraw; // ownerDraw style int ownerDrawFlags; // show flags for ownerdraw items - float borderSize; // + float borderSize; // int flags; // visible, focus, mouseover, cursor Rectangle rectEffects; // for various effects Rectangle rectEffects2; // for various effects @@ -142,7 +142,7 @@ vec4_t backColor; // border color vec4_t borderColor; // border color vec4_t outlineColor; // border color - qhandle_t background; // background asset + qhandle_t background; // background asset } windowDef_t; typedef windowDef_t Window; @@ -156,13 +156,13 @@ // FIXME: combine flags into bitfields to save space // FIXME: consolidate all of the common stuff in one structure for menus and items // THINKABOUTME: is there any compelling reason not to have items contain items -// and do away with a menu per say.. major issue is not being able to dynamically allocate -// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have +// and do away with a menu per say.. major issue is not being able to dynamically allocate +// and destroy stuff.. Another point to consider is adding an alloc free call for vm's and have // the engine just allocate the pool for it based on a cvar // many of the vars are re-used for different item types, as such they are not always named appropriately // the benefits of c++ in DOOM will greatly help crap like this // FIXME: need to put a type ptr that points to specific type info per type -// +// #define MAX_LB_COLUMNS 16 typedef struct columnInfo_s { @@ -189,10 +189,10 @@ float minVal; // edit field limits float maxVal; // float defVal; // - float range; // + float range; // int maxChars; // for edit fields int maxPaintChars; // for edit fields - int paintOffset; // + int paintOffset; // } editFieldDef_t; #define MAX_MULTI_CVARS 32 @@ -220,7 +220,7 @@ typedef struct itemDef_s { Window window; // common positional, border, style, layout info - Rectangle textRect; // rectangle the text ( if any ) consumes + Rectangle textRect; // rectangle the text ( if any ) consumes int type; // text, button, radiobutton, checkbox, textfield, listbox, combo int alignment; // left center right int textalignment; // ( optional ) alignment for text within rect based on text width @@ -234,11 +234,12 @@ const char *mouseEnterText; // mouse enter script const char *mouseExitText; // mouse exit script const char *mouseEnter; // mouse enter script - const char *mouseExit; // mouse exit script + const char *mouseExit; // mouse exit script + const char *delayEvent; // delay event script const char *action; // select script const char *onFocus; // select script const char *leaveFocus; // select script - const char *cvar; // associated cvar + const char *cvar; // associated cvar const char *cvarTest; // associated cvar for enable actions const char *enableCvar; // enable, disable, show, or hide based on value, this can contain a list int cvarFlags; // what type of action to take on cvarenables @@ -247,15 +248,15 @@ colorRangeDef_t colorRanges[MAX_COLOR_RANGES]; float special; // used for feeder id's etc.. diff per type int cursorPos; // cursor position in characters - void *typeData; // type specific data ptr's + void *typeData; // type specific data ptr's } itemDef_t; typedef struct { Window window; const char *font; // font - qboolean fullScreen; // covers entire screen + qboolean fullScreen; // covers entire screen int itemCount; // number of items; - int fontIndex; // + int fontIndex; // int cursorItem; // which item as the cursor int fadeCycle; // float fadeClamp; // @@ -267,7 +268,7 @@ vec4_t focusColor; // focus color for items vec4_t disableColor; // focus color for items - itemDef_t *items[MAX_MENUITEMS]; // items this menu contains + itemDef_t *items[MAX_MENUITEMS]; // items this menu contains } menuDef_t; typedef struct { @@ -353,7 +354,7 @@ void (*keynumToStringBuf)( int keynum, char *buf, int buflen ); void (*getBindingBuf)( int keynum, char *buf, int buflen ); void (*setBinding)( int keynum, const char *binding ); - void (*executeText)(int exec_when, const char *text ); + void (*executeText)(int exec_when, const char *text ); void (*Error)(int level, const char *error, ...); void (*Print)(const char *msg, ...); void (*Pause)(qboolean b);