diff -ru buildlog964-clipped/src/game/g_admin.c buildlog964/src/game/g_admin.c --- buildlog964-clipped/src/game/g_admin.c 2007-08-01 01:51:31.000000000 +0100 +++ buildlog964/src/game/g_admin.c 2007-08-01 01:47:50.000000000 +0100 @@ -151,6 +151,11 @@ "(^5layout^7)" }, + {"revert", G_admin_revert, "R", + "revert one or more buildlog events, optionally of only one team", + "(^5number^7)(^5a|h^7)" + }, + {"setlevel", G_admin_setlevel, "s", "sets the admin level of a player", "[^3name|slot#|admin#^7] [^3level^7]" @@ -3120,6 +3125,209 @@ return qtrue; } +qboolean G_admin_revert( gentity_t *ent, int skiparg ) +{ + int i, j = 0, repeat, len; + pTeam_t team; + qboolean force = qfalse; + gentity_t *targ; + buildHistory_t *ptr, *tmp, *mark, *prev; + vec3_t dist; + char rbuf[ 8 ], *name, *bname, *action, *article, *teamchar; + len = G_CountBuildLog(); + if( !len ) + { + ADMP( "^3!revert: ^7no build log found\n" ); + return qfalse; + } + if( G_SayArgc() >= 2 + skiparg ) + { + G_SayArgv( 1 + skiparg, rbuf, sizeof( rbuf ) ); + // this instead of atoi so rbuf[i] can be the end of the number + for( i = repeat = 0; rbuf[i] > '0' && rbuf[i] < '9'; i++ ) + { + repeat *= 10; + repeat += rbuf[i] - '0'; + } + // at least once + if( repeat == 0 ) repeat = 1; + // get a team or force indicator + switch( rbuf[i++] ) + { + case 'A': + case 'a': + team = PTE_ALIENS; + break; + case 'H': + case 'h': + team = PTE_HUMANS; + break; + case '!': + force = qtrue; // teamless force + default: + team = PTE_NONE; + } + if( rbuf[i] == '!' ) // team and force + force = qtrue; + } + else + { + // defaults + repeat = 1; + team = PTE_NONE; + } + // normalise repeat to sensible values + if( repeat > len ) repeat = len; + for( i = 0, ptr = prev = level.buildHistory; repeat > 0; repeat--, j = 0 ) + { + if( !ptr ) break; // run out of bhist + if( team != PTE_NONE && + team != BG_FindTeamForBuildable( ptr->buildable ) ) + { + // team doesn't match, so skip this ptr and reset prev + prev = ptr; + ptr = ptr->next; + // we don't want to count this one so counteract the decrement by the for + repeat++; + continue; + } + // get the ent's current or last recorded name + if( ptr->ent ) + { + if( ptr->ent->client ) + name = ptr->ent->client->pers.netname; + else + name = ""; // non-client actions + } + else + name = ptr->name; + bname = BG_FindHumanNameForBuildable( ptr->buildable ); + action = "\0"; + switch( ptr->fate ) + { + case BF_BUILT: + action = "build"; + for( j = MAX_CLIENTS, targ = g_entities + j; + j < level.num_entities; j++, targ++ ) + { + // easy checks first to save time + if( targ->s.eType != ET_BUILDABLE ) + continue; + if( targ->s.modelindex != ptr->buildable ) + continue; + VectorSubtract( targ->s.pos.trBase, + ptr->origin, dist ); + if( VectorLength( dist ) > 10 ) + continue; // 10 is somewhat arbitrary, watch for false pos/neg + // if we didn't continue then it's this one, unlink it but we can't + // free it yet, because the markdecon buildables might not place + trap_UnlinkEntity( targ ); + break; + } + // if there are marked buildables to replace, and we aren't overriding + // space check, check they can fit before acting + if( ptr->marked && !force ) + { + for( mark = ptr->marked; mark; mark = mark->marked ) + if( !G_RevertCanFit( mark ) ) + { + trap_LinkEntity( targ ); // put it back, we failed + switch( team ) + { + case PTE_ALIENS: teamchar = "a"; break; + case PTE_HUMANS: teamchar = "h"; break; + default: teamchar = ""; // this is unlikely to ever occur + } + // you only need to specify repeat if it isn't one, reflect this + if( repeat > 1 ) + Com_sprintf( rbuf, sizeof( rbuf ), "%d", repeat ); + else + *rbuf = 0; + // note that repeat has already been decremented by any + // successful loops, so the suggested command will be + // adjusted appropriately + ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would " + "conflict with another buildable, use ^3!revert %s%s! ^7to " + "override\n", action, rbuf, teamchar ) ); + return qfalse; + } + } + // if we haven't returned yet then we're good to go, free it + G_FreeEntity( targ ); + // put the marked buildables back and mark them again + if( ptr->marked ) // there may be a more efficient way of doing this + { + for( mark = ptr->marked; mark; mark = mark->marked ) + G_SpawnRevertedBuildable( mark, qtrue ); + } + break; + case BF_DECONNED: + action = "deconstruction"; + case BF_DESTROYED: + if( !*action ) action = "destruction"; + // if we're not overriding and the replacement can't fit, as before + if( !force && !G_RevertCanFit( ptr ) ) + { + switch( team ) + { + case PTE_ALIENS: teamchar = "A"; break; + case PTE_HUMANS: teamchar = "H"; break; + default: teamchar = ""; + } + if( repeat > 1 ) + Com_sprintf( rbuf, sizeof( rbuf ), "%d", repeat ); + else + Q_strncpyz( rbuf, "", 1 ); + ADMP( va( "^3!revert: ^7revert aborted: reverting this %s would " + "conflict with another buildable, use ^3!revert %s%s! ^7to " + "override\n", action, rbuf, teamchar ) ); + return qfalse; + } + // else replace it but don't mark it (it might have been marked before + // but it isn't that important) + G_SpawnRevertedBuildable( ptr, qfalse ); + break; + default: + // if this happens something has gone wrong + ADMP( "^3!revert: ^7incomplete or corrupted build log entry\n" ); + /* quarantine and dispose of the log, it's dangerous + trap_Cvar_Set( "g_buildLogMaxLength", "0" ); + G_CountBuildLog(); + */ + return qfalse; + } + if( j == level.num_entities ) + // any instance of this is technically a bug but not a major one + // a better solution would be just cutting it out of the log and moving + // on silently, but ideally they shouldn't arise at all + ADMP( "^3!revert: ^7could not find logged buildable\n" ); + else + { + // this is similar to the buildlog stuff + if( BG_FindUniqueTestForBuildable( ptr->buildable ) ) + article = "the"; + else if( strchr( "aeiouAEIOU", *bname ) ) + article = "an"; + else + article = "a"; + AP( va( "print \"%s^7 reverted %s^7'%s %s of %s %s\n\"", + ( ent ) ? ent->client->pers.netname : "console", + name, strchr( "Ss", name[ strlen(name) - 1 ] ) ? "" : "s", + action, article, bname ) ); + // remove the reverted entry + // ptr moves on, prev just readjusts ->next unless it is about to be + // freed, in which case it is forced to move on too + tmp = ptr; + if( ptr == level.buildHistory ) + prev = level.buildHistory = ptr = ptr->next; + else + prev->next = ptr = ptr->next; + G_Free( tmp ); + } + } + return qtrue; +} + /* ================ G_admin_print diff -ru buildlog964-clipped/src/game/g_admin.h buildlog964/src/game/g_admin.h --- buildlog964-clipped/src/game/g_admin.h 2007-07-29 19:06:20.000000000 +0100 +++ buildlog964/src/game/g_admin.h 2007-07-27 18:11:58.000000000 +0100 @@ -168,6 +168,7 @@ qboolean G_admin_lock( gentity_t *ent, int skiparg ); qboolean G_admin_unlock( gentity_t *ent, int skiparg ); qboolean G_admin_buildlog( gentity_t *ent, int skiparg ); +qboolean G_admin_revert( gentity_t *ent, int skiparg ); void G_admin_print( gentity_t *ent, char *m ); void G_admin_buffer_print( gentity_t *ent, char *m ); diff -ru buildlog964-clipped/src/game/g_buildable.c buildlog964/src/game/g_buildable.c --- buildlog964-clipped/src/game/g_buildable.c 2007-07-29 19:25:27.000000000 +0100 +++ buildlog964/src/game/g_buildable.c 2007-07-29 19:46:47.000000000 +0100 @@ -626,6 +626,10 @@ else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; + VectorCopy( self->s.pos.trBase, new->origin ); + VectorCopy( self->s.angles, new->angles ); + VectorCopy( self->s.origin2, new->origin2 ); + VectorCopy( self->s.angles2, new->angles2 ); new->fate = BF_DESTROYED; new->next = NULL; G_LogBuild( new ); @@ -890,6 +894,10 @@ else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; + VectorCopy( self->s.pos.trBase, new->origin ); + VectorCopy( self->s.angles, new->angles ); + VectorCopy( self->s.origin2, new->origin2 ); + VectorCopy( self->s.angles2, new->angles2 ); new->fate = BF_DESTROYED; new->next = NULL; G_LogBuild( new ); @@ -1308,6 +1316,10 @@ else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; + VectorCopy( self->s.pos.trBase, new->origin ); + VectorCopy( self->s.angles, new->angles ); + VectorCopy( self->s.origin2, new->origin2 ); + VectorCopy( self->s.angles2, new->angles2 ); new->fate = BF_DESTROYED; new->next = NULL; G_LogBuild( new ); @@ -2313,6 +2325,10 @@ else Q_strncpyz( new->name, "", 8 ); new->buildable = self->s.modelindex; + VectorCopy( self->s.pos.trBase, new->origin ); + VectorCopy( self->s.angles, new->angles ); + VectorCopy( self->s.origin2, new->origin2 ); + VectorCopy( self->s.angles2, new->angles2 ); new->fate = BF_DESTROYED; new->next = NULL; G_LogBuild( new ); @@ -2662,6 +2678,10 @@ new->ent = NULL; Q_strncpyz( new->name, "", 12 ); new->buildable = ent->s.modelindex; + VectorCopy( ent->s.pos.trBase, new->origin ); + VectorCopy( ent->s.angles, new->angles ); + VectorCopy( ent->s.origin2, new->origin2 ); + VectorCopy( ent->s.angles2, new->angles2 ); new->fate = BF_DECONNED; new->next = NULL; new->marked = NULL; @@ -3335,6 +3355,10 @@ new->ent = builder; new->name[0] = 0; new->buildable = buildable; + VectorCopy( built->s.pos.trBase, new->origin ); + VectorCopy( built->s.angles, new->angles ); + VectorCopy( built->s.origin2, new->origin2 ); + VectorCopy( built->s.angles2, new->angles2 ); new->fate = BF_BUILT; } @@ -3712,6 +3736,207 @@ /* ============ +G_InstantBuild + +This function is extremely similar to the few functions that place a +buildable on map load. It exists because G_LayoutBuildItem takes a couple +of frames to finish spawning it, so it's not truly instant +Do not call this function immediately after the map loads - that's what +G_LayoutBuildItem is for. +============ +*/ +gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, vec3_t angles, + vec3_t origin2, vec3_t angles2 ) +{ + gentity_t *builder, *built; + trace_t tr; + vec3_t dest; + + 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 ); +//old method didn't quite work out +//builder->s.modelindex = buildable; +//G_FinishSpawningBuildable( builder ); + + built = G_Build( builder, buildable, + builder->s.pos.trBase, builder->s.angles ); + G_FreeEntity( builder ); + + built->takedamage = qtrue; + built->spawned = qtrue; //map entities are already spawned + built->health = BG_FindHealthForBuildable( buildable ); + built->s.generic1 |= B_SPAWNED_TOGGLEBIT; + + // 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 ); + if( tr.startsolid ) + { + G_Printf( S_COLOR_YELLOW "G_FinishSpawningBuildable: %s startsolid at %s\n", + built->classname, vtos( built->s.origin ) ); + G_FreeEntity( built ); + return NULL; + } + + //point items in the correct direction + VectorCopy( tr.plane.normal, built->s.origin2 ); + + // allow to ride movers + built->s.groundEntityNum = tr.entityNum; + + G_SetOrigin( built, tr.endpos ); + + trap_LinkEntity( built ); + return built; +} + +/* +============ +G_SpawnRevertedBuildable + +Given a buildhistory, try to replace the lost buildable +============ +*/ +void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark ) +{ + vec3_t mins, maxs; + int i, j, blockCount, blockers[ MAX_GENTITIES ], toRecontent[ MAX_GENTITIES ]; + gentity_t *targ, *built; + + BG_FindBBoxForBuildable( bh->buildable, mins, maxs ); + VectorAdd( bh->origin, mins, mins ); + VectorAdd( bh->origin, maxs, maxs ); + blockCount = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); + for( i = j = 0; i < blockCount; i++ ) + { + targ = g_entities + blockers[ i ]; + if( targ->s.eType == ET_BUILDABLE ) + G_FreeEntity( targ ); + else if( targ->s.eType == ET_PLAYER ) + { + targ->r.contents = 0; // make it intangible + toRecontent[ j++ ] = targ-g_entities; // and remember it + } + } + level.numBuildablesForRemoval = 0; + built = G_InstantBuild( bh->buildable, bh->origin, bh->angles, + bh->origin2, bh->angles2 ); + if( built ) + { + built->r.contents = 0; + built->think = G_CommitRevertedBuildable; + built->nextthink = level.time + 50; + built->deconstruct = mark; + } + for( i = 0; i < j; i++ ) + g_entities[ toRecontent[ i ] ].r.contents = CONTENTS_BODY; +} + +/* +============ +G_CommitRevertedBuildable + +Check if there's anyone occupying me, and if not, become solid and operate as +normal. Else, try to get rid of them. +============ +*/ +void G_CommitRevertedBuildable( gentity_t *ent ) +{ + gentity_t *targ; + int i, n, occupants[ MAX_GENTITIES ]; + vec3_t mins, maxs; + VectorAdd( ent->s.origin, ent->r.mins, mins ); + VectorAdd( ent->s.origin, ent->r.maxs, maxs ); + trap_UnlinkEntity( ent ); + n = trap_EntitiesInBox( mins, maxs, occupants, MAX_GENTITIES ); + trap_LinkEntity( ent ); + if( n == 0 ) + { // we're in the clear! + ent->r.contents = MASK_PLAYERSOLID; + trap_LinkEntity( ent ); // relink + // oh dear, manual think set + switch( ent->s.modelindex ) + { + case BA_A_SPAWN: ent->think = ASpawn_Think; break; + case BA_A_BARRICADE: + case BA_A_BOOSTER: ent->think = ABarricade_Think; break; + case BA_A_ACIDTUBE: ent->think = AAcidTube_Think; break; + case BA_A_HIVE: ent->think = AHive_Think; break; + case BA_A_TRAPPER: ent->think = ATrapper_Think; break; + case BA_A_OVERMIND: ent->think = AOvermind_Think; break; + case BA_A_HOVEL: ent->think = AHovel_Think; break; + case BA_H_SPAWN: ent->think = HSpawn_Think; break; + case BA_H_MGTURRET: ent->think = HMGTurret_Think; break; + case BA_H_TESLAGEN: ent->think = HTeslaGen_Think; break; + case BA_H_ARMOURY: ent->think = HArmoury_Think; break; + case BA_H_DCC: ent->think = HDCC_Think; break; + case BA_H_MEDISTAT: ent->think = HMedistat_Think; break; + case BA_H_REACTOR: ent->think = HReactor_Think; break; + case BA_H_REPEATER: ent->think = HRepeater_Think; break; + } + ent->nextthink = + level.time + BG_FindNextThinkForBuildable( ent->s.modelindex ); + // oh if only everything was that simple + return; + } + for( i = 0; i < n; i++ ) + { + vec3_t gtfo; + targ = g_entities + occupants[ i ]; + if( targ->client ) + { + VectorSet( gtfo, crandom() * 150, crandom() * 150, random() * 150 ); + VectorAdd( targ->client->ps.velocity, gtfo, targ->client->ps.velocity ); + } + } + ent->nextthink = level.time + 50; +} + +/* +============ +G_RevertCanFit + +take a bhist and make sure you're not overwriting anything by placing it +============ +*/ +qboolean G_RevertCanFit( buildHistory_t *bh ) +{ + int i, num, blockers[ MAX_GENTITIES ]; + vec3_t mins, maxs; + gentity_t *targ; + vec3_t dist; + + BG_FindBBoxForBuildable( bh->buildable, mins, maxs ); + VectorAdd( bh->origin, mins, mins ); + VectorAdd( bh->origin, maxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, blockers, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + targ = g_entities + blockers[ i ]; + if( targ->s.eType == ET_BUILDABLE ) + { + VectorSubtract( bh->origin, targ->s.pos.trBase, dist ); + if( targ->s.modelindex == bh->buildable && VectorLength( dist ) < 10 && + targ->health <= 0 ) + continue; // it's the same buildable, hasn't blown up yet + else + return qfalse; // can't get rid of this one + } + else + continue; + } + return qtrue; +} + +/* +============ G_LayoutLoad load the layout .dat file indicated by level.layout and spawn buildables @@ -3798,7 +4023,7 @@ } int G_LogBuild( buildHistory_t *new ) -{ +{ new->next = level.buildHistory; level.buildHistory = new; return G_CountBuildLog(); @@ -3823,7 +4048,7 @@ while( mark = ptr->next ) { ptr->next = ptr->next->marked; - G_Free( mark ); + G_Free( mark ); } } } diff -ru buildlog964-clipped/src/game/g_cmds.c buildlog964/src/game/g_cmds.c --- buildlog964-clipped/src/game/g_cmds.c 2007-07-29 19:23:57.000000000 +0100 +++ buildlog964/src/game/g_cmds.c 2007-07-27 18:11:58.000000000 +0100 @@ -1851,6 +1851,10 @@ new->ent = ent; new->name[0] = 0; new->buildable = traceEnt->s.modelindex; + VectorCopy( traceEnt->s.pos.trBase, new->origin ); + VectorCopy( traceEnt->s.angles, new->angles ); + VectorCopy( traceEnt->s.origin2, new->origin2 ); + VectorCopy( traceEnt->s.angles2, new->angles2 ); new->fate = BF_DECONNED; new->next = NULL; new->marked = NULL; diff -ru buildlog964-clipped/src/game/g_local.h buildlog964/src/game/g_local.h --- buildlog964-clipped/src/game/g_local.h 2007-07-29 19:26:33.000000000 +0100 +++ buildlog964/src/game/g_local.h 2007-07-29 02:16:05.000000000 +0100 @@ -525,12 +525,18 @@ BF_DESTROYED } buildableFate_t; +// record all changes to the buildable layout - build, decon, destroy - and +// enough information to revert that change typedef struct buildHistory_s buildHistory_t; struct buildHistory_s { gentity_t *ent; // who, NULL if they've disconnected (or aren't an ent) char name[ MAX_NETNAME ]; // who, saves name if ent is NULL int buildable; // what + vec3_t origin; // where + vec3_t angles; // which way round + vec3_t origin2; // I don't know what the hell these are, but layoutsave saves + vec3_t angles2; // them so I will do the same buildableFate_t fate; // was it built, destroyed or deconned buildHistory_t *next; // next oldest change buildHistory_t *marked; // linked list of markdecon buildings taken @@ -782,6 +788,11 @@ void G_LayoutSelect( void ); void G_LayoutLoad( void ); void G_BaseSelfDestruct( pTeam_t team ); +gentity_t *G_InstantBuild( buildable_t buildable, vec3_t origin, + vec3_t angles, vec3_t origin2, vec3_t angles2 ); +void G_SpawnRevertedBuildable( buildHistory_t *bh, qboolean mark ); +void G_CommitRevertedBuildable( gentity_t *ent ); +qboolean G_RevertCanFit( buildHistory_t *bh ); int G_LogBuild( buildHistory_t *new ); int G_CountBuildLog( void );