summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/game/g_admin.c95
-rw-r--r--src/game/g_admin.h2
-rw-r--r--src/game/g_buildable.c327
-rw-r--r--src/game/g_cmds.c5
-rw-r--r--src/game/g_local.h10
-rw-r--r--src/game/g_main.c12
-rw-r--r--src/game/g_maprotation.c21
-rw-r--r--src/game/g_spawn.c4
-rw-r--r--src/game/g_svcmds.c78
-rw-r--r--src/game/g_weapon.c6
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;