diff options
Diffstat (limited to 'src/game')
-rw-r--r-- | src/game/g_admin.c | 95 | ||||
-rw-r--r-- | src/game/g_admin.h | 2 | ||||
-rw-r--r-- | src/game/g_buildable.c | 327 | ||||
-rw-r--r-- | src/game/g_cmds.c | 5 | ||||
-rw-r--r-- | src/game/g_local.h | 10 | ||||
-rw-r--r-- | src/game/g_main.c | 12 | ||||
-rw-r--r-- | src/game/g_maprotation.c | 21 | ||||
-rw-r--r-- | src/game/g_spawn.c | 4 | ||||
-rw-r--r-- | src/game/g_svcmds.c | 78 | ||||
-rw-r--r-- | src/game/g_weapon.c | 6 |
10 files changed, 533 insertions, 27 deletions
diff --git a/src/game/g_admin.c b/src/game/g_admin.c index 3767c52a..99a9a08b 100644 --- a/src/game/g_admin.c +++ b/src/game/g_admin.c @@ -74,6 +74,11 @@ g_admin_cmd_t g_admin_cmds[ ] = "display a list of all server admins and their levels", "(^5name|start admin#^7)" }, + + {"listlayouts", G_admin_listlayouts, "L", + "display a list of all available layouts for a map", + "(^5mapname^7)" + }, {"listplayers", G_admin_listplayers, "i", "display a list of players, their client numbers and their levels", @@ -84,6 +89,11 @@ g_admin_cmd_t g_admin_cmds[ ] = "lock a team to prevent anyone from joining it", "[^3a|h^7]" }, + + {"map", G_admin_map, "M", + "load a map (and optionally force layout)", + "[^3mapname^7] (^5layout^7)" + }, {"mute", G_admin_mute, "m", "mute a player", @@ -121,8 +131,8 @@ g_admin_cmd_t g_admin_cmds[ ] = }, {"restart", G_admin_restart, "r", - "restart the current map", - "" + "restart the current map (optionally using named layout)", + "(^5layout^7)" }, {"setlevel", G_admin_setlevel, "s", @@ -1875,6 +1885,33 @@ qboolean G_admin_putteam( gentity_t *ent, int skiparg ) return qtrue; } +qboolean G_admin_map( gentity_t *ent, int skiparg ) +{ + char map[ MAX_QPATH ]; + char layout[ MAX_QPATH ]; + + if( G_SayArgc() < 2 + skiparg ) + { + ADMP( "^3!map: ^7usage: !map [map] (layout)\n" ); + return qfalse; + } + + G_SayArgv( skiparg + 1, map, sizeof( map ) ); + G_SayArgv( skiparg + 2, layout, sizeof( layout ) ); + + if( !trap_FS_FOpenFile( va( "maps/%s.bsp", map ), NULL, FS_READ ) ) + { + ADMP( va( "^3!map: ^7invalid map name '%s'\n", map ) ); + return qfalse; + } + trap_Cvar_Set( "g_layouts", layout ); + trap_SendConsoleCommand( EXEC_APPEND, va( "map %s", map ) ); + AP( va( "print \"^3!map: ^7map '%s' started by %s %s\n\"", map, + ( ent ) ? ent->client->pers.netname : "console", + ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); + return qtrue; +} + qboolean G_admin_mute( gentity_t *ent, int skiparg ) { int pids[ MAX_CLIENTS ]; @@ -2011,6 +2048,50 @@ qboolean G_admin_listadmins( gentity_t *ent, int skiparg ) return qtrue; } +qboolean G_admin_listlayouts( gentity_t *ent, int skiparg ) +{ + char list[ MAX_CVAR_VALUE_STRING ]; + char map[ MAX_QPATH ]; + int count = 0; + char *s; + char layout[ MAX_QPATH ] = { "" }; + int i = 0; + + if( G_SayArgc( ) == 2 + skiparg ) + G_SayArgv( 1 +skiparg, map, sizeof( map ) ); + else + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + + count = G_LayoutList( map, list, sizeof( list ) ); + if( !count ) + { + ADMP( va( "^3!listlayouts:^7 ^7no layouts found for map '%s'\n", map ) ); + return qfalse; + } + ADMBP_begin( ); + ADMBP( va( "^3!listlayouts:^7 %d layouts found for '%s':\n", count, map ) ); + s = &list[ 0 ]; + while( *s ) + { + if( *s == ' ' ) + { + ADMBP( va ( " %s\n", layout ) ); + layout[ 0 ] = '\0'; + i = 0; + } + else if( i < sizeof( layout ) - 2 ) + { + layout[ i++ ] = *s; + layout[ i ] = '\0'; + } + s++; + } + if( layout[ 0 ] ) + ADMBP( va ( " %s\n", layout ) ); + ADMBP_end( ); + return qtrue; +} + qboolean G_admin_listplayers( gentity_t *ent, int skiparg ) { int i, j; @@ -2539,12 +2620,14 @@ qboolean G_admin_rename( gentity_t *ent, int skiparg ) qboolean G_admin_restart( gentity_t *ent, int skiparg ) { - char command[ MAX_ADMIN_CMD_LEN ]; + char layout[ MAX_CVAR_VALUE_STRING ]; - G_SayArgv( skiparg, command, sizeof( command ) ); + G_SayArgv( skiparg + 1, layout, sizeof( layout ) ); + trap_Cvar_Set( "g_layouts", layout ); trap_SendConsoleCommand( EXEC_APPEND, "map_restart" ); - AP( va( "print \"^3!restart: ^7map restarted by %s\n\"", - ( ent ) ? ent->client->pers.netname : "console" ) ); + AP( va( "print \"^3!restart: ^7map restarted by %s %s\n\"", + ( ent ) ? ent->client->pers.netname : "console", + ( layout[ 0 ] ) ? va( "(forcing layout '%s')", layout ) : "" ) ); return qtrue; } diff --git a/src/game/g_admin.h b/src/game/g_admin.h index c91fc42a..7958bda3 100644 --- a/src/game/g_admin.h +++ b/src/game/g_admin.h @@ -148,7 +148,9 @@ qboolean G_admin_ban( gentity_t *ent, int skiparg ); qboolean G_admin_unban( gentity_t *ent, int skiparg ); qboolean G_admin_putteam( gentity_t *ent, int skiparg ); qboolean G_admin_listadmins( gentity_t *ent, int skiparg ); +qboolean G_admin_listlayouts( gentity_t *ent, int skiparg ); qboolean G_admin_listplayers( gentity_t *ent, int skiparg ); +qboolean G_admin_map( gentity_t *ent, int skiparg ); qboolean G_admin_mute( gentity_t *ent, int skiparg ); qboolean G_admin_showbans( gentity_t *ent, int skiparg ); qboolean G_admin_help( gentity_t *ent, int skiparg ); diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index f10251cf..8bf17b24 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -2925,6 +2925,43 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori built->biteam = built->s.modelindex2 = BG_FindTeamForBuildable( buildable ); BG_FindBBoxForBuildable( buildable, built->r.mins, built->r.maxs ); + + // detect the buildable's normal vector + if( !builder->client ) + { + // initial base layout created by server + + if( builder->s.origin2[ 0 ] + || builder->s.origin2[ 1 ] + || builder->s.origin2[ 2 ] ) + { + VectorCopy( builder->s.origin2, normal ); + } + else if( BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY ) + VectorSet( normal, 0.0f, 0.0f, -1.0f ); + else + VectorSet( normal, 0.0f, 0.0f, 1.0f ); + } + else + { + // in-game building by a player + + if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) + { + if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) + VectorSet( normal, 0.0f, 0.0f, -1.0f ); + else + VectorCopy( builder->client->ps.grapplePoint, normal ); + } + else + VectorSet( normal, 0.0f, 0.0f, 1.0f ); + } + + // when building the initial layout, spawn the entity slightly off its + // target surface so that it can be "dropped" onto it + if( !builder->client ) + VectorMA( origin, 1.0f, normal, origin ); + built->health = 1; built->splashDamage = BG_FindSplashDamageForBuildable( buildable ); @@ -2937,6 +2974,14 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori built->spawned = qfalse; built->buildTime = built->s.time = level.time; + // build instantly in cheat mode + if( builder->client && g_cheats.integer ) + { + built->health = BG_FindHealthForBuildable( buildable ); + built->buildTime = built->s.time = + level.time - BG_FindBuildTimeForBuildable( buildable ); + } + //things that vary for each buildable that aren't in the dbase switch( buildable ) { @@ -3052,6 +3097,13 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori built->builtBy = -1; G_SetOrigin( built, origin ); + + // gently nudge the buildable onto the surface :) + VectorScale( normal, -50.0f, built->s.pos.trDelta ); + + // set turret angles + VectorCopy( builder->s.angles2, built->s.angles2 ); + VectorCopy( angles, built->s.angles ); built->s.angles[ PITCH ] = 0.0f; built->s.angles2[ YAW ] = angles[ YAW ]; @@ -3060,19 +3112,6 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori built->physicsBounce = BG_FindBounceForBuildable( buildable ); built->s.groundEntityNum = -1; - if( builder->client && builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBING ) - { - if( builder->client->ps.stats[ STAT_STATE ] & SS_WALLCLIMBINGCEILING ) - VectorSet( normal, 0.0f, 0.0f, -1.0f ); - else - VectorCopy( builder->client->ps.grapplePoint, normal ); - - //gently nudge the buildable onto the surface :) - VectorScale( normal, -50.0f, built->s.pos.trDelta ); - } - else - VectorSet( normal, 0.0f, 0.0f, 1.0f ); - built->s.generic1 = (int)( ( (float)built->health / (float)BG_FindHealthForBuildable( buildable ) ) * B_HEALTH_MASK ); @@ -3229,11 +3268,9 @@ static void G_FinishSpawningBuildable( gentity_t *ent ) built->health = BG_FindHealthForBuildable( buildable ); built->s.generic1 |= B_SPAWNED_TOGGLEBIT; - // drop to floor - if( buildable != BA_NONE && BG_FindTrajectoryForBuildable( buildable ) == TR_BUOYANCY ) - VectorSet( dest, built->s.origin[ 0 ], built->s.origin[ 1 ], built->s.origin[ 2 ] + 4096 ); - else - VectorSet( dest, built->s.origin[ 0 ], built->s.origin[ 1 ], built->s.origin[ 2 ] - 4096 ); + // drop towards normal surface + VectorScale( built->s.origin2, -4096.0f, dest ); + VectorAdd( dest, built->s.origin, dest ); trap_Trace( &tr, built->s.origin, built->r.mins, built->r.maxs, dest, built->s.number, built->clipmask ); @@ -3275,3 +3312,257 @@ void G_SpawnBuildable( gentity_t *ent, buildable_t buildable ) ent->nextthink = level.time + FRAMETIME * 2; ent->think = G_FinishSpawningBuildable; } + +/* +============ +G_LayoutSave + +============ +*/ +void G_LayoutSave( char *name ) +{ + char map[ MAX_QPATH ]; + char fileName[ MAX_OSPATH ]; + fileHandle_t f; + int len; + int i; + gentity_t *ent; + char *s; + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + if( !map[ 0 ] ) + { + G_Printf( "LayoutSave( ): no map is loaded\n" ); + return; + } + Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, name ); + + len = trap_FS_FOpenFile( fileName, &f, FS_WRITE ); + if( len < 0 ) + { + G_Printf( "layoutsave: could not open %s\n", fileName ); + return; + } + + G_Printf("layoutsave: saving layout to %s\n", fileName ); + + for( i = MAX_CLIENTS; i < level.num_entities; i++ ) + { + ent = &level.gentities[ i ]; + if( ent->s.eType != ET_BUILDABLE ) + continue; + + s = va( "%i %f %f %f %f %f %f %f %f %f %f %f %f\n", + ent->s.modelindex, + ent->s.pos.trBase[ 0 ], + ent->s.pos.trBase[ 1 ], + ent->s.pos.trBase[ 2 ], + ent->s.angles[ 0 ], + ent->s.angles[ 1 ], + ent->s.angles[ 2 ], + ent->s.origin2[ 0 ], + ent->s.origin2[ 1 ], + ent->s.origin2[ 2 ], + ent->s.angles2[ 0 ], + ent->s.angles2[ 1 ], + ent->s.angles2[ 2 ] ); + trap_FS_Write( s, strlen( s ), f ); + } + trap_FS_FCloseFile( f ); +} + +int G_LayoutList( const char *map, char *list, int len ) +{ + // up to 128 single character layout names could fit in layouts + char fileList[ ( MAX_CVAR_VALUE_STRING / 2 ) * 5 ] = {""}; + char layouts[ MAX_CVAR_VALUE_STRING ] = {""}; + int numFiles, i, fileLen = 0, listLen; + int count = 0; + char *filePtr; + + numFiles = trap_FS_GetFileList( va( "layouts/%s", map ), ".dat", + fileList, sizeof( fileList ) ); + filePtr = fileList; + for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + listLen = strlen( layouts ); + if( fileLen < 5 ) + continue; + + // list is full, stop trying to add to it + if( ( listLen + fileLen ) >= sizeof( layouts ) ) + break; + + Q_strcat( layouts, sizeof( layouts ), filePtr ); + listLen = strlen( layouts ); + + // strip extension and add space delimiter + layouts[ listLen - 4 ] = ' '; + layouts[ listLen - 3 ] = '\0'; + count++; + } + if( count != numFiles ) + { + G_Printf( S_COLOR_YELLOW "WARNING: layout list was truncated to %d " + "layouts, but %d layout files exist in layouts/%s/.\n", + count, numFiles, map ); + } + Q_strncpyz( list, layouts, len ); + return count; +} + +/* +============ +G_LayoutSelect + +set level.layout based on g_layouts or g_layoutAuto +============ +*/ +void G_LayoutSelect( void ) +{ + char fileName[ MAX_OSPATH ]; + char layouts[ MAX_CVAR_VALUE_STRING ]; + char layouts2[ MAX_CVAR_VALUE_STRING ]; + char *l; + char map[ MAX_QPATH ]; + char *s; + int cnt = 0; + int layoutNum; + + Q_strncpyz( layouts, g_layouts.string, sizeof( layouts ) ); + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + + // one time use cvar + trap_Cvar_Set( "g_layouts", "" ); + + // pick an included layout at random if no list has been provided + if( !layouts[ 0 ] && g_layoutAuto.integer ) + { + G_LayoutList( map, layouts, sizeof( layouts ) ); + } + + if( !layouts[ 0 ] ) + return; + + Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) ); + l = &layouts2[ 0 ]; + layouts[ 0 ] = '\0'; + s = COM_ParseExt( &l, qfalse ); + while( *s ) + { + Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/%s.dat", map, s ); + if( trap_FS_FOpenFile( fileName, NULL, FS_READ ) > 0 ) + { + Q_strcat( layouts, sizeof( layouts ), s ); + Q_strcat( layouts, sizeof( layouts ), " " ); + cnt++; + } + else + G_Printf( S_COLOR_YELLOW "WARNING: layout \"%s\" does not exist\n", s ); + s = COM_ParseExt( &l, qfalse ); + } + if( !cnt ) + { + G_Printf( S_COLOR_RED "ERROR: none of the specified layouts could be " + "found, using map default\n" ); + return; + } + layoutNum = ( rand( ) & ( cnt - 1 ) ) + 1; + cnt = 0; + + Q_strncpyz( layouts2, layouts, sizeof( layouts2 ) ); + l = &layouts2[ 0 ]; + s = COM_ParseExt( &l, qfalse ); + while( *s ) + { + Q_strncpyz( level.layout, s, sizeof( level.layout ) ); + cnt++; + if( cnt >= layoutNum ) + break; + s = COM_ParseExt( &l, qfalse ); + } + G_Printf("using layout \"%s\" from list ( %s)\n", level.layout, layouts ); +} + +static void G_LayoutBuildItem( buildable_t buildable, vec3_t origin, + vec3_t angles, vec3_t origin2, vec3_t angles2 ) +{ + gentity_t *builder; + + builder = G_Spawn( ); + builder->client = 0; + VectorCopy( origin, builder->s.pos.trBase ); + VectorCopy( angles, builder->s.angles ); + VectorCopy( origin2, builder->s.origin2 ); + VectorCopy( angles2, builder->s.angles2 ); + G_SpawnBuildable( builder, buildable ); +} + +/* +============ +G_LayoutLoad + +load the layout .dat file indicated by level.layout and spawn buildables +as if a builder was creating them +============ +*/ +void G_LayoutLoad( void ) +{ + fileHandle_t f; + int len; + char *layout; + char map[ MAX_QPATH ]; + int buildable = BA_NONE; + vec3_t origin = { 0.0f, 0.0f, 0.0f }; + vec3_t angles = { 0.0f, 0.0f, 0.0f }; + vec3_t origin2 = { 0.0f, 0.0f, 0.0f }; + vec3_t angles2 = { 0.0f, 0.0f, 0.0f }; + char line[ MAX_STRING_CHARS ]; + int i = 0; + + if( !level.layout[ 0 ] ) + return; + + trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); + len = trap_FS_FOpenFile( va( "layouts/%s/%s.dat", map, level.layout ), + &f, FS_READ ); + if( len < 0 ) + { + G_Printf( "ERROR: layout %s could not be opened\n", level.layout ); + return; + } + layout = G_Alloc( len + 1 ); + trap_FS_Read( layout, len, f ); + *( layout + len ) = '\0'; + trap_FS_FCloseFile( f ); + while( *layout ) + { + if( i >= sizeof( line ) - 1 ) + { + G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n", + va( "layouts/%s/%s.dat", map, level.layout ), line ); + return; + } + line[ i++ ] = *layout; + line[ i ] = '\0'; + if( *layout == '\n' ) + { + i = 0; + sscanf( line, "%d %f %f %f %f %f %f %f %f %f %f %f %f\n", + &buildable, + &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], + &angles[ 0 ], &angles[ 1 ], &angles[ 2 ], + &origin2[ 0 ], &origin2[ 1 ], &origin2[ 2 ], + &angles2[ 0 ], &angles2[ 1 ], &angles2[ 2 ] ); + + if( buildable > BA_NONE && buildable < BA_NUM_BUILDABLES ) + G_LayoutBuildItem( buildable, origin, angles, origin2, angles2 ); + else + G_Printf( S_COLOR_YELLOW "WARNING: bad buildable number (%d) in " + " layout. skipping\n", buildable ); + } + layout++; + } +} + diff --git a/src/game/g_cmds.c b/src/game/g_cmds.c index a52bc37d..fc5fff19 100644 --- a/src/game/g_cmds.c +++ b/src/game/g_cmds.c @@ -1809,8 +1809,9 @@ void Cmd_Destroy_f( gentity_t *ent, qboolean deconstruct ) else G_FreeEntity( traceEnt ); - ent->client->ps.stats[ STAT_MISC ] += - BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2; + if( !g_cheats.integer ) + ent->client->ps.stats[ STAT_MISC ] += + BG_FindBuildDelayForWeapon( ent->s.weapon ) >> 2; } } } diff --git a/src/game/g_local.h b/src/game/g_local.h index 8d9b698b..7e96d656 100644 --- a/src/game/g_local.h +++ b/src/game/g_local.h @@ -650,6 +650,8 @@ typedef struct int unlaggedIndex; int unlaggedTimes[ MAX_UNLAGGED_MARKERS ]; + + char layout[ MAX_QPATH ]; } level_locals_t; // @@ -734,6 +736,10 @@ void G_SetBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim void G_SetIdleBuildableAnim( gentity_t *ent, buildableAnimNumber_t anim ); void G_SpawnBuildable(gentity_t *ent, buildable_t buildable); void FinishSpawningBuildable( gentity_t *ent ); +void G_LayoutSave( char *name ); +int G_LayoutList( const char *map, char *list, int len ); +void G_LayoutSelect( void ); +void G_LayoutLoad( void ); // // g_utils.c @@ -1019,6 +1025,7 @@ typedef struct mapRotationEntry_s char name[ MAX_QPATH ]; char postCmds[ MAX_MAP_COMMANDS ][ MAX_STRING_CHARS ]; + char layouts[ MAX_CVAR_VALUE_STRING ]; int numCmds; mapRotationCondition_t conditions[ MAX_MAP_ROTATION_CONDS ]; @@ -1144,6 +1151,9 @@ extern vmCvar_t g_shove; extern vmCvar_t g_mapConfigs; +extern vmCvar_t g_layouts; +extern vmCvar_t g_layoutAuto; + extern vmCvar_t g_admin; extern vmCvar_t g_adminLog; extern vmCvar_t g_adminParseSay; diff --git a/src/game/g_main.c b/src/game/g_main.c index b4eea97b..16b40c0c 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -120,6 +120,9 @@ vmCvar_t g_shove; vmCvar_t g_mapConfigs; vmCvar_t g_chatTeamPrefix; +vmCvar_t g_layouts; +vmCvar_t g_layoutAuto; + vmCvar_t g_admin; vmCvar_t g_adminLog; vmCvar_t g_adminParseSay; @@ -234,6 +237,9 @@ static cvarTable_t gameCvarTable[ ] = { &g_mapConfigs, "g_mapConfigs", "", CVAR_ARCHIVE, 0, qfalse }, { NULL, "g_mapConfigsLoaded", "0", CVAR_ROM, 0, qfalse }, + { &g_layouts, "g_layouts", "", CVAR_LATCH, 0, qfalse }, + { &g_layoutAuto, "g_layoutAuto", "1", CVAR_ARCHIVE, 0, qfalse }, + { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse }, { &g_adminLog, "g_adminLog", "admin.log", CVAR_ARCHIVE, 0, qfalse }, { &g_adminParseSay, "g_adminParseSay", "1", CVAR_ARCHIVE, 0, qfalse }, @@ -590,9 +596,15 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) trap_SetConfigstring( CS_INTERMISSION, "0" ); + // test to see if a custom buildable layout will be loaded + G_LayoutSelect( ); + // parse the key/value pairs and spawn gentities G_SpawnEntitiesFromString( ); + // load up a custom building layout if there is one + G_LayoutLoad( ); + // the map might disable some things BG_InitAllowedGameElements( ); diff --git a/src/game/g_maprotation.c b/src/game/g_maprotation.c index ad4e5cf4..c3a8d8a6 100644 --- a/src/game/g_maprotation.c +++ b/src/game/g_maprotation.c @@ -52,6 +52,19 @@ static qboolean G_ParseMapCommandSection( mapRotationEntry_t *mre, char **text_p if( !Q_stricmp( token, "}" ) ) return qtrue; //reached the end of this command section + if( !Q_stricmp( token, "layouts" ) ) + { + token = COM_ParseExt( text_p, qfalse ); + mre->layouts[ 0 ] = '\0'; + while( token && token[ 0 ] != 0 ) + { + Q_strcat( mre->layouts, sizeof( mre->layouts ), token ); + Q_strcat( mre->layouts, sizeof( mre->layouts ), " " ); + token = COM_ParseExt( text_p, qfalse ); + } + continue; + } + Q_strncpyz( mre->postCmds[ mre->numCmds ], token, sizeof( mre->postCmds[ 0 ] ) ); Q_strcat( mre->postCmds[ mre->numCmds ], sizeof( mre->postCmds[ 0 ] ), " " ); @@ -476,6 +489,14 @@ static void G_IssueMapChange( int rotation ) int map = G_GetCurrentMap( rotation ); char cmd[ MAX_TOKEN_CHARS ]; + // allow a manually defined g_layouts setting to override the maprotation + if( !g_layouts.string[ 0 ] && + mapRotations.rotations[ rotation ].maps[ map ].layouts[ 0 ] ) + { + trap_Cvar_Set( "g_layouts", + mapRotations.rotations[ rotation ].maps[ map ].layouts ); + } + trap_SendConsoleCommand( EXEC_APPEND, va( "map %s\n", mapRotations.rotations[ rotation ].maps[ map ].name ) ); diff --git a/src/game/g_spawn.c b/src/game/g_spawn.c index 3e0e1812..aee0cfd5 100644 --- a/src/game/g_spawn.c +++ b/src/game/g_spawn.c @@ -327,6 +327,10 @@ qboolean G_CallSpawn( gentity_t *ent ) //check buildable spawn functions if( ( buildable = BG_FindBuildNumForEntityName( ent->classname ) ) != BA_NONE ) { + // don't spawn built-in buildings if we are using a custom layout + if( level.layout[ 0 ] ) + return qtrue; + if( buildable == BA_A_SPAWN || buildable == BA_H_SPAWN ) { ent->s.angles[ YAW ] += 180.0f; diff --git a/src/game/g_svcmds.c b/src/game/g_svcmds.c index b1cc89c1..6ede44be 100644 --- a/src/game/g_svcmds.c +++ b/src/game/g_svcmds.c @@ -475,9 +475,75 @@ void Svcmd_ForceTeam_f( void ) //FIXME: tremulise this } +/* +=================== +Svcmd_LayoutSave_f + +layoutsave <name> +=================== +*/ +void Svcmd_LayoutSave_f( void ) +{ + char str[ MAX_QPATH ]; + char str2[ MAX_QPATH - 4 ]; + char *s; + int i = 0; + + if( trap_Argc( ) != 2 ) + { + G_Printf( "usage: layoutsave LAYOUTNAME\n" ); + return; + } + trap_Argv( 1, str, sizeof( str ) ); + + // sanitize name + s = &str[ 0 ]; + while( *s && i < sizeof( str2 ) - 1 ) + { + if( ( *s >= '0' && *s <= '9' ) || + ( *s >= 'a' && *s <= 'z' ) || + ( *s >= 'A' && *s <= 'Z' ) ) + { + str2[ i++ ] = *s; + str2[ i ] = '\0'; + } + s++; + } + + if( !str2[ 0 ] ) + { + G_Printf("layoutsave: invalid name \"%s\"\n", str ); + return; + } + + G_LayoutSave( str2 ); +} + char *ConcatArgs( int start ); /* +=================== +Svcmd_LayoutLoad_f + +layoutload [<name> [<name2> [<name3 [...]]]] + +This is just a silly alias for doing: + set g_layouts "name name2 name3" + map_restart +=================== +*/ +void Svcmd_LayoutLoad_f( void ) +{ + char layouts[ MAX_CVAR_VALUE_STRING ]; + char *s; + + s = ConcatArgs( 1 ); + Q_strncpyz( layouts, s, sizeof( layouts ) ); + trap_Cvar_Set( "g_layouts", layouts ); + trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); +} + +/* ================= ConsoleCommand @@ -576,6 +642,18 @@ qboolean ConsoleCommand( void ) return qtrue; } + + if( !Q_stricmp( cmd, "layoutsave" ) ) + { + Svcmd_LayoutSave_f( ); + return qtrue; + } + + if( !Q_stricmp( cmd, "layoutload" ) ) + { + Svcmd_LayoutLoad_f( ); + return qtrue; + } // see if this is a a admin command if( G_admin_cmd_check( NULL, qfalse ) ) diff --git a/src/game/g_weapon.c b/src/game/g_weapon.c index e6605743..2e8481c3 100644 --- a/src/game/g_weapon.c +++ b/src/game/g_weapon.c @@ -759,7 +759,11 @@ void buildFire( gentity_t *ent, dynMenu_t menu ) if( G_BuildIfValid( ent, ent->client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) ) { - if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) ) + if( g_cheats.integer ) + { + ent->client->ps.stats[ STAT_MISC ] = 0; + } + else if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && !G_IsOvermindBuilt( ) ) { ent->client->ps.stats[ STAT_MISC ] += BG_FindBuildDelayForWeapon( ent->s.weapon ) * 2; |