diff options
Diffstat (limited to 'src/game/g_buildable.c')
-rw-r--r-- | src/game/g_buildable.c | 1038 |
1 files changed, 853 insertions, 185 deletions
diff --git a/src/game/g_buildable.c b/src/game/g_buildable.c index 3290ccc..5a2698b 100644 --- a/src/game/g_buildable.c +++ b/src/game/g_buildable.c @@ -513,6 +513,54 @@ static void freeBuildable( gentity_t *self ) G_FreeEntity( self ); } +/* +================ +G_RecoverBuildPoints + +Schedule build points for delayed recovery when a buildable dies + credit to Mercenaries Guild dev team (mgdev) for the whole idea +================ +*/ +void G_RecoverBuildPoints( gentity_t *self ) +{ + int team; + int value; + + if( g_buildPointsRecoverRate.integer < 1 ) + return; + + if( self->killedBy != ENTITYNUM_NONE ) + return; + + team = BG_FindTeamForBuildable( self->s.modelindex ); + value = BG_FindBuildPointsForBuildable( self->s.modelindex ); + if( team == BIT_ALIENS ) + { + if( !level.alienRecoverBuildPoints ) + level.alienRecoverTime = level.time + 60000 / g_buildPointsRecoverRate.integer; + level.alienRecoverBuildPoints += value; + } + else if( team == BIT_HUMANS ) + { + if( !level.humanRecoverBuildPoints ) + level.humanRecoverTime = level.time + 60000 / g_buildPointsRecoverRate.integer; + level.humanRecoverBuildPoints += value; + } + + self->killedBy = ENTITYNUM_NONE; +} + +static void G_RecoverSetKiller( gentity_t *self, gentity_t *attacker ) +{ + if( g_buildPointsRecoverRate.integer < 1 ) + return; + + // note attacker if teamkill + if( attacker && attacker->client && + attacker->client->ps.stats[ STAT_PTEAM ] == BG_FindTeamForBuildable( self->s.modelindex ) ) + self->killedBy = attacker - g_entities; +} + //================================================================================== @@ -531,6 +579,7 @@ void A_CreepRecede( gentity_t *self ) if( !( self->s.eFlags & EF_DEAD ) ) { self->s.eFlags |= EF_DEAD; + G_RecoverBuildPoints( self ); G_AddEvent( self, EV_BUILD_DESTROY, 0 ); if( self->spawned ) @@ -573,6 +622,7 @@ void ASpawn_Melt( gentity_t *self ) if( !( self->s.eFlags & EF_DEAD ) ) { self->s.eFlags |= EF_DEAD; + G_RecoverBuildPoints( self ); G_AddEvent( self, EV_BUILD_DESTROY, 0 ); if( self->spawned ) @@ -631,6 +681,7 @@ void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; @@ -658,6 +709,8 @@ void ASpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->s.eFlags &= ~EF_FIRING; //prevent any firing effects + G_RecoverSetKiller( self, attacker ); + if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) @@ -796,6 +849,18 @@ void AOvermind_Think( gentity_t *self ) vec3_t mins, maxs; int i, num; gentity_t *enemy; + float om_range = OVERMIND_ATTACK_RANGE; + float om_damage = self->splashDamage; + float om_splash = self->splashRadius; + + if( g_modMainStrength.integer > 0 ) + { + om_range = om_range * g_modMainStrength.value / 100; + range[0] = range[1] = range[2] = om_range; + + om_damage = om_damage * g_modMainStrength.value / 100; + om_splash = om_splash * g_modMainStrength.value / 100; + } VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); @@ -814,8 +879,8 @@ void AOvermind_Think( gentity_t *self ) if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) { self->timestamp = level.time; - G_SelectiveRadiusDamage( self->s.pos.trBase, self, self->splashDamage, - self->splashRadius, self, MOD_OVERMIND, PTE_ALIENS ); + G_SelectiveRadiusDamage( self->s.pos.trBase, self, om_damage, + om_splash, self, MOD_OVERMIND, PTE_ALIENS ); G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); } } @@ -940,6 +1005,7 @@ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; @@ -966,6 +1032,8 @@ void ABarricade_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, else self->nextthink = level.time; //blast immediately + G_RecoverSetKiller( self, attacker ); + if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) @@ -1031,13 +1099,18 @@ void AAcidTube_Damage( gentity_t *self ) { if( self->spawned ) { + int repeatRate = ACIDTUBE_REPEAT; + if( !( self->s.eFlags & EF_FIRING ) ) { self->s.eFlags |= EF_FIRING; G_AddEvent( self, EV_ALIEN_ACIDTUBE, DirToByte( self->s.origin2 ) ); } - if( ( self->timestamp + ACIDTUBE_REPEAT ) > level.time ) + if( g_modBuildableSpeed.integer > 0 ) + repeatRate = repeatRate * 100 / g_modBuildableSpeed.integer; + + if( ( self->timestamp + repeatRate ) > level.time ) self->think = AAcidTube_Damage; else { @@ -1096,7 +1169,8 @@ void AAcidTube_Think( gentity_t *self ) if( !G_Visible( self, enemy ) ) continue; - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( enemy->client && + ( enemy->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS || enemy->client->pers.bleeder ) ) { if( level.paused || enemy->client->pers.paused ) continue; @@ -1389,6 +1463,7 @@ void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; @@ -1447,6 +1522,8 @@ void AHovel_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink + G_RecoverSetKiller( self, attacker ); + if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) @@ -1502,6 +1579,18 @@ void ABooster_Touch( gentity_t *self, gentity_t *other, trace_t *trace ) if( client && client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) return; + if( client->pers.bleeder ) + { + if( !(client->ps.stats[ STAT_STATE ] & SS_POISONCLOUDED ) ) + { + client->ps.stats[ STAT_STATE ] |= SS_POISONCLOUDED; + client->lastPoisonCloudedTime = level.time; + trap_SendServerCommand( client->ps.clientNum, "poisoncloud" ); + trap_SendServerCommand( client->ps.clientNum, "print \"Your booster has poisoned you\n\"" ); + } + return; + } + //only allow boostage once every 30 seconds if( client->lastBoostedTime + BOOSTER_INTERVAL > level.time ) return; @@ -1772,6 +1861,15 @@ void HReactor_Think( gentity_t *self ) vec3_t mins, maxs; int i, num; gentity_t *enemy, *tent; + float rc_range = REACTOR_ATTACK_RANGE; + float rc_damage = REACTOR_ATTACK_DAMAGE; + + if( g_modMainStrength.integer > 0 ) + { + rc_range = rc_range * g_modMainStrength.value / 100; + rc_damage = rc_damage * g_modMainStrength.value / 100; + range[0] = range[1] = range[2] = rc_range; + } VectorAdd( self->s.origin, range, maxs ); VectorSubtract( self->s.origin, range, mins ); @@ -1792,8 +1890,8 @@ void HReactor_Think( gentity_t *self ) if( level.paused || enemy->client->pers.paused ) continue; self->timestamp = level.time; - G_SelectiveRadiusDamage( self->s.pos.trBase, self, REACTOR_ATTACK_DAMAGE, - REACTOR_ATTACK_RANGE, self, MOD_REACTOR, PTE_HUMANS ); + G_SelectiveRadiusDamage( self->s.pos.trBase, self, rc_damage, + rc_range, self, MOD_REACTOR, PTE_HUMANS ); tent = G_TempEntity( enemy->s.pos.trBase, EV_TESLATRAIL ); @@ -1943,6 +2041,7 @@ void HMedistat_Think( gentity_t *self ) { if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && player->client->ps.pm_type != PM_DEAD && + !player->client->pers.bleeder && self->enemy == player ) occupied = qtrue; } @@ -1960,7 +2059,8 @@ void HMedistat_Think( gentity_t *self ) if( player->flags & FL_NOTARGET ) continue; // notarget cancels even beneficial effects? - if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS ) + if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && + !player->client->pers.bleeder ) { if( player->health < player->client->ps.stats[ STAT_MAX_HEALTH ] && player->client->ps.pm_type != PM_DEAD ) @@ -1980,6 +2080,34 @@ void HMedistat_Think( gentity_t *self ) } } + // bleeding spree retribution + if( level.bleeders && !self->enemy ) + { + //look for something to hurt + for( i = 0; i < num; i++ ) + { + player = &g_entities[ entityList[ i ] ]; + + if( player->client && player->client->ps.stats[ STAT_PTEAM ] == PTE_HUMANS && player->client->pers.bleeder ) + { + if( player->health > 0 && + player->client->ps.pm_type != PM_DEAD ) + { + self->enemy = player; + + //start the heal anim + if( !self->active ) + { + G_SetBuildableAnim( self, BANIM_ATTACK1, qfalse ); + self->active = qtrue; + } + } + if( BG_InventoryContainsUpgrade( UP_MEDKIT, player->client->ps.stats ) ) + BG_RemoveUpgradeFromInventory( UP_MEDKIT, player->client->ps.stats ); + } + } + } + //nothing left to heal so go back to idling if( !self->enemy && self->active ) { @@ -1990,6 +2118,12 @@ void HMedistat_Think( gentity_t *self ) } else if( self->enemy ) //heal! { + if( self->enemy->client->pers.bleeder ) + { + G_Damage( self->enemy, NULL, NULL, NULL, NULL, 10, 0, MOD_SLIME ); + return; + } + if( self->enemy->client && self->enemy->client->ps.stats[ STAT_STATE ] & SS_POISONED ) self->enemy->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; @@ -2147,7 +2281,8 @@ qboolean HMGTurret_CheckTarget( gentity_t *self, gentity_t *target, qboolean ign if( !traceEnt->client ) return qfalse; - if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS ) + if( traceEnt->client && traceEnt->client->ps.stats[ STAT_PTEAM ] != PTE_ALIENS && + !traceEnt->client->pers.bleeder ) return qfalse; return qtrue; @@ -2211,6 +2346,24 @@ void HMGTurret_FindEnemy( gentity_t *self ) } } + // bleeder retribution + if( level.bleeders ) + { + for( i = 0; i < num; i++ ) + { + target = &g_entities[ entityList[ i ] ]; + + if( target->client && target->client->pers.bleeder ) + { + if( !HMGTurret_CheckTarget( self, target, qfalse ) ) + continue; + + self->enemy = target; + return; + } + } + } + //couldn't find a target self->enemy = NULL; } @@ -2239,7 +2392,7 @@ void HMGTurret_Think( gentity_t *self ) //if not powered don't do anything and check again for power next think if( !( self->powered = G_FindPower( self ) ) ) { - if( self->spawned ) + if( g_turretAim.integer && self->spawned ) { // unpowered turret barrel falls to bottom of range float droop; @@ -2254,7 +2407,7 @@ void HMGTurret_Think( gentity_t *self ) return; } } - + self->nextthink = level.time + POWER_REFRESH_TIME; return; } @@ -2275,9 +2428,24 @@ void HMGTurret_Think( gentity_t *self ) //if a new target cannot be found don't do anything if( !self->enemy ) + { + if( g_turretAim.integer && + self->rotatorAngle >= 360.0f && + level.time > self->last_move_time + TURRET_REST_TIME ) + { + float diff; + + diff = AngleSubtract( self->s.angles2[ YAW ], self->rotatorAngle - 360.0f ); + if( diff < -TURRET_REST_TOLERANCE ) + self->s.angles2[ YAW ] += TURRET_REST_SPEED; + else if ( diff > TURRET_REST_TOLERANCE ) + self->s.angles2[ YAW ] -= TURRET_REST_SPEED; + } return; + } self->enemy->targeted = self; + self->last_move_time = level.time; //if we are pointing at our target and we can fire shoot it if( HMGTurret_TrackEnemy( self ) && ( self->count < level.time ) ) @@ -2346,7 +2514,8 @@ void HTeslaGen_Think( gentity_t *self ) if( enemy->flags & FL_NOTARGET ) continue; - if( enemy->client && enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS && + if( enemy->client && + ( enemy->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS || enemy->client->pers.bleeder ) && enemy->health > 0 && !level.paused && !enemy->client->pers.paused && Distance( enemy->s.pos.trBase, self->s.pos.trBase ) <= TESLAGEN_RANGE ) @@ -2404,6 +2573,9 @@ void HSpawn_Disappear( gentity_t *self ) self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink + + self->s.eFlags |= EF_DEAD; + G_RecoverBuildPoints( self ); } @@ -2436,6 +2608,9 @@ void HSpawn_Blast( gentity_t *self ) self->r.contents = 0; //stop collisions... trap_LinkEntity( self ); //...requires a relink + + self->s.eFlags |= EF_DEAD; + G_RecoverBuildPoints( self ); } @@ -2451,6 +2626,7 @@ void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int buildHistory_t *new; new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = ( attacker && attacker->client ) ? attacker : NULL; if( new->ent ) new->name[ 0 ] = 0; @@ -2485,6 +2661,8 @@ void HSpawn_Die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int self->nextthink = level.time; //blast immediately } + G_RecoverSetKiller( self, attacker ); + if( attacker && attacker->client ) { if( attacker->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) @@ -2769,6 +2947,50 @@ qboolean G_BuildableRange( vec3_t origin, float r, buildable_t buildable ) return qfalse; } +/* +=============== +G_BuildableRangeClosest + +Find Closest distance to a type of buildable, -1 +=============== +*/ +float G_BuildableRangeClosest( vec3_t origin, float r, buildable_t buildable ) +{ + int entityList[ MAX_GENTITIES ]; + vec3_t range; + vec3_t mins, maxs; + int i, num; + gentity_t *ent; + float found = -1.0; + + VectorSet( range, r, r, r ); + VectorAdd( origin, range, maxs ); + VectorSubtract( origin, range, mins ); + + num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); + for( i = 0; i < num; i++ ) + { + ent = &g_entities[ entityList[ i ] ]; + + if( ent->s.eType != ET_BUILDABLE ) + continue; + + if( ent->biteam == BIT_HUMANS && !ent->powered ) + continue; + + if( ent->s.modelindex == buildable && ent->spawned ) + { + float dist; + + dist = Distance( origin, ent->s.origin ); + if( found < 0.0 || dist < found ) + found = dist; + } + } + + return found; +} + static qboolean G_BoundsIntersect(const vec3_t mins, const vec3_t maxs, const vec3_t mins2, const vec3_t maxs2) { @@ -2851,7 +3073,6 @@ static int G_CompareBuildablesForRemoval( const void *a, const void *b ) buildableA = *(gentity_t **)a; buildableB = *(gentity_t **)b; - // Prefer the one that collides with the thing we're building aMatches = G_BuildablesIntersect( cmpBuildable, cmpOrigin, buildableA->s.modelindex, buildableA->s.origin ); @@ -2909,6 +3130,7 @@ void G_FreeMarkedBuildables( void ) new = G_Alloc( sizeof( buildHistory_t ) ); new->ID = -1; + new->time = 0; new->ent = NULL; Q_strncpyz( new->name, "<markdecon>", 12 ); new->buildable = ent->s.modelindex; @@ -3035,12 +3257,7 @@ static itemBuildError_t G_SufficientBPAvailable( buildable_t buildable, // Don't allow destruction of hovel with granger inside if( ent->s.modelindex == BA_A_HOVEL && ent->active ) - { - if( buildable == BA_A_HOVEL ) - return IBE_HOVEL; - else - continue; - } + continue; // Explicitly disallow replacement of the core buildable with anything // other than the core buildable @@ -3163,18 +3380,17 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance { vec3_t angles; vec3_t entity_origin, normal; - vec3_t mins, maxs, nbmins, nbmaxs; - vec3_t nbVect; + vec3_t mins, maxs; trace_t tr1, tr2, tr3; int i; itemBuildError_t reason = IBE_NONE; + itemBuildError_t old_reason = IBE_NONE; gentity_t *tempent; float minNormal; qboolean invert; int contents; playerState_t *ps = &ent->client->ps; int buildPoints; - gentity_t *tmp; itemBuildError_t tempReason; // Stop all buildables from interacting with traces @@ -3200,28 +3416,13 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance if( tr1.entityNum != ENTITYNUM_WORLD ) reason = IBE_NORMAL; + // check nobuild zones + if( nobuild_check( entity_origin ) >= 0 ) + reason = IBE_PERMISSION; + contents = trap_PointContents( entity_origin, -1 ); buildPoints = BG_FindBuildPointsForBuildable( buildable ); - - //check if we are near a nobuild marker, if so, can't build here... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - tmp = &g_entities[ i ]; - - if( !tmp->noBuild.isNB ) - continue; - - nbVect[0] = tmp->noBuild.Area; - nbVect[1] = tmp->noBuild.Area; - nbVect[2] = tmp->noBuild.Height; - - VectorSubtract( origin, nbVect, nbmins ); - VectorAdd( origin, nbVect, nbmaxs ); - - if( trap_EntityContact( nbmins, nbmaxs, tmp ) ) - reason = IBE_PERMISSION; - - } + if( ent->client->ps.stats[ STAT_PTEAM ] == PTE_ALIENS ) { //alien criteria @@ -3276,6 +3477,7 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance switch( buildable ) { case BA_A_OVERMIND: + old_reason = reason; reason = IBE_OVERMIND; break; @@ -3359,6 +3561,7 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance if( tempent->s.modelindex == BA_H_REACTOR && !tempent->deconstruct ) { + old_reason = reason; reason = IBE_REACTOR; break; } @@ -3369,6 +3572,38 @@ itemBuildError_t G_CanBuild( gentity_t *ent, buildable_t buildable, int distance if( ( tempReason = G_SufficientBPAvailable( buildable, origin ) ) != IBE_NONE ) reason = tempReason; + if( g_modBuildableCount.integer > 1 && old_reason == IBE_NONE ) + { + if( reason == IBE_REACTOR ) + { + int rcs = 0; + + for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) + { + if( tempent->s.eType == ET_BUILDABLE && + tempent->s.modelindex == BA_H_REACTOR && + !tempent->deconstruct ) + rcs++; + } + if( rcs < g_modBuildableCount.integer ) + reason = IBE_NONE; + } + else if( reason == IBE_OVERMIND ) + { + int oms = 0; + + for ( i = 1, tempent = g_entities + i; i < level.num_entities; i++, tempent++ ) + { + if( tempent->s.eType == ET_BUILDABLE && + tempent->s.modelindex == BA_A_OVERMIND && + !tempent->deconstruct ) + oms++; + } + if( oms < g_modBuildableCount.integer ) + reason = IBE_NONE; + } + } + // Relink buildables G_SetBuildableLinkState( qtrue ); @@ -3498,8 +3733,10 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori built->buildTime = built->s.time = level.time; built->spawnBlockTime = 0; + built->killedBy = ENTITYNUM_NONE; + // build instantly in cheat mode - if( builder->client && g_cheats.integer ) + if( builder->client && g_cheats.integer || g_instantBuild.integer ) { built->health = BG_FindHealthForBuildable( buildable ); built->buildTime = built->s.time = @@ -3704,6 +3941,7 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori { new = level.buildHistory; new->ID = ( ++level.lastBuildID > 1000 ) ? ( level.lastBuildID = 1 ) : level.lastBuildID; + new->time = level.time; new->ent = builder; new->name[ 0 ] = 0; new->buildable = buildable; @@ -3722,39 +3960,6 @@ static gentity_t *G_Build( gentity_t *builder, buildable_t buildable, vec3_t ori return built; } -static void G_SpawnMarker( vec3_t origin ) -{ - gentity_t *nb; - int i; - - // Make the marker... - nb = G_Spawn( ); - nb->s.modelindex = 0; //Coder humor is win - VectorCopy( origin, nb->s.pos.trBase ); - VectorCopy( origin, nb->r.currentOrigin ); - nb->noBuild.isNB = qtrue; - nb->noBuild.Area = level.nbArea; - nb->noBuild.Height = level.nbHeight; - trap_LinkEntity( nb ); - - // Log markers made... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker != NULL ) - continue; - - level.nbMarkers[ i ].Marker = nb; - VectorCopy( origin, level.nbMarkers[ i ].Origin ); - SnapVector( level.nbMarkers[ i ].Origin ); - break; - } - - // End nobuild mode... - level.noBuilding = qfalse; - level.nbArea = 0.0f; - level.nbHeight = 0.0f; -} - /* ================= G_BuildIfValid @@ -3770,15 +3975,6 @@ qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ) switch( G_CanBuild( ent, buildable, dist, origin ) ) { case IBE_NONE: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - - G_SpawnMarker( origin ); - return qtrue; - } G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; @@ -3844,40 +4040,16 @@ qboolean G_BuildIfValid( gentity_t *ent, buildable_t buildable ) return qfalse; case IBE_SPWNWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } G_TriggerMenu( ent->client->ps.clientNum, MN_A_SPWNWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_TNODEWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } G_TriggerMenu( ent->client->ps.clientNum, MN_H_TNODEWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; case IBE_RPTWARN: - if( level.noBuilding ) - { - vec3_t mins; - BG_FindBBoxForBuildable( buildable, mins, NULL ); - origin[2] += mins[2]; - G_SpawnMarker( origin ); - return qtrue; - } G_TriggerMenu( ent->client->ps.clientNum, MN_H_RPTWARN ); G_Build( ent, buildable, origin, ent->s.apos.trBase ); return qtrue; @@ -4583,128 +4755,624 @@ void G_BaseSelfDestruct( pTeam_t team ) return "<buildlog entry expired>"; } + /* -============ -G_NobuildLoad + * ======= + * nobuild + * ======= + */ -load the nobuild markers that were previously saved (if there are any). -============ -*/ -void G_NobuildLoad( void ) +#define MAX_NOBUILD_ZONES 16 +#define NOBUILD_THINK_TIME 200 + +typedef struct +{ + qboolean in_use; + vec3_t origin; + vec3_t size; +} +nobuild_t; + +typedef struct +{ + vec3_t dir; + float yaw; +} +nobuild_corner_t; + +static nobuild_corner_t nobuildCorner[] = { + { { -1, -1, -1 }, 0 }, + { { -1, 1, -1 }, 270 }, + { { 1, -1, -1 }, 90 }, + { { 1, 1, -1 }, 180 }, + + { { -1, -1, 1 }, 270 }, + { { -1, 1, 1 }, 180 }, + { { 1, -1, 1 }, 0 }, + { { 1, 1, 1 }, 90 } +}; +#define MAX_NOBUILD_CORNERS ( sizeof( nobuildCorner ) / sizeof( nobuild_corner_t ) ) + +typedef enum +{ + NB_ORIGIN_X, + NB_ORIGIN_Y, + NB_ORIGIN_Z, + NB_SIZE_X, + NB_SIZE_Y, + NB_SIZE_Z +} +nobuild_sizer_t; +#define NB_COUNT ( NB_SIZE_Z + 1 ) + +static nobuild_t noBuildZones[ MAX_NOBUILD_ZONES ]; + +static qboolean noBuildActive = qfalse; +static int noBuildEditZone = 0; +static nobuild_sizer_t noBuildEditType = NB_ORIGIN_X; + +void nobuild_init( void ) { - fileHandle_t f; - int len; - char *nobuild; char map[ MAX_QPATH ]; - vec3_t origin = { 0.0f, 0.0f, 0.0f }; char line[ MAX_STRING_CHARS ]; - int i = 0; - int i2; - gentity_t *nb; - float area; - float height; + char *data, *pool; + fileHandle_t f; + int len; + int lcount = 0; + int zonecount = 0; + int i; + int id; + vec3_t origin, size; + + memset( noBuildZones, 0, sizeof( noBuildZones ) ); trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - len = trap_FS_FOpenFile( va( "nobuild/%s.dat", map ), - &f, FS_READ ); + len = trap_FS_FOpenFile( va( "layouts/%s/nobuild.nbd", map ), &f, FS_READ ); if( len < 0 ) { - // This isn't needed since nobuild is pretty much optional... - //G_Printf( "ERROR: nobuild for %s could not be opened\n", map ); return; } - nobuild = G_Alloc( len + 1 ); - trap_FS_Read( nobuild, len, f ); - *( nobuild + len ) = '\0'; + data = pool = G_Alloc( len + 1 ); + trap_FS_Read( data, len, f ); + *( data + len ) = '\0'; trap_FS_FCloseFile( f ); - while( *nobuild ) + + i = 0; + while( *data ) { if( i >= sizeof( line ) - 1 ) { - return; + G_Printf( S_COLOR_RED "ERROR: line overflow in %s before \"%s\"\n", + va( "layouts/%s/nobuild.nbd", map ), line ); } - - line[ i++ ] = *nobuild; + line[ i++ ] = *data; line[ i ] = '\0'; - if( *nobuild == '\n' ) + if( *data == '\n' ) { - i = 0; - sscanf( line, "%f %f %f %f %f\n", - &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], &area, &height ); - - // Make the marker... - nb = G_Spawn( ); - nb->s.modelindex = 0; - VectorCopy( origin, nb->s.pos.trBase ); - VectorCopy( origin, nb->r.currentOrigin ); - nb->noBuild.isNB = qtrue; - nb->noBuild.Area = area; - nb->noBuild.Height = height; - trap_LinkEntity( nb ); - - // Log markers made... - for( i = 0; i < MAX_GENTITIES; i++ ) - { - if( level.nbMarkers[ i ].Marker != NULL ) - continue; - - level.nbMarkers[ i ].Marker = nb; - VectorCopy( origin, level.nbMarkers[ i ].Origin ); - SnapVector( level.nbMarkers[ i ].Origin ); - break; - } - + i = 0; + lcount++; + id = -1; + if( sscanf( line, "%d %f %f %f %f %f %f\n", + &id, + &origin[ 0 ], &origin[ 1 ], &origin[ 2 ], + &size[ 0 ], &size[ 1 ], &size[ 2 ]) == 7 ) + { + if( zonecount >= MAX_NOBUILD_ZONES ) + { + G_Printf( S_COLOR_YELLOW "WARNING: nobuild zone count exceeded on line %d\n", lcount ); + break; + } + noBuildZones[ zonecount ].in_use = qtrue; + VectorCopy( origin, noBuildZones[ zonecount ].origin ); + VectorCopy( size, noBuildZones[ zonecount ].size ); + zonecount++; + } + else + { + G_Printf( S_COLOR_YELLOW "WARNING: nobuild data parse error on line %d\n", lcount ); + } } - nobuild++; + data++; } + + G_Free( pool ); + + if( zonecount ) + G_Printf( "nobuild: loaded %d zones\n", zonecount ); } -/* -============ -G_NobuildSave -Save all currently placed nobuild markers into the "nobuild" folder -============ -*/ -void G_NobuildSave( void ) +static void nobuild_sizer_think( gentity_t *self ) +{ + int zone, size; + vec3_t origin; + + if( !noBuildActive ) + { + G_FreeEntity( self ); + return; + } + + self->nextthink = level.time + NOBUILD_THINK_TIME; + + zone = noBuildEditZone; + if( zone < 0 || zone >= MAX_NOBUILD_ZONES || !noBuildZones[ zone ].in_use ) + { + if( self->r.linked ) + trap_UnlinkEntity( self ); + return; + } + + size = self->damage; + VectorCopy( noBuildZones[ zone ].origin, origin ); + switch ( noBuildEditType ) + { + case NB_ORIGIN_X: + case NB_SIZE_X: + origin[ 0 ] += size; + break; + case NB_ORIGIN_Y: + case NB_SIZE_Y: + origin[ 1 ] += size; + break; + case NB_ORIGIN_Z: + case NB_SIZE_Z: + origin[ 2 ] += size; + break; + } + + VectorCopy( origin, self->s.origin ); + VectorCopy( origin, self->s.pos.trBase ); + VectorCopy( origin, self->r.currentOrigin ); + + if( self->watertype != noBuildEditType ) + { + self->watertype = noBuildEditType; + switch( noBuildEditType ) + { + case NB_ORIGIN_X: + case NB_ORIGIN_Y: + case NB_ORIGIN_Z: + self->s.modelindex = G_ModelIndex( "models/players/human_base/jetpack.md3" ); + break; + case NB_SIZE_X: + case NB_SIZE_Y: + case NB_SIZE_Z: + self->s.modelindex = G_ModelIndex( "models/players/human_base/battpack.md3" ); + break; + } + } + + if( !self->r.linked ) + trap_LinkEntity( self ); +} + +static gentity_t *nobuild_sizer( float distance ) +{ + gentity_t *self; + + self = G_Spawn( ); + self->classname = "nobuild_sizer"; + self->s.eType = ET_GENERAL; + + self->s.time = level.time; + self->s.pos.trType = TR_STATIONARY; + self->s.pos.trTime = level.time; + + self->r.svFlags = SVF_BROADCAST; + + self->damage = distance; + self->watertype = -1; + self->think = nobuild_sizer_think; + + // run a think to set positions + nobuild_sizer_think( self ); + + return self; +} + +static void nobuild_corner_think( gentity_t *self ) +{ + int zone, corner; + vec3_t origin; + + zone = self->count; + corner = self->damage; + + if( !noBuildActive || !noBuildZones[ zone ].in_use ) + { + G_FreeEntity( self ); + return; + } + + self->nextthink = level.time + NOBUILD_THINK_TIME; + + VectorCopy( noBuildZones[ zone ].origin, origin ); + origin[ 0 ] += noBuildZones[ zone ].size[ 0 ] * nobuildCorner[ corner ].dir[ 0 ]; + origin[ 1 ] += noBuildZones[ zone ].size[ 1 ] * nobuildCorner[ corner ].dir[ 1 ]; + origin[ 2 ] += noBuildZones[ zone ].size[ 2 ] * nobuildCorner[ corner ].dir[ 2 ]; + + VectorCopy( origin, self->s.origin ); + VectorCopy( origin, self->s.pos.trBase ); + VectorCopy( origin, self->r.currentOrigin ); +} + +static gentity_t *nobuild_corner( int zone, int corner ) +{ + gentity_t *self; + + if( zone < 0 || zone >= MAX_NOBUILD_ZONES ) + return NULL; + if( !noBuildZones[ zone ].in_use ) + return NULL; + if( corner < 0 || corner >= MAX_NOBUILD_CORNERS ) + return NULL; + + self = G_Spawn( ); + self->classname = "nobuild_corner"; + self->s.eType = ET_GENERAL; + self->s.modelindex = 9999; + + self->s.time = level.time; + self->s.pos.trType = TR_STATIONARY; + self->s.pos.trTime = level.time; + + self->count = zone; + self->damage = corner; + self->think = nobuild_corner_think; + + // run a think to set positions + nobuild_corner_think( self ); + + self->s.apos.trBase[ YAW ] = nobuildCorner[ corner ].yaw; + if( nobuildCorner[ corner ].dir[ 2 ] > 0.0f ) + self->s.apos.trBase[ PITCH ] = 180.0f; + + trap_LinkEntity( self ); + + return self; +} + +static void nobuild_think( gentity_t *self ) +{ + int zone; + + zone = self->count; + + if( !noBuildActive || !noBuildZones[ zone ].in_use ) + { + G_FreeEntity( self ); + return; + } + + self->nextthink = level.time + NOBUILD_THINK_TIME; + VectorCopy( noBuildZones[ zone ].origin, self->s.origin ); + VectorCopy( noBuildZones[ zone ].origin, self->s.pos.trBase ); + VectorCopy( noBuildZones[ zone ].origin, self->r.currentOrigin ); +} + +static gentity_t *nobuild_spawn( int zone ) +{ + gentity_t *self; + int i; + + if( zone < 0 || zone >= MAX_NOBUILD_ZONES ) + return NULL; + if( !noBuildZones[ zone ].in_use ) + return NULL; + + self = G_Spawn( ); + self->classname = "nobuild_zone"; + self->s.eType = ET_GENERAL; + self->s.modelindex = G_ModelIndex( "models/buildables/repeater/repeater.md3" ); + + self->s.time = level.time; + self->s.pos.trType = TR_STATIONARY; + self->s.pos.trTime = level.time; + + self->think = nobuild_think; + self->nextthink = level.time + NOBUILD_THINK_TIME; + + VectorCopy( noBuildZones[ zone ].origin, self->s.origin ); + VectorCopy( noBuildZones[ zone ].origin, self->s.pos.trBase ); + VectorCopy( noBuildZones[ zone ].origin, self->r.currentOrigin ); + + self->s.apos.trBase[ PITCH ] = 180.0f; + + self->count = zone; + + trap_LinkEntity( self ); + + for( i = 0; i < MAX_NOBUILD_CORNERS; i++ ) + nobuild_corner( zone, i ); + + return self; +} + +static void nobuild_on( gentity_t *ent ) +{ + int i; + + if( noBuildActive ) + return; + + noBuildActive = qtrue; + noBuildEditZone = MAX_NOBUILD_ZONES - 1; + noBuildEditType = NB_ORIGIN_X; + + nobuild_command( ent, qtrue, qfalse, 0.0f ); + + for( i = 0; i < MAX_NOBUILD_ZONES; i++ ) + { + nobuild_spawn( i ); + } + + nobuild_sizer( 32.0f ); + nobuild_sizer( -32.0f ); + + trap_SendServerCommand( -1, + va( "print \"^3!nobuild ^7edit mode enabled by %s\n\"", + ( ent ) ? ent->client->pers.netname : "console " ) ); +} + +static void nobuild_off( gentity_t *ent ) +{ + if( noBuildActive ) + { + noBuildActive = qfalse; + + trap_SendServerCommand( -1, + va( "print \"^3!nobuild ^7edit mode disabled by %s\n\"", + ( ent ) ? ent->client->pers.netname : "console " ) ); + } +} + +void nobuild_add( gentity_t *ent ) +{ + int i; + + if( !noBuildActive ) + { + ADMP( "!nobuild is not on, use '!nobuild on' to enable it\n" ); + return; + } + + if( !ent ) + return; + + for( i = 0; i < MAX_NOBUILD_ZONES; i++ ) + { + if( noBuildZones[ i ].in_use ) + continue; + + noBuildZones[ i ].in_use = qtrue; + VectorCopy( ent->s.origin, noBuildZones[ i ].origin ); + VectorSet( noBuildZones[ i ].size, 64.0f, 64.0f, 64.0f ); + + noBuildEditZone = i; + nobuild_spawn( i ); + + ADMP( va( "added nobuild zone #%d\n", i ) ); + return; + } + + ADMP( "maximum nobuild zones reached, can not add another zone.\n" ); +} + +void nobuild_del( gentity_t *ent ) +{ + int zone; + + if( !noBuildActive ) + { + ADMP( "!nobuild is not on, use '!nobuild on' to enable it\n" ); + return; + } + + if( noBuildEditZone < 0 || noBuildEditZone >= MAX_NOBUILD_ZONES || + !noBuildZones[ noBuildEditZone ].in_use ) + { + ADMP( "a nobuild zone is not selected\n" ); + return; + } + + zone = noBuildEditZone; + nobuild_command( ent, qtrue, qfalse, 0.0f ); + noBuildZones[ zone ].in_use = qfalse; + if( noBuildEditZone == zone ) + noBuildEditZone = -1; + + ADMP( va( "removed nobuild zone #%d\n", zone ) ); +} + +#define SIZE_MIN( a, b ) ( a = ( a + b < 32.0 ? a = 32.0 : a + b ) ) + +void nobuild_command( gentity_t *ent, qboolean next_zone, qboolean next_type, float size ) +{ + int n; + + if( !noBuildActive ) + { + ADMP( "!nobuild is not on, use '!nobuild on' to enable it\n" ); + return; + } + + if( next_zone ) + { + n = noBuildEditZone + 1; + while( n != noBuildEditZone ) + { + if( n >= MAX_NOBUILD_ZONES ) + n = 0; + if( noBuildZones[ n ].in_use ) + { + noBuildEditZone = n; + ADMP( va ( "nobuild zone %d selected\n", n ) ); + return; + } + n++; + } + } + else if( next_type ) + { + noBuildEditType++; + if ( noBuildEditType >= NB_COUNT ) + noBuildEditType = 0; + } + else if( size ) + { + switch( noBuildEditType ) + { + case NB_ORIGIN_X: + noBuildZones[ noBuildEditZone ].origin[ 0 ] += size; + break; + case NB_ORIGIN_Y: + noBuildZones[ noBuildEditZone ].origin[ 1 ] += size; + break; + case NB_ORIGIN_Z: + noBuildZones[ noBuildEditZone ].origin[ 2 ] += size; + break; + case NB_SIZE_X: + SIZE_MIN( noBuildZones[ noBuildEditZone ].size[ 0 ], size ); + break; + case NB_SIZE_Y: + SIZE_MIN( noBuildZones[ noBuildEditZone ].size[ 1 ], size ); + break; + case NB_SIZE_Z: + SIZE_MIN( noBuildZones[ noBuildEditZone ].size[ 2 ], size ); + break; + } + } +} + +void nobuild_go( gentity_t *ent ) +{ + if( !ent ) + return; + + if( !noBuildActive ) + { + ADMP( "!nobuild is not on, use '!nobuild on' to enable it\n" ); + return; + } + if( noBuildEditZone < 0 || noBuildEditZone >= MAX_NOBUILD_ZONES || + !noBuildZones[ noBuildEditZone ].in_use ) + { + ADMP( "a nobuild zone is not selected\n" ); + return; + } + + VectorCopy( noBuildZones[ noBuildEditZone ].origin, ent->client->ps.origin ); +} + +void nobuild_set( qboolean enable, gentity_t *ent ) +{ + if( !ent && enable ) + { + ADMP( "only a connected client can enable nobuild mode\n" ); + return; + } + if( enable ) + { + nobuild_on( ent ); + } + else + { + nobuild_off( ent ); + } +} + +void nobuild_save( void ) { char map[ MAX_QPATH ]; char fileName[ MAX_OSPATH ]; fileHandle_t f; int len; + int count = 0; int i; - gentity_t *ent; char *s; trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); if( !map[ 0 ] ) { - G_Printf( "NobuildSave( ): no map is loaded\n" ); + G_Printf( "LayoutSave( ): no map is loaded\n" ); return; } - Com_sprintf( fileName, sizeof( fileName ), "nobuild/%s.dat", map ); - + Com_sprintf( fileName, sizeof( fileName ), "layouts/%s/nobuild.nbd", map ); len = trap_FS_FOpenFile( fileName, &f, FS_WRITE ); - if( len < 0 ) + if( len < 0 ) { - G_Printf( "nobuildsave: could not open %s\n", fileName ); + G_Printf( "layoutsave: could not open %s\n", fileName ); return; } - G_Printf("nobuildsave: saving nobuild to %s\n", fileName ); + G_Printf("layoutsave: saving layout to %s\n", fileName ); - for( i = 0; i < MAX_GENTITIES; i++ ) + for( i = 0; i < MAX_NOBUILD_ZONES; i++ ) { - ent = &level.gentities[ i ]; - if( ent->noBuild.isNB != qtrue ) + if( !noBuildZones[ i ].in_use ) continue; - s = va( "%f %f %f %f %f\n", - ent->r.currentOrigin[ 0 ], - ent->r.currentOrigin[ 1 ], - ent->r.currentOrigin[ 2 ], - ent->noBuild.Area, - ent->noBuild.Height ); + s = va( "%d %f %f %f %f %f %f\n", + count, + noBuildZones[ i ].origin[ 0 ], + noBuildZones[ i ].origin[ 1 ], + noBuildZones[ i ].origin[ 2 ], + noBuildZones[ i ].size[ 0 ], + noBuildZones[ i ].size[ 1 ], + noBuildZones[ i ].size[ 2 ] ); trap_FS_Write( s, strlen( s ), f ); + count++; } + trap_FS_FCloseFile( f ); + G_Printf( "saved %d nobuild zones\n", count ); } + +void nobuild_list( gentity_t *ent ) +{ + int count = 0; + int i; + + ADMBP_begin(); + for( i = 0; i < MAX_NOBUILD_ZONES; i++ ) + { + if( !noBuildZones[ i ].in_use ) + continue; + + ADMBP( va( "%2d @ x: %6.2f, y: %6.2f, z: %6.2f - %4.2f x %4.2f x %4.2f\n", + i, + noBuildZones[ i ].origin[ 0 ], + noBuildZones[ i ].origin[ 1 ], + noBuildZones[ i ].origin[ 2 ], + noBuildZones[ i ].size[ 0 ], + noBuildZones[ i ].size[ 1 ], + noBuildZones[ i ].size[ 2 ] ) ); + count++; + } + + ADMBP( va( "nobuild contains %d zones\n", count ) ); + + ADMBP_end(); +} + +int nobuild_check( vec3_t origin ) +{ + int i; + + for( i = 0; i < MAX_NOBUILD_ZONES; i ++ ) + { + if( !noBuildZones[ i ].in_use ) + continue; + + if( origin[ 0 ] > noBuildZones[ i ].origin[ 0 ] - noBuildZones[ i ].size[ 0 ] && + origin[ 0 ] < noBuildZones[ i ].origin[ 0 ] + noBuildZones[ i ].size[ 0 ] && + origin[ 1 ] > noBuildZones[ i ].origin[ 1 ] - noBuildZones[ i ].size[ 1 ] && + origin[ 1 ] < noBuildZones[ i ].origin[ 1 ] + noBuildZones[ i ].size[ 1 ] && + origin[ 2 ] > noBuildZones[ i ].origin[ 2 ] - noBuildZones[ i ].size[ 2 ] && + origin[ 2 ] < noBuildZones[ i ].origin[ 2 ] + noBuildZones[ i ].size[ 2 ] ) + return i; + } + + return -1; +} + |