From 997376603b684e13d6b1c21708db88fc55e9e0eb Mon Sep 17 00:00:00 2001 From: Paweł Redman Date: Sat, 7 Sep 2013 09:56:40 +0200 Subject: Remove .orig files --- src/game/g_active.c.orig | 1937 ------------------------- src/game/g_client.c.orig | 1600 --------------------- src/game/g_cmds.c.orig | 3588 ---------------------------------------------- src/game/g_combat.c.orig | 1468 ------------------- src/game/g_main.c.orig | 2548 -------------------------------- 5 files changed, 11141 deletions(-) delete mode 100644 src/game/g_active.c.orig delete mode 100644 src/game/g_client.c.orig delete mode 100644 src/game/g_cmds.c.orig delete mode 100644 src/game/g_combat.c.orig delete mode 100644 src/game/g_main.c.orig (limited to 'src/game') diff --git a/src/game/g_active.c.orig b/src/game/g_active.c.orig deleted file mode 100644 index cfa6f89..0000000 --- a/src/game/g_active.c.orig +++ /dev/null @@ -1,1937 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2009 Darklegion Development - -This file is part of Tremulous. - -Tremulous is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Tremulous is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "g_local.h" - -/* To create useful and interesting cuboids (like ladders), the ability to specify - * custom surface flags for buildings was needed. This is what the following function - * was written for. It replaces the regular tracing function in PM code (also on the - * client side, grep for CG_PMTraceHack). This function also takes into account - * the modelindex of the entity it found and adjusts the surfaceFlags output - * accordingly. This way custom surface flags for buildings can be done without - * messing around with the PM code or the engine itself. - */ -void G_PMTraceHack(trace_t *results,const vec3_t start,const vec3_t mins,const vec3_t maxs,const vec3_t end,int passEntityNum,int contentmask ) -{ - int modelindex; - trap_Trace(results,start,mins,maxs,end,passEntityNum,contentmask); - if(results->entityNum!=ENTITYNUM_NONE) - { - modelindex=g_entities[results->entityNum].s.modelindex; - if(modelindex>=CUBOID_FIRST&&modelindex<=CUBOID_LAST&&(g_entities[results->entityNum].s.eFlags&EF_B_SPAWNED)) - results->surfaceFlags|=BG_CuboidAttributes(modelindex)->surfaceFlags; - } -} - - -/* -=============== -G_DamageFeedback - -Called just before a snapshot is sent to the given player. -Totals up all damage and generates both the player_state_t -damage values to that client for pain blends and kicks, and -global pain sound events for all clients. -=============== -*/ -void P_DamageFeedback( gentity_t *player ) -{ - gclient_t *client; - float count; - vec3_t angles; - - client = player->client; - if( !PM_Live( client->ps.pm_type ) ) - return; - - // total points of damage shot at the player this frame - count = client->damage_blood + client->damage_armor; - if( count == 0 ) - return; // didn't take any damage - - if( count > 255 ) - count = 255; - - // send the information to the client - - // world damage (falling, slime, etc) uses a special code - // to make the blend blob centered instead of positional - if( client->damage_fromWorld ) - { - client->ps.damagePitch = 255; - client->ps.damageYaw = 255; - - client->damage_fromWorld = qfalse; - } - else - { - vectoangles( client->damage_from, angles ); - client->ps.damagePitch = angles[ PITCH ] / 360.0 * 256; - client->ps.damageYaw = angles[ YAW ] / 360.0 * 256; - } - - // play an apropriate pain sound - if( ( level.time > player->pain_debounce_time ) && !( player->flags & FL_GODMODE ) ) - { - player->pain_debounce_time = level.time + 700; - G_AddEvent( player, EV_PAIN, player->health > 255 ? 255 : player->health ); - client->ps.damageEvent++; - } - - - client->ps.damageCount = count; - - // - // clear totals - // - client->damage_blood = 0; - client->damage_armor = 0; - client->damage_knockback = 0; -} - - - -/* -============= -P_WorldEffects - -Check for lava / slime contents and drowning -============= -*/ -void P_WorldEffects( gentity_t *ent ) -{ - int waterlevel; - - if( ent->client->noclip ) - { - ent->client->airOutTime = level.time + 12000; // don't need air - return; - } - - waterlevel = ent->waterlevel; - - // - // check for drowning - // - if( waterlevel == 3 ) - { - // if out of air, start drowning - if( ent->client->airOutTime < level.time) - { - // drown! - ent->client->airOutTime += 1000; - if( ent->health > 0 ) - { - // take more damage the longer underwater - ent->damage += 2; - if( ent->damage > 15 ) - ent->damage = 15; - - // play a gurp sound instead of a normal pain sound - if( ent->health <= ent->damage ) - G_Sound( ent, CHAN_VOICE, G_SoundIndex( "*drown.wav" ) ); - else if( rand( ) < RAND_MAX / 2 + 1 ) - G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp1.wav" ) ); - else - G_Sound( ent, CHAN_VOICE, G_SoundIndex( "sound/player/gurp2.wav" ) ); - - // don't play a normal pain sound - ent->pain_debounce_time = level.time + 200; - - G_Damage( ent, NULL, NULL, NULL, NULL, - ent->damage, DAMAGE_NO_ARMOR, MOD_WATER ); - } - } - } - else - { - ent->client->airOutTime = level.time + 12000; - ent->damage = 2; - } - - // - // check for sizzle damage (move to pmove?) - // - if( waterlevel && - ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) - { - if( ent->health > 0 && - ent->pain_debounce_time <= level.time ) - { - if( ent->watertype & CONTENTS_LAVA ) - { - G_Damage( ent, NULL, NULL, NULL, NULL, - 30 * waterlevel, 0, MOD_LAVA ); - } - - if( ent->watertype & CONTENTS_SLIME ) - { - G_Damage( ent, NULL, NULL, NULL, NULL, - 10 * waterlevel, 0, MOD_SLIME ); - } - } - } -} - - - -/* -=============== -G_SetClientSound -=============== -*/ -void G_SetClientSound( gentity_t *ent ) -{ - if( ent->waterlevel && ( ent->watertype & ( CONTENTS_LAVA | CONTENTS_SLIME ) ) ) - ent->client->ps.loopSound = level.snd_fry; - else - ent->client->ps.loopSound = 0; -} - - - -//============================================================== - -/* -============== -ClientShove -============== -*/ -static int GetClientMass( gentity_t *ent ) -{ - int entMass = 100; - - if( ent->client->pers.teamSelection == TEAM_ALIENS ) - entMass = BG_Class( ent->client->pers.classSelection )->health; - else if( ent->client->pers.teamSelection == TEAM_HUMANS ) - { - if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, ent->client->ps.stats ) ) - entMass *= 2; - } - else - return 0; - return entMass; -} - -static void ClientShove( gentity_t *ent, gentity_t *victim ) -{ - vec3_t dir, push; - float force; - int entMass, vicMass; - - // Don't push if the entity is not trying to move - if( !ent->client->pers.cmd.rightmove && !ent->client->pers.cmd.forwardmove && - !ent->client->pers.cmd.upmove ) - return; - - // Cannot push enemy players unless they are walking on the player - if( !OnSameTeam( ent, victim ) && - victim->client->ps.groundEntityNum != ent - g_entities ) - return; - - // Shove force is scaled by relative mass - entMass = GetClientMass( ent ); - vicMass = GetClientMass( victim ); - if( vicMass <= 0 || entMass <= 0 ) - return; - force = g_shove.value * entMass / vicMass; - if( force < 0 ) - force = 0; - if( force > 150 ) - force = 150; - - // Give the victim some shove velocity - VectorSubtract( victim->r.currentOrigin, ent->r.currentOrigin, dir ); - VectorNormalizeFast( dir ); - VectorScale( dir, force, push ); - VectorAdd( victim->client->ps.velocity, push, victim->client->ps.velocity ); - - // Set the pmove timer so that the other client can't cancel - // out the movement immediately - if( !victim->client->ps.pm_time ) - { - int time; - - time = force * 2 + 0.5f; - if( time < 50 ) - time = 50; - if( time > 200 ) - time = 200; - victim->client->ps.pm_time = time; - victim->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - } -} - -/* -============== -ClientImpacts -============== -*/ -void ClientImpacts( gentity_t *ent, pmove_t *pm ) -{ - int i; - trace_t trace; - gentity_t *other; - - // clear a fake trace struct for touch function - memset( &trace, 0, sizeof( trace ) ); - - for( i = 0; i < pm->numtouch; i++ ) - { - other = &g_entities[ pm->touchents[ i ] ]; - - // see G_UnlaggedDetectCollisions(), this is the inverse of that. - // if our movement is blocked by another player's real position, - // don't use the unlagged position for them because they are - // blocking or server-side Pmove() from reaching it - if( other->client && other->client->unlaggedCalc.used ) - other->client->unlaggedCalc.used = qfalse; - - // tyrant impact attacks - if( ent->client->ps.weapon == WP_ALEVEL4 ) - { - G_ChargeAttack( ent, other ); - G_CrushAttack( ent, other ); - } - - // shove players - if( ent->client && other->client ) - ClientShove( ent, other ); - - // touch triggers - if( other->touch ) - other->touch( other, ent, &trace ); - } -} - -/* -============ -G_TouchTriggers - -Find all trigger entities that ent's current position touches. -Spectators will only interact with teleporters. -============ -*/ -void G_TouchTriggers( gentity_t *ent ) -{ - int i, num; - int touch[MAX_GENTITIES]; - gentity_t *hit; - trace_t trace; - vec3_t mins, maxs; - vec3_t pmins, pmaxs; - static vec3_t range = { 10, 10, 10 }; - - if( !ent->client ) - return; - - // dead clients don't activate triggers! - if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 ) - return; - - BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], - pmins, pmaxs, NULL, NULL, NULL ); - - VectorAdd( ent->client->ps.origin, pmins, mins ); - VectorAdd( ent->client->ps.origin, pmaxs, maxs ); - - VectorSubtract( mins, range, mins ); - VectorAdd( maxs, range, maxs ); - - num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); - - // can't use ent->absmin, because that has a one unit pad - VectorAdd( ent->client->ps.origin, ent->r.mins, mins ); - VectorAdd( ent->client->ps.origin, ent->r.maxs, maxs ); - - for( i = 0; i < num; i++ ) - { - hit = &g_entities[ touch[ i ] ]; - - if( !hit->touch && !ent->touch ) - continue; - - if( !( hit->r.contents & CONTENTS_TRIGGER ) ) - continue; - - // ignore most entities if a spectator - if( ent->client->sess.spectatorState != SPECTATOR_NOT ) - { - if( hit->s.eType != ET_TELEPORT_TRIGGER && - // this is ugly but adding a new ET_? type will - // most likely cause network incompatibilities - hit->touch != Touch_DoorTrigger ) - { - //check for manually triggered doors - manualTriggerSpectator( hit, ent ); - continue; - } - } - - if( !trap_EntityContact( mins, maxs, hit ) ) - continue; - - memset( &trace, 0, sizeof( trace ) ); - - if( hit->touch ) - hit->touch( hit, ent, &trace ); - } -} - -/* -================= -SpectatorThink -================= -*/ -void SpectatorThink( gentity_t *ent, usercmd_t *ucmd ) -{ - pmove_t pm; - gclient_t *client; - int clientNum; - qboolean attack1, attack3, following, queued; - - client = ent->client; - - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - - attack1 = ( client->buttons & BUTTON_ATTACK ) && - !( client->oldbuttons & BUTTON_ATTACK ); - attack3 = ( client->buttons & BUTTON_USE_HOLDABLE ) && - !( client->oldbuttons & BUTTON_USE_HOLDABLE ); - - // We are in following mode only if we are following a non-spectating client - following = client->sess.spectatorState == SPECTATOR_FOLLOW; - if( following ) - { - clientNum = client->sess.spectatorClient; - if( clientNum < 0 || clientNum > level.maxclients || - !g_entities[ clientNum ].client || - g_entities[ clientNum ].client->sess.spectatorState != SPECTATOR_NOT ) - following = qfalse; - } - - // Check to see if we are in the spawn queue - if( client->pers.teamSelection == TEAM_ALIENS ) - queued = G_SearchSpawnQueue( &level.alienSpawnQueue, ent - g_entities ); - else if( client->pers.teamSelection == TEAM_HUMANS ) - queued = G_SearchSpawnQueue( &level.humanSpawnQueue, ent - g_entities ); - else - queued = qfalse; - - // Wants to get out of spawn queue - if( attack1 && queued ) - { - if( client->sess.spectatorState == SPECTATOR_FOLLOW ) - G_StopFollowing( ent ); - if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - G_RemoveFromSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); - else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - G_RemoveFromSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); - client->pers.classSelection = PCL_NONE; - client->ps.stats[ STAT_CLASS ] = PCL_NONE; - client->ps.pm_flags &= ~PMF_QUEUED; - queued = qfalse; - } - else if( attack1 ) - { - // Wants to get into spawn queue - if( client->sess.spectatorState == SPECTATOR_FOLLOW ) - G_StopFollowing( ent ); - if( client->pers.teamSelection == TEAM_NONE ) - G_TriggerMenu( client->ps.clientNum, MN_TEAM ); - else if( client->pers.teamSelection == TEAM_ALIENS ) - G_TriggerMenu( client->ps.clientNum, MN_A_CLASS ); - else if( client->pers.teamSelection == TEAM_HUMANS ) - G_TriggerMenu( client->ps.clientNum, MN_H_SPAWN ); - } - - // We are either not following anyone or following a spectator - if( !following ) - { - if( client->sess.spectatorState == SPECTATOR_LOCKED || - client->sess.spectatorState == SPECTATOR_FOLLOW ) - client->ps.pm_type = PM_FREEZE; - else if( client->noclip ) - client->ps.pm_type = PM_NOCLIP; - else - client->ps.pm_type = PM_SPECTATOR; - - if( queued ) - client->ps.pm_flags |= PMF_QUEUED; - - client->ps.speed = client->pers.flySpeed; - client->ps.stats[ STAT_STAMINA ] = 0; - client->ps.stats[ STAT_MISC ] = 0; - client->buildTimer=0; - client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; - client->ps.stats[ STAT_CLASS ] = PCL_NONE; - client->ps.weapon = WP_NONE; - - // Set up for pmove - memset( &pm, 0, sizeof( pm ) ); - pm.ps = &client->ps; - pm.pmext = &client->pmext; - pm.cmd = *ucmd; - pm.tracemask = MASK_DEADSOLID; // spectators can fly through bodies - pm.trace = G_PMTraceHack; - pm.pointcontents = trap_PointContents; - - // Perform a pmove - Pmove( &pm ); - - // Save results of pmove - VectorCopy( client->ps.origin, ent->s.origin ); - - G_TouchTriggers( ent ); - trap_UnlinkEntity( ent ); - - // Set the queue position and spawn count for the client side - if( client->ps.pm_flags & PMF_QUEUED ) - { - if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - { - client->ps.persistant[ PERS_QUEUEPOS ] = - G_GetPosInSpawnQueue( &level.alienSpawnQueue, client->ps.clientNum ); - client->ps.persistant[ PERS_SPAWNS ] = level.numAlienSpawns; - } - else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - { - client->ps.persistant[ PERS_QUEUEPOS ] = - G_GetPosInSpawnQueue( &level.humanSpawnQueue, client->ps.clientNum ); - client->ps.persistant[ PERS_SPAWNS ] = level.numHumanSpawns; - } - } - } -} - - - -/* -================= -ClientInactivityTimer - -Returns qfalse if the client is dropped -================= -*/ -qboolean ClientInactivityTimer( gentity_t *ent ) -{ - gclient_t *client = ent->client; - - if( ! g_inactivity.integer ) - { - // give everyone some time, so if the operator sets g_inactivity during - // gameplay, everyone isn't kicked - client->inactivityTime = level.time + 60 * 1000; - client->inactivityWarning = qfalse; - } - else if( client->pers.cmd.forwardmove || - client->pers.cmd.rightmove || - client->pers.cmd.upmove || - ( client->pers.cmd.buttons & BUTTON_ATTACK ) ) - { - client->inactivityTime = level.time + g_inactivity.integer * 1000; - client->inactivityWarning = qfalse; - } - else if( !client->pers.localClient ) - { - if( level.time > client->inactivityTime && - !G_admin_permission( ent, ADMF_ACTIVITY ) ) - { - trap_DropClient( client - level.clients, "Dropped due to inactivity" ); - return qfalse; - } - - if( level.time > client->inactivityTime - 10000 && - !client->inactivityWarning && - !G_admin_permission( ent, ADMF_ACTIVITY ) ) - { - client->inactivityWarning = qtrue; - trap_SendServerCommand( client - level.clients, "cp \"Ten seconds until inactivity drop!\n\"" ); - } - } - - return qtrue; -} - -/* -================== -ClientTimerActions - -Actions that happen once a second -================== -*/ -void ClientTimerActions( gentity_t *ent, int msec ) -{ - gclient_t *client; - usercmd_t *ucmd; - int aForward, aRight; - qboolean walking = qfalse, stopped = qfalse, - crouched = qfalse, jumping = qfalse, - strafing = qfalse; - int i; - - ucmd = &ent->client->pers.cmd; - - aForward = abs( ucmd->forwardmove ); - aRight = abs( ucmd->rightmove ); - - if( aForward == 0 && aRight == 0 ) - stopped = qtrue; - else if( aForward <= 64 && aRight <= 64 ) - walking = qtrue; - - if( aRight > 0 ) - strafing = qtrue; - - if( ucmd->upmove > 0 ) - jumping = qtrue; - else if( ent->client->ps.pm_flags & PMF_DUCKED ) - crouched = qtrue; - - client = ent->client; - client->time100 += msec; - client->time1000 += msec; - client->time10000 += msec; - - while ( client->time100 >= 100 ) - { - weapon_t weapon = BG_GetPlayerWeapon( &client->ps ); - - client->time100 -= 100; - - // Restore or subtract stamina - if( stopped || client->ps.pm_type == PM_JETPACK ) - client->ps.stats[ STAT_STAMINA ] += STAMINA_STOP_RESTORE; - else if( ( client->ps.stats[ STAT_STATE ] & SS_SPEEDBOOST ) && - !( client->buttons & BUTTON_WALKING ) ) // walk overrides sprint - client->ps.stats[ STAT_STAMINA ] -= STAMINA_SPRINT_TAKE; - else if( walking || crouched ) - client->ps.stats[ STAT_STAMINA ] += STAMINA_WALK_RESTORE; - - // Check stamina limits - if( client->ps.stats[ STAT_STAMINA ] > STAMINA_MAX ) - client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; - else if( client->ps.stats[ STAT_STAMINA ] < -STAMINA_MAX ) - client->ps.stats[ STAT_STAMINA ] = -STAMINA_MAX; - - if( weapon == WP_ABUILD || weapon == WP_ABUILD2 || - BG_InventoryContainsWeapon( WP_HBUILD, client->ps.stats ) ) - { - client->buildTimer-=MIN(100,client->buildTimer); - G_RecalcBuildTimer(client); - } - - switch( weapon ) - { - case WP_ABUILD: - case WP_ABUILD2: - case WP_HBUILD: - - // Set validity bit on buildable - if( ( client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT ) > BA_NONE ) - { - int dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; - vec3_t dummy, dummy2; - - if( G_CanBuild( ent, client->ps.stats[ STAT_BUILDABLE ] & ~SB_VALID_TOGGLEBIT, - dist, dummy, dummy2, client->cuboidSelection ) == IBE_NONE ) - client->ps.stats[ STAT_BUILDABLE ] |= SB_VALID_TOGGLEBIT; - else - client->ps.stats[ STAT_BUILDABLE ] &= ~SB_VALID_TOGGLEBIT; - - // Let the client know which buildables will be removed by building - for( i = 0; i < MAX_MISC; i++ ) - { - if( i < level.numBuildablesForRemoval ) - client->ps.misc[ i ] = level.markedBuildables[ i ]->s.number; - else - client->ps.misc[ i ] = 0; - } - } - else - { - for( i = 0; i < MAX_MISC; i++ ) - client->ps.misc[ i ] = 0; - } - break; - - default: - break; - } - - if( ent->client->pers.teamSelection == TEAM_HUMANS && - ( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X ) ) - { - int remainingStartupTime = MEDKIT_STARTUP_TIME - ( level.time - client->lastMedKitTime ); - - if( remainingStartupTime < 0 ) - { - if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && - ent->client->medKitHealthToRestore && - ent->client->ps.pm_type != PM_DEAD ) - { - ent->client->medKitHealthToRestore--; - ent->health++; - } - else - ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X; - } - else - { - if( ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] && - ent->client->medKitHealthToRestore && - ent->client->ps.pm_type != PM_DEAD ) - { - //partial increase - if( level.time > client->medKitIncrementTime ) - { - ent->client->medKitHealthToRestore--; - ent->health++; - - client->medKitIncrementTime = level.time + - ( remainingStartupTime / MEDKIT_STARTUP_SPEED ); - } - } - else - ent->client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_2X; - } - } - - //use fuel - if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) && - BG_UpgradeIsActive( UP_JETPACK, ent->client->ps.stats ) && - ent->client->ps.stats[ STAT_FUEL ] > 0 ) - { - ent->client->ps.stats[ STAT_FUEL ] -= JETPACK_FUEL_USAGE; - if( ent->client->ps.stats[ STAT_FUEL ] <= 0 ) - { - ent->client->ps.stats[ STAT_FUEL ] = 0; - BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); - BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &client->ps ); - } - } - - //biores - if( BG_InventoryContainsUpgrade( UP_BIORES, ent->client->ps.stats ) && - ent->health < ent->client->ps.stats[ STAT_MAX_HEALTH ] ) - { - float hp, dmod; - - dmod = MIN( level.time - ent->lastDamageTime, 3000 ) / 3000.0f; - hp = (float)ent->health / ent->client->ps.stats[ STAT_MAX_HEALTH ]; - ent->client->bioresHealTimer += (BIORES_EQUATION) * 10 * dmod; - } - - if( ent->client->bioresHealTimer >= 100 ) - { - int delta; - - delta = ent->client->bioresHealTimer / 100; - ent->health = MAX( MIN( ent->health+delta, ent->client->ps.stats[ STAT_MAX_HEALTH ] ), 1 ); - - ent->client->bioresHealTimer %= 100; - } - - } - - while( client->time1000 >= 1000 ) - { - client->time1000 -= 1000; - - //client is poisoned - if( client->ps.stats[ STAT_STATE ] & SS_POISONED ) - { - int damage = ALIEN_POISON_DMG; - - if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, client->ps.stats ) ) - damage -= BSUIT_POISON_PROTECTION; - - if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, client->ps.stats ) ) - damage -= HELMET_MK1_POISON_PROTECTION; - - if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, client->ps.stats ) ) - damage -= HELMET_MK2_POISON_PROTECTION; - - if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, client->ps.stats ) ) - damage -= LIGHTARMOUR_POISON_PROTECTION; - - if( BG_InventoryContainsUpgrade( UP_BIORES, client->ps.stats ) ) - damage *= BIORES_POISON_MODIFIER; - - G_Damage( ent, client->lastPoisonClient, client->lastPoisonClient, NULL, - 0, damage, 0, MOD_POISON ); - } - - // turn off life support when a team admits defeat - if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS && - level.surrenderTeam == TEAM_ALIENS ) - { - G_Damage( ent, NULL, NULL, NULL, NULL, - BG_Class( client->ps.stats[ STAT_CLASS ] )->regenRate, - DAMAGE_NO_ARMOR, MOD_SUICIDE ); - } - else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS && - level.surrenderTeam == TEAM_HUMANS ) - { - G_Damage( ent, NULL, NULL, NULL, NULL, 5, DAMAGE_NO_ARMOR, MOD_SUICIDE ); - } - - // lose some voice enthusiasm - if( client->voiceEnthusiasm > 0.0f ) - client->voiceEnthusiasm -= VOICE_ENTHUSIASM_DECAY; - else - client->voiceEnthusiasm = 0.0f; - - client->pers.aliveSeconds++; - if( g_freeFundPeriod.integer > 0 && - client->pers.aliveSeconds % g_freeFundPeriod.integer == 0 ) - { - // Give clients some credit periodically - if( G_TimeTilSuddenDeath( ) > 0 ) - { - if( client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - G_AddCreditToClient( client, FREEKILL_ALIEN, qtrue ); - else if( client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - G_AddCreditToClient( client, FREEKILL_HUMAN, qtrue ); - } - } - } - - while( client->time10000 >= 10000 ) - { - client->time10000 -= 10000; - - if( ent->client->ps.weapon == WP_ABUILD || - ent->client->ps.weapon == WP_ABUILD2 ) - { - AddScore( ent, ALIEN_BUILDER_SCOREINC ); - } - else if( ent->client->ps.weapon == WP_HBUILD ) - { - AddScore( ent, HUMAN_BUILDER_SCOREINC ); - } - - // Give score to basis that healed other aliens - if( ent->client->pers.hasHealed ) - { - if( client->ps.weapon == WP_ALEVEL1 ) - AddScore( ent, LEVEL1_REGEN_SCOREINC ); - else if( client->ps.weapon == WP_ALEVEL1_UPG ) - AddScore( ent, LEVEL1_UPG_REGEN_SCOREINC ); - - ent->client->pers.hasHealed = qfalse; - } - } - - // Regenerate Adv. Dragoon barbs - if( client->ps.weapon == WP_ALEVEL3_UPG ) - { - if( client->ps.ammo < BG_Weapon( WP_ALEVEL3_UPG )->maxAmmo ) - { - if( ent->timestamp + LEVEL3_BOUNCEBALL_REGEN < level.time ) - { - client->ps.ammo++; - ent->timestamp = level.time; - } - } - else - ent->timestamp = level.time; - } -} - -/* -==================== -ClientIntermissionThink -==================== -*/ -void ClientIntermissionThink( gclient_t *client ) -{ - client->ps.eFlags &= ~EF_FIRING; - client->ps.eFlags &= ~EF_FIRING2; - - // the level will exit when everyone wants to or after timeouts - - // swap and latch button actions - client->oldbuttons = client->buttons; - client->buttons = client->pers.cmd.buttons; - if( client->buttons & ( BUTTON_ATTACK | BUTTON_USE_HOLDABLE ) & ( client->oldbuttons ^ client->buttons ) ) - client->readyToExit = 1; -} - - -/* -================ -ClientEvents - -Events will be passed on to the clients for presentation, -but any server game effects are handled here -================ -*/ -void ClientEvents( gentity_t *ent, int oldEventSequence ) -{ - int i; - int event; - gclient_t *client; - int damage; - vec3_t dir; - vec3_t point, mins; - float fallDistance; - class_t class; - - client = ent->client; - class = client->ps.stats[ STAT_CLASS ]; - - if( oldEventSequence < client->ps.eventSequence - MAX_PS_EVENTS ) - oldEventSequence = client->ps.eventSequence - MAX_PS_EVENTS; - - for( i = oldEventSequence; i < client->ps.eventSequence; i++ ) - { - event = client->ps.events[ i & ( MAX_PS_EVENTS - 1 ) ]; - - switch( event ) - { - case EV_FALL_MEDIUM: - case EV_FALL_FAR: - if( ent->s.eType != ET_PLAYER ) - break; // not in the player model - - fallDistance = ( (float)client->ps.stats[ STAT_FALLDIST ] - MIN_FALL_DISTANCE ) / - ( MAX_FALL_DISTANCE - MIN_FALL_DISTANCE ); - - if( fallDistance < 0.0f ) - fallDistance = 0.0f; - else if( fallDistance > 1.0f ) - fallDistance = 1.0f; - - damage = (int)( (float)BG_Class( class )->health * - BG_Class( class )->fallDamage * fallDistance ); - - VectorSet( dir, 0, 0, 1 ); - BG_ClassBoundingBox( class, mins, NULL, NULL, NULL, NULL ); - mins[ 0 ] = mins[ 1 ] = 0.0f; - VectorAdd( client->ps.origin, mins, point ); - - ent->pain_debounce_time = level.time + 200; // no normal pain sound - G_Damage( ent, NULL, NULL, dir, point, damage, DAMAGE_NO_LOCDAMAGE, MOD_FALLING ); - break; - - case EV_FIRE_WEAPON: - FireWeapon( ent ); - break; - - case EV_FIRE_WEAPON2: - FireWeapon2( ent ); - break; - - case EV_FIRE_WEAPON3: - FireWeapon3( ent ); - break; - - case EV_NOAMMO: - break; - - default: - break; - } - } -} - - -/* -============== -SendPendingPredictableEvents -============== -*/ -void SendPendingPredictableEvents( playerState_t *ps ) -{ - gentity_t *t; - int event, seq; - int extEvent, number; - - // if there are still events pending - if( ps->entityEventSequence < ps->eventSequence ) - { - // create a temporary entity for this event which is sent to everyone - // except the client who generated the event - seq = ps->entityEventSequence & ( MAX_PS_EVENTS - 1 ); - event = ps->events[ seq ] | ( ( ps->entityEventSequence & 3 ) << 8 ); - // set external event to zero before calling BG_PlayerStateToEntityState - extEvent = ps->externalEvent; - ps->externalEvent = 0; - // create temporary entity for event - t = G_TempEntity( ps->origin, event ); - number = t->s.number; - BG_PlayerStateToEntityState( ps, &t->s, qtrue ); - t->s.number = number; - t->s.eType = ET_EVENTS + event; - t->s.eFlags |= EF_PLAYER_EVENT; - t->s.otherEntityNum = ps->clientNum; - // send to everyone except the client who generated the event - t->r.svFlags |= SVF_NOTSINGLECLIENT; - t->r.singleClient = ps->clientNum; - // set back external event - ps->externalEvent = extEvent; - } -} - -/* -============== - G_UnlaggedStore - - Called on every server frame. Stores position data for the client at that - into client->unlaggedHist[] and the time into level.unlaggedTimes[]. - This data is used by G_UnlaggedCalc() -============== -*/ -void G_UnlaggedStore( void ) -{ - int i = 0; - gentity_t *ent; - unlagged_t *save; - - if( !g_unlagged.integer ) - return; - level.unlaggedIndex++; - if( level.unlaggedIndex >= MAX_UNLAGGED_MARKERS ) - level.unlaggedIndex = 0; - - level.unlaggedTimes[ level.unlaggedIndex ] = level.time; - - for( i = 0; i < level.maxclients; i++ ) - { - ent = &g_entities[ i ]; - save = &ent->client->unlaggedHist[ level.unlaggedIndex ]; - save->used = qfalse; - if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) - continue; - if( ent->client->pers.connected != CON_CONNECTED ) - continue; - VectorCopy( ent->r.mins, save->mins ); - VectorCopy( ent->r.maxs, save->maxs ); - VectorCopy( ent->s.pos.trBase, save->origin ); - save->used = qtrue; - } -} - -/* -============== - G_UnlaggedClear - - Mark all unlaggedHist[] markers for this client invalid. Useful for - preventing teleporting and death. -============== -*/ -void G_UnlaggedClear( gentity_t *ent ) -{ - int i; - - for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) - ent->client->unlaggedHist[ i ].used = qfalse; -} - -/* -============== - G_UnlaggedCalc - - Loops through all active clients and calculates their predicted position - for time then stores it in client->unlaggedCalc -============== -*/ -void G_UnlaggedCalc( int time, gentity_t *rewindEnt ) -{ - int i = 0; - gentity_t *ent; - int startIndex = level.unlaggedIndex; - int stopIndex = -1; - int frameMsec = 0; - float lerp = 0.5f; - - if( !g_unlagged.integer ) - return; - - // clear any calculated values from a previous run - for( i = 0; i < level.maxclients; i++ ) - { - ent = &g_entities[ i ]; - ent->client->unlaggedCalc.used = qfalse; - } - - for( i = 0; i < MAX_UNLAGGED_MARKERS; i++ ) - { - if( level.unlaggedTimes[ startIndex ] <= time ) - break; - stopIndex = startIndex; - if( --startIndex < 0 ) - startIndex = MAX_UNLAGGED_MARKERS - 1; - } - if( i == MAX_UNLAGGED_MARKERS ) - { - // if we searched all markers and the oldest one still isn't old enough - // just use the oldest marker with no lerping - lerp = 0.0f; - } - - // client is on the current frame, no need for unlagged - if( stopIndex == -1 ) - return; - - // lerp between two markers - frameMsec = level.unlaggedTimes[ stopIndex ] - - level.unlaggedTimes[ startIndex ]; - if( frameMsec > 0 ) - { - lerp = ( float )( time - level.unlaggedTimes[ startIndex ] ) / - ( float )frameMsec; - } - - for( i = 0; i < level.maxclients; i++ ) - { - ent = &g_entities[ i ]; - if( ent == rewindEnt ) - continue; - if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) - continue; - if( ent->client->pers.connected != CON_CONNECTED ) - continue; - if( !ent->client->unlaggedHist[ startIndex ].used ) - continue; - if( !ent->client->unlaggedHist[ stopIndex ].used ) - continue; - - // between two unlagged markers - VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].mins, - ent->client->unlaggedHist[ stopIndex ].mins, - ent->client->unlaggedCalc.mins ); - VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].maxs, - ent->client->unlaggedHist[ stopIndex ].maxs, - ent->client->unlaggedCalc.maxs ); - VectorLerp( lerp, ent->client->unlaggedHist[ startIndex ].origin, - ent->client->unlaggedHist[ stopIndex ].origin, - ent->client->unlaggedCalc.origin ); - - ent->client->unlaggedCalc.used = qtrue; - } -} - -/* -============== - G_UnlaggedOff - - Reverses the changes made to all active clients by G_UnlaggedOn() -============== -*/ -void G_UnlaggedOff( void ) -{ - int i = 0; - gentity_t *ent; - - if( !g_unlagged.integer ) - return; - - for( i = 0; i < level.maxclients; i++ ) - { - ent = &g_entities[ i ]; - if( !ent->client->unlaggedBackup.used ) - continue; - VectorCopy( ent->client->unlaggedBackup.mins, ent->r.mins ); - VectorCopy( ent->client->unlaggedBackup.maxs, ent->r.maxs ); - VectorCopy( ent->client->unlaggedBackup.origin, ent->r.currentOrigin ); - ent->client->unlaggedBackup.used = qfalse; - trap_LinkEntity( ent ); - } -} - -/* -============== - G_UnlaggedOn - - Called after G_UnlaggedCalc() to apply the calculated values to all active - clients. Once finished tracing, G_UnlaggedOff() must be called to restore - the clients' position data - - As an optimization, all clients that have an unlagged position that is - not touchable at "range" from "muzzle" will be ignored. This is required - to prevent a huge amount of trap_LinkEntity() calls per user cmd. -============== -*/ - -void G_UnlaggedOn( gentity_t *attacker, vec3_t muzzle, float range ) -{ - int i = 0; - gentity_t *ent; - unlagged_t *calc; - - if( !g_unlagged.integer ) - return; - - if( !attacker->client->pers.useUnlagged ) - return; - - for( i = 0; i < level.maxclients; i++ ) - { - ent = &g_entities[ i ]; - calc = &ent->client->unlaggedCalc; - - if( !calc->used ) - continue; - if( ent->client->unlaggedBackup.used ) - continue; - if( !ent->r.linked || !( ent->r.contents & CONTENTS_BODY ) ) - continue; - if( VectorCompare( ent->r.currentOrigin, calc->origin ) ) - continue; - if( muzzle ) - { - float r1 = Distance( calc->origin, calc->maxs ); - float r2 = Distance( calc->origin, calc->mins ); - float maxRadius = ( r1 > r2 ) ? r1 : r2; - - if( Distance( muzzle, calc->origin ) > range + maxRadius ) - continue; - } - - // create a backup of the real positions - VectorCopy( ent->r.mins, ent->client->unlaggedBackup.mins ); - VectorCopy( ent->r.maxs, ent->client->unlaggedBackup.maxs ); - VectorCopy( ent->r.currentOrigin, ent->client->unlaggedBackup.origin ); - ent->client->unlaggedBackup.used = qtrue; - - // move the client to the calculated unlagged position - VectorCopy( calc->mins, ent->r.mins ); - VectorCopy( calc->maxs, ent->r.maxs ); - VectorCopy( calc->origin, ent->r.currentOrigin ); - trap_LinkEntity( ent ); - } -} -/* -============== - G_UnlaggedDetectCollisions - - cgame prediction will predict a client's own position all the way up to - the current time, but only updates other player's positions up to the - postition sent in the most recent snapshot. - - This allows player X to essentially "move through" the position of player Y - when player X's cmd is processed with Pmove() on the server. This is because - player Y was clipping player X's Pmove() on his client, but when the same - cmd is processed with Pmove on the server it is not clipped. - - Long story short (too late): don't use unlagged positions for players who - were blocking this player X's client-side Pmove(). This makes the assumption - that if player X's movement was blocked in the client he's going to still - be up against player Y when the Pmove() is run on the server with the - same cmd. - - NOTE: this must be called after Pmove() and G_UnlaggedCalc() -============== -*/ -static void G_UnlaggedDetectCollisions( gentity_t *ent ) -{ - unlagged_t *calc; - trace_t tr; - float r1, r2; - float range; - - if( !g_unlagged.integer ) - return; - - if( !ent->client->pers.useUnlagged ) - return; - - calc = &ent->client->unlaggedCalc; - - // if the client isn't moving, this is not necessary - if( VectorCompare( ent->client->oldOrigin, ent->client->ps.origin ) ) - return; - - range = Distance( ent->client->oldOrigin, ent->client->ps.origin ); - - // increase the range by the player's largest possible radius since it's - // the players bounding box that collides, not their origin - r1 = Distance( calc->origin, calc->mins ); - r2 = Distance( calc->origin, calc->maxs ); - range += ( r1 > r2 ) ? r1 : r2; - - G_UnlaggedOn( ent, ent->client->oldOrigin, range ); - - trap_Trace(&tr, ent->client->oldOrigin, ent->r.mins, ent->r.maxs, - ent->client->ps.origin, ent->s.number, MASK_PLAYERSOLID ); - if( tr.entityNum >= 0 && tr.entityNum < MAX_CLIENTS ) - g_entities[ tr.entityNum ].client->unlaggedCalc.used = qfalse; - - G_UnlaggedOff( ); -} - -/* -============== -ClientThink - -This will be called once for each client frame, which will -usually be a couple times for each server frame on fast clients. - -If "g_synchronousClients 1" is set, this will be called exactly -once for each server frame, which makes for smooth demo recording. -============== -*/ -void ClientThink_real( gentity_t *ent ) -{ - gclient_t *client; - pmove_t pm; - int oldEventSequence; - int msec; - usercmd_t *ucmd; - - client = ent->client; - - // don't think if the client is not yet connected (and thus not yet spawned in) - if( client->pers.connected != CON_CONNECTED ) - return; - - // mark the time, so the connection sprite can be removed - ucmd = &ent->client->pers.cmd; - - // sanity check the command time to prevent speedup cheating - if( ucmd->serverTime > level.time + 200 ) - { - ucmd->serverTime = level.time + 200; -// G_Printf("serverTime <<<<<\n" ); - } - - if( ucmd->serverTime < level.time - 1000 ) - { - ucmd->serverTime = level.time - 1000; -// G_Printf("serverTime >>>>>\n" ); - } - - msec = ucmd->serverTime - client->ps.commandTime; - // following others may result in bad times, but we still want - // to check for follow toggles - if( msec < 1 && client->sess.spectatorState != SPECTATOR_FOLLOW ) - return; - - if( msec > 200 ) - msec = 200; - - client->unlaggedTime = ucmd->serverTime; - - if( pmove_msec.integer < 8 ) - trap_Cvar_Set( "pmove_msec", "8" ); - else if( pmove_msec.integer > 33 ) - trap_Cvar_Set( "pmove_msec", "33" ); - - if( pmove_fixed.integer || client->pers.pmoveFixed ) - { - ucmd->serverTime = ( ( ucmd->serverTime + pmove_msec.integer - 1 ) / pmove_msec.integer ) * pmove_msec.integer; - //if (ucmd->serverTime - client->ps.commandTime <= 0) - // return; - } - - // - // check for exiting intermission - // - if( level.intermissiontime ) - { - ClientIntermissionThink( client ); - return; - } - - // spectators don't do much - if( client->sess.spectatorState != SPECTATOR_NOT ) - { - if( client->sess.spectatorState == SPECTATOR_SCOREBOARD ) - return; - - SpectatorThink( ent, ucmd ); - return; - } - - G_namelog_update_score( client ); - - // check for inactivity timer, but never drop the local client of a non-dedicated server - if( !ClientInactivityTimer( ent ) ) - return; - - // calculate where ent is currently seeing all the other active clients - G_UnlaggedCalc( ent->client->unlaggedTime, ent ); - - if( client->noclip ) - client->ps.pm_type = PM_NOCLIP; - else if( client->ps.stats[ STAT_HEALTH ] <= 0 ) - client->ps.pm_type = PM_DEAD; - else if( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED || - client->ps.stats[ STAT_STATE ] & SS_GRABBED ) - client->ps.pm_type = PM_GRABBED; - else if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) - client->ps.pm_type = PM_JETPACK; - else - client->ps.pm_type = PM_NORMAL; - - if( ( client->ps.stats[ STAT_STATE ] & SS_GRABBED ) && - client->grabExpiryTime < level.time ) - client->ps.stats[ STAT_STATE ] &= ~SS_GRABBED; - - if( ( client->ps.stats[ STAT_STATE ] & SS_BLOBLOCKED ) && - client->lastLockTime + LOCKBLOB_LOCKTIME < level.time ) - client->ps.stats[ STAT_STATE ] &= ~SS_BLOBLOCKED; - - if( ( client->ps.stats[ STAT_STATE ] & SS_SLOWLOCKED ) && - client->lastSlowTime + ABUILDER_BLOB_TIME < level.time ) - client->ps.stats[ STAT_STATE ] &= ~SS_SLOWLOCKED; - - // Update boosted state flags - client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTEDWARNING; - if( client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) - { - if( level.time - client->boostedTime >= BOOST_TIME ) - client->ps.stats[ STAT_STATE ] &= ~SS_BOOSTED; - else if( level.time - client->boostedTime >= BOOST_WARN_TIME ) - client->ps.stats[ STAT_STATE ] |= SS_BOOSTEDWARNING; - } - - // Check if poison cloud has worn off - if( ( client->ps.eFlags & EF_POISONCLOUDED ) && - BG_PlayerPoisonCloudTime( &client->ps ) - level.time + - client->lastPoisonCloudedTime <= 0 ) - client->ps.eFlags &= ~EF_POISONCLOUDED; - - if( client->ps.stats[ STAT_STATE ] & SS_POISONED && - client->lastPoisonTime + ALIEN_POISON_TIME < level.time ) - client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; - - client->ps.gravity = g_gravity.value; - - if( BG_InventoryContainsUpgrade( UP_MEDKIT, client->ps.stats ) && - BG_UpgradeIsActive( UP_MEDKIT, client->ps.stats ) ) - { - //if currently using a medkit or have no need for a medkit now - if( client->ps.stats[ STAT_STATE ] & SS_HEALING_2X || - ( client->ps.stats[ STAT_HEALTH ] == client->ps.stats[ STAT_MAX_HEALTH ] && - !( client->ps.stats[ STAT_STATE ] & SS_POISONED ) ) ) - { - BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); - } - else if( client->ps.stats[ STAT_HEALTH ] > 0 ) - { - //remove anti toxin - BG_DeactivateUpgrade( UP_MEDKIT, client->ps.stats ); - BG_RemoveUpgradeFromInventory( UP_MEDKIT, client->ps.stats ); - - client->ps.stats[ STAT_STATE ] &= ~SS_POISONED; - client->poisonImmunityTime = level.time + MEDKIT_POISON_IMMUNITY_TIME; - - client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; - client->lastMedKitTime = level.time; - client->medKitHealthToRestore = - client->ps.stats[ STAT_MAX_HEALTH ] - client->ps.stats[ STAT_HEALTH ]; - client->medKitIncrementTime = level.time + - ( MEDKIT_STARTUP_TIME / MEDKIT_STARTUP_SPEED ); - - G_AddEvent( ent, EV_MEDKIT_USED, 0 ); - } - } - - // Replenish alien health - if( level.surrenderTeam != client->pers.teamSelection && - ent->nextRegenTime >= 0 && ent->nextRegenTime < level.time ) - { - float regenRate = - BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->regenRate; - - if( ent->health <= 0 || ent->nextRegenTime < 0 || regenRate == 0 ) - ent->nextRegenTime = -1; // no regen - else - { - int entityList[ MAX_GENTITIES ]; - int i, num; - int count, interval; - vec3_t range, mins, maxs; - float modifier = 1.0f; - - VectorSet( range, REGEN_BOOST_RANGE, REGEN_BOOST_RANGE, - REGEN_BOOST_RANGE ); - VectorAdd( client->ps.origin, range, maxs ); - VectorSubtract( client->ps.origin, range, mins ); - - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - gentity_t *boost = &g_entities[ entityList[ i ] ]; - - if( Distance( client->ps.origin, boost->s.origin ) > REGEN_BOOST_RANGE ) - continue; - - if( modifier < BOOSTER_REGEN_MOD && boost->s.eType == ET_BUILDABLE && - boost->s.modelindex == BA_A_BOOSTER && boost->spawned && - boost->health > 0 && boost->powered ) - { - modifier = BOOSTER_REGEN_MOD; - continue; - } - - if( boost->s.eType == ET_PLAYER && boost->client && - boost->client->pers.teamSelection == - ent->client->pers.teamSelection && boost->health > 0 ) - { - class_t class = boost->client->ps.stats[ STAT_CLASS ]; - qboolean didBoost = qfalse; - - if( class == PCL_ALIEN_LEVEL1 && modifier < LEVEL1_REGEN_MOD ) - { - modifier = LEVEL1_REGEN_MOD; - didBoost = qtrue; - } - else if( class == PCL_ALIEN_LEVEL1_UPG && - modifier < LEVEL1_UPG_REGEN_MOD ) - { - modifier = LEVEL1_UPG_REGEN_MOD; - didBoost = qtrue; - } - - if( didBoost && ent->health < client->ps.stats[ STAT_MAX_HEALTH ] ) - boost->client->pers.hasHealed = qtrue; - } - } - - // Transmit heal rate to the client so it can be displayed on the HUD - client->ps.stats[ STAT_STATE ] |= SS_HEALING_ACTIVE; - client->ps.stats[ STAT_STATE ] &= ~( SS_HEALING_2X | SS_HEALING_3X ); - if( modifier == 1.0f && !G_FindCreep( ent ) ) - { - client->ps.stats[ STAT_STATE ] &= ~SS_HEALING_ACTIVE; - modifier *= ALIEN_REGEN_NOCREEP_MOD; - } - else if( modifier >= 3.0f ) - client->ps.stats[ STAT_STATE ] |= SS_HEALING_3X; - else if( modifier >= 2.0f ) - client->ps.stats[ STAT_STATE ] |= SS_HEALING_2X; - - interval = 1000 / ( regenRate * modifier ); - // if recovery interval is less than frametime, compensate - count = 1 + ( level.time - ent->nextRegenTime ) / interval; - - ent->health += count; - ent->nextRegenTime += count * interval; - - // if at max health, clear damage counters - if( ent->health >= client->ps.stats[ STAT_MAX_HEALTH ] ) - { - ent->health = client->ps.stats[ STAT_MAX_HEALTH ]; - for( i = 0; i < MAX_CLIENTS; i++ ) - ent->credits[ i ] = 0; - } - } - } - - if( BG_InventoryContainsUpgrade( UP_GRENADE, client->ps.stats ) && - BG_UpgradeIsActive( UP_GRENADE, client->ps.stats ) ) - { - int lastWeapon = ent->s.weapon; - - //remove grenade - BG_DeactivateUpgrade( UP_GRENADE, client->ps.stats ); - BG_RemoveUpgradeFromInventory( UP_GRENADE, client->ps.stats ); - - //M-M-M-M-MONSTER HACK - ent->s.weapon = WP_GRENADE; - FireWeapon( ent ); - ent->s.weapon = lastWeapon; - } - - // set speed - if( client->ps.pm_type == PM_NOCLIP ) - client->ps.speed = client->pers.flySpeed; - else - client->ps.speed = g_speed.value * - BG_Class( client->ps.stats[ STAT_CLASS ] )->speed; - - if( client->lastCreepSlowTime + CREEP_TIMEOUT < level.time ) - client->ps.stats[ STAT_STATE ] &= ~SS_CREEPSLOWED; - - //randomly disable the jet pack if damaged - if( BG_InventoryContainsUpgrade( UP_JETPACK, client->ps.stats ) && - BG_UpgradeIsActive( UP_JETPACK, client->ps.stats ) ) - { - if( ent->lastDamageTime + JETPACK_DISABLE_TIME > level.time ) - { - if( random( ) > JETPACK_DISABLE_CHANCE ) - client->ps.pm_type = PM_NORMAL; - } - - //switch jetpack off if no reactor - //if( !G_Reactor( ) ) - // BG_DeactivateUpgrade( UP_JETPACK, client->ps.stats ); - } - - // set up for pmove - oldEventSequence = client->ps.eventSequence; - - memset( &pm, 0, sizeof( pm ) ); - - if( ent->flags & FL_FORCE_GESTURE ) - { - ent->flags &= ~FL_FORCE_GESTURE; - ent->client->pers.cmd.buttons |= BUTTON_GESTURE; - } - - // clear fall velocity before every pmove - client->pmext.fallVelocity = 0.0f; - - pm.ps = &client->ps; - pm.pmext = &client->pmext; - pm.cmd = *ucmd; - - if( pm.ps->pm_type == PM_DEAD ) - pm.tracemask = MASK_DEADSOLID; - else - pm.tracemask = MASK_PLAYERSOLID; - - pm.trace = G_PMTraceHack; - pm.pointcontents = trap_PointContents; - pm.debugLevel = g_debugMove.integer; - pm.noFootsteps = 0; - - pm.pmove_fixed = pmove_fixed.integer | client->pers.pmoveFixed; - pm.pmove_msec = pmove_msec.integer; - - VectorCopy( client->ps.origin, client->oldOrigin ); - - // moved from after Pmove -- potentially the cause of - // future triggering bugs - if( !ent->client->noclip ) - G_TouchTriggers( ent ); - - Pmove( &pm ); - - G_UnlaggedDetectCollisions( ent ); - - // save results of pmove - if( ent->client->ps.eventSequence != oldEventSequence ) - ent->eventTime = level.time; - - if( g_smoothClients.integer ) - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); - else - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); - - switch( client->ps.weapon ) - { - case WP_ALEVEL0: - if( !CheckVenomAttack( ent ) ) - { - client->ps.weaponstate = WEAPON_READY; - } - else - { - client->ps.generic1 = WPM_PRIMARY; - G_AddEvent( ent, EV_FIRE_WEAPON, 0 ); - } - break; - - case WP_ALEVEL1: - case WP_ALEVEL1_UPG: - CheckGrabAttack( ent ); - break; - - case WP_ALEVEL3: - case WP_ALEVEL3_UPG: - if( !CheckPounceAttack( ent ) ) - { - client->ps.weaponstate = WEAPON_READY; - } - else - { - client->ps.generic1 = WPM_SECONDARY; - G_AddEvent( ent, EV_FIRE_WEAPON2, 0 ); - } - break; - - case WP_ALEVEL4: - // If not currently in a trample, reset the trample bookkeeping data - if( !( client->ps.pm_flags & PMF_CHARGE ) && client->trampleBuildablesHitPos ) - { - ent->client->trampleBuildablesHitPos = 0; - memset( ent->client->trampleBuildablesHit, 0, sizeof( ent->client->trampleBuildablesHit ) ); - } - break; - - case WP_HBUILD: - CheckCkitRepair( ent ); - break; - - default: - break; - } - - SendPendingPredictableEvents( &ent->client->ps ); - - if( !( ent->client->ps.eFlags & EF_FIRING ) ) - client->fireHeld = qfalse; // for grapple - if( !( ent->client->ps.eFlags & EF_FIRING2 ) ) - client->fire2Held = qfalse; - - // use the snapped origin for linking so it matches client predicted versions - VectorCopy( ent->s.pos.trBase, ent->r.currentOrigin ); - - VectorCopy( pm.mins, ent->r.mins ); - VectorCopy( pm.maxs, ent->r.maxs ); - - ent->waterlevel = pm.waterlevel; - ent->watertype = pm.watertype; - - // touch other objects - ClientImpacts( ent, &pm ); - - // execute client events - ClientEvents( ent, oldEventSequence ); - - // link entity now, after any personal teleporters have been used - trap_LinkEntity( ent ); - - // NOTE: now copy the exact origin over otherwise clients can be snapped into solid - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - VectorCopy( ent->client->ps.origin, ent->s.origin ); - - // save results of triggers and client events - if( ent->client->ps.eventSequence != oldEventSequence ) - ent->eventTime = level.time; - - // Don't think anymore if dead - if( client->ps.stats[ STAT_HEALTH ] <= 0 ) - return; - - // swap and latch button actions - client->oldbuttons = client->buttons; - client->buttons = ucmd->buttons; - client->latched_buttons |= client->buttons & ~client->oldbuttons; - - if( ( client->buttons & BUTTON_USE_EVOLVE ) && !( client->oldbuttons & BUTTON_USE_EVOLVE ) && - client->ps.stats[ STAT_HEALTH ] > 0 ) - { - trace_t trace; - vec3_t eyes, view, point; - gentity_t *traceEnt; - -#define USE_OBJECT_RANGE 64 - - int entityList[ MAX_GENTITIES ]; - vec3_t range = { USE_OBJECT_RANGE, USE_OBJECT_RANGE, USE_OBJECT_RANGE }; - vec3_t mins, maxs; - int i, num; - - // look for object infront of player - AngleVectors( client->ps.viewangles, view, NULL, NULL ); - BG_GetClientViewOrigin( &client->ps, eyes ); // !@#CUBOID - VectorMA( eyes, USE_OBJECT_RANGE, view, point ); - - trap_Trace( &trace, client->ps.origin, NULL, NULL, point, ent->s.number, MASK_SHOT ); - traceEnt = &g_entities[ trace.entityNum ]; - - if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) - traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context - else - { - //no entity in front of player - do a small area search - - VectorAdd( client->ps.origin, range, maxs ); - VectorSubtract( client->ps.origin, range, mins ); - - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - traceEnt = &g_entities[ entityList[ i ] ]; - - if( traceEnt && traceEnt->buildableTeam == client->ps.stats[ STAT_TEAM ] && traceEnt->use ) - { - traceEnt->use( traceEnt, ent, ent ); //other and activator are the same in this context - break; - } - } - - if( i == num && client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - { - if( BG_AlienCanEvolve( client->ps.stats[ STAT_CLASS ], - client->pers.credit, - g_alienStage.integer ) ) - { - //no nearby objects and alien - show class menu - G_TriggerMenu( ent->client->ps.clientNum, MN_A_INFEST ); - } - else - { - //flash frags - G_AddEvent( ent, EV_ALIEN_EVOLVE_FAILED, 0 ); - } - } - } - } - - client->ps.persistant[ PERS_BP ] = G_GetBuildPoints( client->ps.origin, - client->ps.stats[ STAT_TEAM ] ); - client->ps.persistant[ PERS_MARKEDBP ] = G_GetMarkedBuildPoints( client->ps.origin, - client->ps.stats[ STAT_TEAM ] ); - - if( client->ps.persistant[ PERS_BP ] < 0 ) - client->ps.persistant[ PERS_BP ] = 0; - - // perform once-a-second actions - ClientTimerActions( ent, msec ); - - if( ent->suicideTime > 0 && ent->suicideTime < level.time ) - { - ent->flags &= ~FL_GODMODE; - ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; - player_die( ent, ent, ent, 100000, MOD_SUICIDE ); - - ent->suicideTime = 0; - } -} - -/* -================== -ClientThink - -A new command has arrived from the client -================== -*/ -void ClientThink( int clientNum ) -{ - gentity_t *ent; - - ent = g_entities + clientNum; - trap_GetUsercmd( clientNum, &ent->client->pers.cmd ); - - // mark the time we got info, so we can display the - // phone jack if they don't get any for a while - ent->client->lastCmdTime = level.time; - - if( !g_synchronousClients.integer ) - ClientThink_real( ent ); -} - - -void G_RunClient( gentity_t *ent ) -{ - if( !g_synchronousClients.integer ) - return; - - ent->client->pers.cmd.serverTime = level.time; - ClientThink_real( ent ); -} - - -/* -================== -SpectatorClientEndFrame - -================== -*/ -void SpectatorClientEndFrame( gentity_t *ent ) -{ - gclient_t *cl; - int clientNum; - int score, ping; - - // if we are doing a chase cam or a remote view, grab the latest info - if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) - { - clientNum = ent->client->sess.spectatorClient; - if( clientNum >= 0 && clientNum < level.maxclients ) - { - cl = &level.clients[ clientNum ]; - if( cl->pers.connected == CON_CONNECTED ) - { - score = ent->client->ps.persistant[ PERS_SCORE ]; - ping = ent->client->ps.ping; - - // Copy - ent->client->ps = cl->ps; - - // Restore - ent->client->ps.persistant[ PERS_SCORE ] = score; - ent->client->ps.ping = ping; - - ent->client->ps.pm_flags |= PMF_FOLLOW; - ent->client->ps.pm_flags &= ~PMF_QUEUED; - } - } - } -} - -/* -============== -ClientEndFrame - -Called at the end of each server frame for each connected client -A fast client will have multiple ClientThink for each ClientEdFrame, -while a slow client may have multiple ClientEndFrame between ClientThink. -============== -*/ -void ClientEndFrame( gentity_t *ent ) -{ - clientPersistant_t *pers; - - if( ent->client->sess.spectatorState != SPECTATOR_NOT ) - { - SpectatorClientEndFrame( ent ); - return; - } - - pers = &ent->client->pers; - - // - // If the end of unit layout is displayed, don't give - // the player any normal movement attributes - // - if( level.intermissiontime ) - return; - - // burn from lava, etc - P_WorldEffects( ent ); - - // apply all the damage taken this frame - P_DamageFeedback( ent ); - - // add the EF_CONNECTION flag if we haven't gotten commands recently - if( level.time - ent->client->lastCmdTime > 1000 ) - ent->s.eFlags |= EF_CONNECTION; - else - ent->s.eFlags &= ~EF_CONNECTION; - - ent->client->ps.stats[ STAT_HEALTH ] = ent->health; // FIXME: get rid of ent->health... - - // respawn if dead - if( ent->client->ps.stats[ STAT_HEALTH ] <= 0 && level.time >= ent->client->respawnTime ) - respawn( ent ); - - G_SetClientSound( ent ); - - // set the latest infor - if( g_smoothClients.integer ) - BG_PlayerStateToEntityStateExtraPolate( &ent->client->ps, &ent->s, ent->client->ps.commandTime, qtrue ); - else - BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); - - SendPendingPredictableEvents( &ent->client->ps ); -} - diff --git a/src/game/g_client.c.orig b/src/game/g_client.c.orig deleted file mode 100644 index 735c59d..0000000 --- a/src/game/g_client.c.orig +++ /dev/null @@ -1,1600 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2009 Darklegion Development - -This file is part of Tremulous. - -Tremulous is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Tremulous is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "g_local.h" - -// g_client.c -- client functions that don't happen every frame - -static vec3_t playerMins = {-15, -15, -24}; -static vec3_t playerMaxs = {15, 15, 32}; - -/*QUAKED info_player_deathmatch (1 0 1) (-16 -16 -24) (16 16 32) initial -potential spawning position for deathmatch games. -The first time a player enters the game, they will be at an 'initial' spot. -Targets will be fired when someone spawns in on them. -"nobots" will prevent bots from using this spot. -"nohumans" will prevent non-bots from using this spot. -*/ -void SP_info_player_deathmatch( gentity_t *ent ) -{ - int i; - - G_SpawnInt( "nobots", "0", &i); - - if( i ) - ent->flags |= FL_NO_BOTS; - - G_SpawnInt( "nohumans", "0", &i ); - if( i ) - ent->flags |= FL_NO_HUMANS; -} - -/*QUAKED info_player_start (1 0 0) (-16 -16 -24) (16 16 32) -equivelant to info_player_deathmatch -*/ -void SP_info_player_start( gentity_t *ent ) -{ - ent->classname = "info_player_deathmatch"; - SP_info_player_deathmatch( ent ); -} - -/*QUAKED info_player_intermission (1 0 1) (-16 -16 -24) (16 16 32) -The intermission will be viewed from this point. Target an info_notnull for the view direction. -*/ -void SP_info_player_intermission( gentity_t *ent ) -{ -} - -/*QUAKED info_alien_intermission (1 0 1) (-16 -16 -24) (16 16 32) -The intermission will be viewed from this point. Target an info_notnull for the view direction. -*/ -void SP_info_alien_intermission( gentity_t *ent ) -{ -} - -/*QUAKED info_human_intermission (1 0 1) (-16 -16 -24) (16 16 32) -The intermission will be viewed from this point. Target an info_notnull for the view direction. -*/ -void SP_info_human_intermission( gentity_t *ent ) -{ -} - -/* -=============== -G_AddCreditToClient -=============== -*/ -void G_AddCreditToClient( gclient_t *client, short credit, qboolean cap ) -{ - int capAmount; - - if( !client ) - return; - - if( cap && credit > 0 ) - { - capAmount = client->pers.teamSelection == TEAM_ALIENS ? - ALIEN_MAX_CREDITS : HUMAN_MAX_CREDITS; - if( client->pers.credit < capAmount ) - { - client->pers.credit += credit; - if( client->pers.credit > capAmount ) - client->pers.credit = capAmount; - } - } - else - client->pers.credit += credit; - - if( client->pers.credit < 0 ) - client->pers.credit = 0; - - // Copy to ps so the client can access it - client->ps.persistant[ PERS_CREDIT ] = client->pers.credit; -} - - -/* -======================================================================= - - G_SelectSpawnPoint - -======================================================================= -*/ - -/* -================ -SpotWouldTelefrag - -================ -*/ -qboolean SpotWouldTelefrag( gentity_t *spot ) -{ - int i, num; - int touch[ MAX_GENTITIES ]; - gentity_t *hit; - vec3_t mins, maxs; - - VectorAdd( spot->s.origin, playerMins, mins ); - VectorAdd( spot->s.origin, playerMaxs, maxs ); - num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); - - for( i = 0; i < num; i++ ) - { - hit = &g_entities[ touch[ i ] ]; - //if ( hit->client && hit->client->ps.stats[STAT_HEALTH] > 0 ) { - if( hit->client ) - return qtrue; - } - - return qfalse; -} - - -/* -=========== -G_SelectRandomFurthestSpawnPoint - -Chooses a player start, deathmatch start, etc -============ -*/ -static gentity_t *G_SelectRandomFurthestSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) -{ - gentity_t *spot; - vec3_t delta; - float dist; - float list_dist[ 64 ]; - gentity_t *list_spot[ 64 ]; - int numSpots, rnd, i, j; - - numSpots = 0; - spot = NULL; - - while( ( spot = G_Find( spot, FOFS( classname ), "info_player_deathmatch" ) ) != NULL ) - { - if( SpotWouldTelefrag( spot ) ) - continue; - - VectorSubtract( spot->s.origin, avoidPoint, delta ); - dist = VectorLength( delta ); - - for( i = 0; i < numSpots; i++ ) - { - if( dist > list_dist[ i ] ) - { - if( numSpots >= 64 ) - numSpots = 64 - 1; - - for( j = numSpots; j > i; j-- ) - { - list_dist[ j ] = list_dist[ j - 1 ]; - list_spot[ j ] = list_spot[ j - 1 ]; - } - - list_dist[ i ] = dist; - list_spot[ i ] = spot; - numSpots++; - - if( numSpots > 64 ) - numSpots = 64; - - break; - } - } - - if( i >= numSpots && numSpots < 64 ) - { - list_dist[ numSpots ] = dist; - list_spot[ numSpots ] = spot; - numSpots++; - } - } - - if( !numSpots ) - { - spot = G_Find( NULL, FOFS( classname ), "info_player_deathmatch" ); - - if( !spot ) - G_Error( "Couldn't find a spawn point" ); - - VectorCopy( spot->s.origin, origin ); - origin[ 2 ] += 9; - VectorCopy( spot->s.angles, angles ); - return spot; - } - - // select a random spot from the spawn points furthest away - rnd = random( ) * ( numSpots / 2 ); - - VectorCopy( list_spot[ rnd ]->s.origin, origin ); - origin[ 2 ] += 9; - VectorCopy( list_spot[ rnd ]->s.angles, angles ); - - return list_spot[ rnd ]; -} - - -/* -================ -G_SelectSpawnBuildable - -find the nearest buildable of the right type that is -spawned/healthy/unblocked etc. -================ -*/ -static gentity_t *G_SelectSpawnBuildable( vec3_t preference, buildable_t buildable ) -{ - gentity_t *search, *spot; - - search = spot = NULL; - - while( ( search = G_Find( search, FOFS( classname ), - BG_Buildable( buildable, NULL )->entityName ) ) != NULL ) - { - if( !search->spawned ) - continue; - - if( search->health <= 0 ) - continue; - - if( !search->s.groundEntityNum ) - continue; - - if( search->clientSpawnTime > 0 ) - continue; - - if( G_CheckSpawnPoint( search->s.number, search->s.origin, - search->s.origin2, buildable, NULL ) != NULL ) - continue; - - if( !spot || DistanceSquared( preference, search->s.origin ) < - DistanceSquared( preference, spot->s.origin ) ) - spot = search; - } - - return spot; -} - -/* -=========== -G_SelectSpawnPoint - -Chooses a player start, deathmatch start, etc -============ -*/ -gentity_t *G_SelectSpawnPoint( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) -{ - return G_SelectRandomFurthestSpawnPoint( avoidPoint, origin, angles ); -} - - -/* -=========== -G_SelectTremulousSpawnPoint - -Chooses a player start, deathmatch start, etc -============ -*/ -gentity_t *G_SelectTremulousSpawnPoint( team_t team, vec3_t preference, vec3_t origin, vec3_t angles ) -{ - gentity_t *spot = NULL; - - if( team == TEAM_ALIENS ) - { - if( level.numAlienSpawns <= 0 ) - return NULL; - - spot = G_SelectSpawnBuildable( preference, BA_A_SPAWN ); - } - else if( team == TEAM_HUMANS ) - { - if( level.numHumanSpawns <= 0 ) - return NULL; - - spot = G_SelectSpawnBuildable( preference, BA_H_SPAWN ); - } - - //no available spots - if( !spot ) - return NULL; - - if( team == TEAM_ALIENS ) - G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_A_SPAWN, origin ); - else if( team == TEAM_HUMANS ) - G_CheckSpawnPoint( spot->s.number, spot->s.origin, spot->s.origin2, BA_H_SPAWN, origin ); - - VectorCopy( spot->s.angles, angles ); - angles[ ROLL ] = 0; - - return spot; - -} - - -/* -=========== -G_SelectSpectatorSpawnPoint - -============ -*/ -static gentity_t *G_SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) -{ - FindIntermissionPoint( ); - - VectorCopy( level.intermission_origin, origin ); - VectorCopy( level.intermission_angle, angles ); - - return NULL; -} - - -/* -=========== -G_SelectAlienLockSpawnPoint - -Try to find a spawn point for alien intermission otherwise -use normal intermission spawn. -============ -*/ -gentity_t *G_SelectAlienLockSpawnPoint( vec3_t origin, vec3_t angles ) -{ - gentity_t *spot; - - spot = NULL; - spot = G_Find( spot, FOFS( classname ), "info_alien_intermission" ); - - if( !spot ) - return G_SelectSpectatorSpawnPoint( origin, angles ); - - VectorCopy( spot->s.origin, origin ); - VectorCopy( spot->s.angles, angles ); - - return spot; -} - - -/* -=========== -G_SelectHumanLockSpawnPoint - -Try to find a spawn point for human intermission otherwise -use normal intermission spawn. -============ -*/ -gentity_t *G_SelectHumanLockSpawnPoint( vec3_t origin, vec3_t angles ) -{ - gentity_t *spot; - - spot = NULL; - spot = G_Find( spot, FOFS( classname ), "info_human_intermission" ); - - if( !spot ) - return G_SelectSpectatorSpawnPoint( origin, angles ); - - VectorCopy( spot->s.origin, origin ); - VectorCopy( spot->s.angles, angles ); - - return spot; -} - - -/* -======================================================================= - -BODYQUE - -======================================================================= -*/ - - -/* -============= -BodySink - -After sitting around for five seconds, fall into the ground and dissapear -============= -*/ -static void BodySink( gentity_t *ent ) -{ - //run on first BodySink call - if( !ent->active ) - { - ent->active = qtrue; - - //sinking bodies can't be infested - ent->killedBy = ent->s.misc = MAX_CLIENTS; - ent->timestamp = level.time; - } - - if( level.time - ent->timestamp > 6500 ) - { - G_FreeEntity( ent ); - return; - } - - ent->nextthink = level.time + 100; - ent->s.pos.trBase[ 2 ] -= 1; -} - - -/* -============= -SpawnCorpse - -A player is respawning, so make an entity that looks -just like the existing corpse to leave behind. -============= -*/ -static void SpawnCorpse( gentity_t *ent ) -{ - gentity_t *body; - int contents; - vec3_t origin, dest; - trace_t tr; - float vDiff; - - VectorCopy( ent->r.currentOrigin, origin ); - - trap_UnlinkEntity( ent ); - - // if client is in a nodrop area, don't leave the body - contents = trap_PointContents( origin, -1 ); - if( contents & CONTENTS_NODROP ) - return; - - body = G_Spawn( ); - - VectorCopy( ent->s.apos.trBase, body->s.angles ); - body->s.eFlags = EF_DEAD; - body->s.eType = ET_CORPSE; - body->s.number = body - g_entities; - body->timestamp = level.time; - body->s.event = 0; - body->r.contents = CONTENTS_CORPSE; - body->s.clientNum = ent->client->ps.stats[ STAT_CLASS ]; - body->nonSegModel = ent->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL; - - if( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - body->classname = "humanCorpse"; - else - body->classname = "alienCorpse"; - - body->s.misc = MAX_CLIENTS; - - body->think = BodySink; - body->nextthink = level.time + 20000; - - body->s.legsAnim = ent->s.legsAnim; - - if( !body->nonSegModel ) - { - switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) - { - case BOTH_DEATH1: - case BOTH_DEAD1: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD1; - break; - case BOTH_DEATH2: - case BOTH_DEAD2: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD2; - break; - case BOTH_DEATH3: - case BOTH_DEAD3: - default: - body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; - break; - } - } - else - { - switch( body->s.legsAnim & ~ANIM_TOGGLEBIT ) - { - case NSPA_DEATH1: - case NSPA_DEAD1: - body->s.legsAnim = NSPA_DEAD1; - break; - case NSPA_DEATH2: - case NSPA_DEAD2: - body->s.legsAnim = NSPA_DEAD2; - break; - case NSPA_DEATH3: - case NSPA_DEAD3: - default: - body->s.legsAnim = NSPA_DEAD3; - break; - } - } - - body->takedamage = qfalse; - - body->health = ent->health = ent->client->ps.stats[ STAT_HEALTH ]; - ent->health = 0; - - //change body dimensions - BG_ClassBoundingBox( ent->client->ps.stats[ STAT_CLASS ], NULL, NULL, NULL, body->r.mins, body->r.maxs ); - vDiff = body->r.mins[ 2 ] - ent->r.mins[ 2 ]; - - //drop down to match the *model* origins of ent and body - VectorSet( dest, origin[ 0 ], origin[ 1 ], origin[ 2 ] - vDiff ); - trap_Trace( &tr, origin, body->r.mins, body->r.maxs, dest, body->s.number, body->clipmask ); - VectorCopy( tr.endpos, origin ); - - G_SetOrigin( body, origin ); - VectorCopy( origin, body->s.origin ); - body->s.pos.trType = TR_GRAVITY; - body->s.pos.trTime = level.time; - VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); - - VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); - trap_LinkEntity( body ); -} - -//====================================================================== - - -/* -================== -G_SetClientViewAngle - -================== -*/ -void G_SetClientViewAngle( gentity_t *ent, vec3_t angle ) -{ - int i; - - // set the delta angle - for( i = 0; i < 3; i++ ) - { - int cmdAngle; - - cmdAngle = ANGLE2SHORT( angle[ i ] ); - ent->client->ps.delta_angles[ i ] = cmdAngle - ent->client->pers.cmd.angles[ i ]; - } - - VectorCopy( angle, ent->s.angles ); - VectorCopy( ent->s.angles, ent->client->ps.viewangles ); -} - -/* -================ -respawn -================ -*/ -void respawn( gentity_t *ent ) -{ - int i; - - SpawnCorpse( ent ); - - // Clients can't respawn - they must go through the class cmd - ent->client->pers.classSelection = PCL_NONE; - ClientSpawn( ent, NULL, NULL, NULL ); - - // stop any following clients that don't have sticky spec on - for( i = 0; i < level.maxclients; i++ ) - { - if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && - level.clients[ i ].sess.spectatorClient == ent - g_entities ) - { - if( !( level.clients[ i ].pers.stickySpec ) ) - { - if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) - G_StopFollowing( &g_entities[ i ] ); - } - else - G_FollowLockView( &g_entities[ i ] ); - } - } -} - -static qboolean G_IsEmoticon( const char *s, qboolean *escaped ) -{ - int i, j; - const char *p = s; - char emoticon[ MAX_EMOTICON_NAME_LEN ] = {""}; - qboolean escape = qfalse; - - if( *p != '[' ) - return qfalse; - p++; - if( *p == '[' ) - { - escape = qtrue; - p++; - } - i = 0; - while( *p && i < ( MAX_EMOTICON_NAME_LEN - 1 ) ) - { - if( *p == ']' ) - { - for( j = 0; j < level.emoticonCount; j++ ) - { - if( !Q_stricmp( emoticon, level.emoticons[ j ].name ) ) - { - *escaped = escape; - return qtrue; - } - } - return qfalse; - } - emoticon[ i++ ] = *p; - emoticon[ i ] = '\0'; - p++; - } - return qfalse; -} - -/* -=========== -G_ClientCleanName -============ -*/ -static void G_ClientCleanName( const char *in, char *out, int outSize ) -{ - int len, colorlessLen; - char *p; - int spaces; - qboolean escaped; - qboolean invalid = qfalse; - - //save room for trailing null byte - outSize--; - - len = 0; - colorlessLen = 0; - p = out; - *p = 0; - spaces = 0; - - for( ; *in; in++ ) - { - // don't allow leading spaces - if( colorlessLen == 0 && *in == ' ' ) - continue; - - // don't allow nonprinting characters or (dead) console keys - if( *in < ' ' || *in > '}' || *in == '`' ) - continue; - - // check colors - if( Q_IsColorString( in ) ) - { - in++; - - // make sure room in dest for both chars - if( len > outSize - 2 ) - break; - - *out++ = Q_COLOR_ESCAPE; - - *out++ = *in; - - len += 2; - continue; - } - else if( !g_emoticonsAllowedInNames.integer && G_IsEmoticon( in, &escaped ) ) - { - // make sure room in dest for both chars - if( len > outSize - 2 ) - break; - - *out++ = '['; - *out++ = '['; - len += 2; - if( escaped ) - in++; - continue; - } - - // don't allow too many consecutive spaces - if( *in == ' ' ) - { - spaces++; - if( spaces > 3 ) - continue; - } - else - spaces = 0; - - if( len > outSize - 1 ) - break; - - *out++ = *in; - colorlessLen++; - len++; - } - - *out = 0; - - // don't allow names beginning with "[skipnotify]" because it messes up /ignore-related code - if( !Q_stricmpn( p, "[skipnotify]", 12 ) ) - invalid = qtrue; - - // don't allow comment-beginning strings because it messes up various parsers - if( strstr( p, "//" ) || strstr( p, "/*" ) ) - invalid = qtrue; - - // don't allow empty names - if( *p == 0 || colorlessLen == 0 ) - invalid = qtrue; - - // if something made the name bad, put them back to UnnamedPlayer - if( invalid ) - Q_strncpyz( p, "UnnamedPlayer", outSize ); -} - - -/* -====================== -G_NonSegModel - -Reads an animation.cfg to check for nonsegmentation -====================== -*/ -static qboolean G_NonSegModel( const char *filename ) -{ - char *text_p; - int len; - char *token; - char text[ 20000 ]; - fileHandle_t f; - - // load the file - len = trap_FS_FOpenFile( filename, &f, FS_READ ); - if( !f ) - { - G_Printf( "File not found: %s\n", filename ); - return qfalse; - } - - if( len < 0 ) - return qfalse; - - if( len == 0 || len >= sizeof( text ) - 1 ) - { - trap_FS_FCloseFile( f ); - G_Printf( "File %s is %s\n", filename, len == 0 ? "empty" : "too long" ); - return qfalse; - } - - trap_FS_Read( text, len, f ); - text[ len ] = 0; - trap_FS_FCloseFile( f ); - - // parse the text - text_p = text; - - // read optional parameters - while( 1 ) - { - token = COM_Parse( &text_p ); - - //EOF - if( !token[ 0 ] ) - break; - - if( !Q_stricmp( token, "nonsegmented" ) ) - return qtrue; - } - - return qfalse; -} - -/* -=========== -ClientUserInfoChanged - -Called from ClientConnect when the player first connects and -directly by the server system when the player updates a userinfo variable. - -The game can override any of the settings and call trap_SetUserinfo -if desired. -============ -*/ -char *ClientUserinfoChanged( int clientNum, qboolean forceName ) -{ - gentity_t *ent; - char *s; - char model[ MAX_QPATH ]; - char buffer[ MAX_QPATH ]; - char filename[ MAX_QPATH ]; - char oldname[ MAX_NAME_LENGTH ]; - char newname[ MAX_NAME_LENGTH ]; - char err[ MAX_STRING_CHARS ]; - qboolean revertName = qfalse; - gclient_t *client; - char userinfo[ MAX_INFO_STRING ]; - - ent = g_entities + clientNum; - client = ent->client; - - trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); - - // check for malformed or illegal info strings - if( !Info_Validate(userinfo) ) - { - trap_SendServerCommand( ent - g_entities, - "disconnect \"illegal or malformed userinfo\n\"" ); - trap_DropClient( ent - g_entities, - "dropped: illegal or malformed userinfo"); - return "Illegal or malformed userinfo"; - } - // If their userinfo overflowed, tremded is in the process of disconnecting them. - // If we send our own disconnect, it won't work, so just return to prevent crashes later - // in this function. This check must come after the Info_Validate call. - else if( !userinfo[ 0 ] ) - return "Empty (overflowed) userinfo"; - - // stickyspec toggle - s = Info_ValueForKey( userinfo, "cg_stickySpec" ); - client->pers.stickySpec = atoi( s ) != 0; - - // set name - Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); - s = Info_ValueForKey( userinfo, "name" ); - G_ClientCleanName( s, newname, sizeof( newname ) ); - - if( strcmp( oldname, newname ) ) - { - if( !forceName && client->pers.namelog->nameChangeTime && - level.time - client->pers.namelog->nameChangeTime <= - g_minNameChangePeriod.value * 1000 ) - { - trap_SendServerCommand( ent - g_entities, va( - "print \"Name change spam protection (g_minNameChangePeriod = %d)\n\"", - g_minNameChangePeriod.integer ) ); - revertName = qtrue; - } - else if( !forceName && g_maxNameChanges.integer > 0 && - client->pers.namelog->nameChanges >= g_maxNameChanges.integer ) - { - trap_SendServerCommand( ent - g_entities, va( - "print \"Maximum name changes reached (g_maxNameChanges = %d)\n\"", - g_maxNameChanges.integer ) ); - revertName = qtrue; - } - else if( !forceName && client->pers.namelog->muted ) - { - trap_SendServerCommand( ent - g_entities, - "print \"You cannot change your name while you are muted\n\"" ); - revertName = qtrue; - } - else if( !G_admin_name_check( ent, newname, err, sizeof( err ) ) ) - { - trap_SendServerCommand( ent - g_entities, va( "print \"%s\n\"", err ) ); - revertName = qtrue; - } - - if( revertName ) - { - Q_strncpyz( client->pers.netname, *oldname ? oldname : "UnnamedPlayer", - sizeof( client->pers.netname ) ); - Info_SetValueForKey( userinfo, "name", oldname ); - trap_SetUserinfo( clientNum, userinfo ); - } - else - { - G_CensorString( client->pers.netname, newname, - sizeof( client->pers.netname ), ent ); - if( !forceName && client->pers.connected == CON_CONNECTED ) - { - client->pers.namelog->nameChangeTime = level.time; - client->pers.namelog->nameChanges++; - } - if( *oldname ) - { - G_LogPrintf( "ClientRename: %i [%s] (%s) \"%s^7\" -> \"%s^7\" \"%c%s%c^7\"\n", - clientNum, client->pers.ip.str, client->pers.guid, - oldname, client->pers.netname, - DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); - } - } - G_namelog_update_name( client ); - } - - if( client->pers.classSelection == PCL_NONE ) - { - //This looks hacky and frankly it is. The clientInfo string needs to hold different - //model details to that of the spawning class or the info change will not be - //registered and an axis appears instead of the player model. There is zero chance - //the player can spawn with the battlesuit, hence this choice. - Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( PCL_HUMAN_BSUIT )->modelName, - BG_ClassConfig( PCL_HUMAN_BSUIT )->skinName ); - } - else - { - Com_sprintf( buffer, MAX_QPATH, "%s/%s", BG_ClassConfig( client->pers.classSelection )->modelName, - BG_ClassConfig( client->pers.classSelection )->skinName ); - - //model segmentation - Com_sprintf( filename, sizeof( filename ), "models/players/%s/animation.cfg", - BG_ClassConfig( client->pers.classSelection )->modelName ); - - if( G_NonSegModel( filename ) ) - client->ps.persistant[ PERS_STATE ] |= PS_NONSEGMODEL; - else - client->ps.persistant[ PERS_STATE ] &= ~PS_NONSEGMODEL; - } - Q_strncpyz( model, buffer, sizeof( model ) ); - - // wallwalk follow - s = Info_ValueForKey( userinfo, "cg_wwFollow" ); - - if( atoi( s ) ) - client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGFOLLOW; - else - client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGFOLLOW; - - // wallwalk toggle - s = Info_ValueForKey( userinfo, "cg_wwToggle" ); - - if( atoi( s ) ) - client->ps.persistant[ PERS_STATE ] |= PS_WALLCLIMBINGTOGGLE; - else - client->ps.persistant[ PERS_STATE ] &= ~PS_WALLCLIMBINGTOGGLE; - - // always sprint - s = Info_ValueForKey( userinfo, "cg_sprintToggle" ); - - if( atoi( s ) ) - client->ps.persistant[ PERS_STATE ] |= PS_SPRINTTOGGLE; - else - client->ps.persistant[ PERS_STATE ] &= ~PS_SPRINTTOGGLE; - - // fly speed - s = Info_ValueForKey( userinfo, "cg_flySpeed" ); - - if( *s ) - client->pers.flySpeed = atoi( s ); - else - client->pers.flySpeed = BG_Class( PCL_NONE )->speed; - - // disable blueprint errors - s = Info_ValueForKey( userinfo, "cg_disableBlueprintErrors" ); - - if( atoi( s ) ) - client->pers.disableBlueprintErrors = qtrue; - else - client->pers.disableBlueprintErrors = qfalse; - - // teamInfo - s = Info_ValueForKey( userinfo, "teamoverlay" ); - - if( atoi( s ) != 0 ) - client->pers.teamInfo = qtrue; - else - client->pers.teamInfo = qfalse; - - s = Info_ValueForKey( userinfo, "cg_unlagged" ); - if( !s[0] || atoi( s ) != 0 ) - client->pers.useUnlagged = qtrue; - else - client->pers.useUnlagged = qfalse; - - Q_strncpyz( client->pers.voice, Info_ValueForKey( userinfo, "voice" ), - sizeof( client->pers.voice ) ); - - // send over a subset of the userinfo keys so other clients can - // print scoreboards, display models, and play custom sounds - - Com_sprintf( userinfo, sizeof( userinfo ), - "n\\%s\\t\\%i\\model\\%s\\ig\\%16s\\v\\%s", - client->pers.netname, client->pers.teamSelection, model, - Com_ClientListString( &client->sess.ignoreList ), - client->pers.voice ); - - trap_SetConfigstring( CS_PLAYERS + clientNum, userinfo ); - - /*G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, userinfo );*/ - - return NULL; -} - - -/* -=========== -ClientConnect - -Called when a player begins connecting to the server. -Called again for every map change or tournement restart. - -The session information will be valid after exit. - -Return NULL if the client should be allowed, otherwise return -a string with the reason for denial. - -Otherwise, the client will be sent the current gamestate -and will eventually get to ClientBegin. - -firstTime will be qtrue the very first time a client connects -to the server machine, but qfalse on map changes and tournement -restarts. -============ -*/ -char *ClientConnect( int clientNum, qboolean firstTime ) -{ - char *value; - char *userInfoError; - gclient_t *client; - char userinfo[ MAX_INFO_STRING ]; - gentity_t *ent; - char reason[ MAX_STRING_CHARS ] = {""}; - int i; - - ent = &g_entities[ clientNum ]; - client = &level.clients[ clientNum ]; - - // ignore if client already connected - if( client->pers.connected != CON_DISCONNECTED ) - return NULL; - - ent->client = client; - memset( client, 0, sizeof( *client ) ); - - trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); - - value = Info_ValueForKey( userinfo, "cl_guid" ); - Q_strncpyz( client->pers.guid, value, sizeof( client->pers.guid ) ); - - value = Info_ValueForKey( userinfo, "ip" ); - // check for local client - if( !strcmp( value, "localhost" ) ) - client->pers.localClient = qtrue; - G_AddressParse( value, &client->pers.ip ); - - client->pers.admin = G_admin_admin( client->pers.guid ); - - // check for admin ban - if( G_admin_ban_check( ent, reason, sizeof( reason ) ) ) - { - return va( "%s", reason ); - } - - // check for a password - value = Info_ValueForKey( userinfo, "password" ); - - if( g_password.string[ 0 ] && Q_stricmp( g_password.string, "none" ) && - strcmp( g_password.string, value ) != 0 ) - return "Invalid password"; - - // add guid to session so we don't have to keep parsing userinfo everywhere - for( i = 0; i < sizeof( client->pers.guid ) - 1 && - isxdigit( client->pers.guid[ i ] ); i++ ); - - if( i < sizeof( client->pers.guid ) - 1 ) - return "Invalid GUID"; - - for( i = 0; i < level.maxclients; i++ ) - { - if( level.clients[ i ].pers.connected == CON_DISCONNECTED ) - continue; - - if( !Q_stricmp( client->pers.guid, level.clients[ i ].pers.guid ) ) - { - if( !G_ClientIsLagging( level.clients + i ) ) - { - trap_SendServerCommand( i, "cp \"Your GUID is not secure\"" ); - return "Duplicate GUID"; - } - trap_DropClient( i, "Ghost" ); - } - } - - client->pers.connected = CON_CONNECTING; - - // read or initialize the session data - if( firstTime || level.newSession ) - G_InitSessionData( client, userinfo ); - - G_ReadSessionData( client ); - - // get and distribute relevent paramters - G_namelog_connect( client ); - userInfoError = ClientUserinfoChanged( clientNum, qfalse ); - if( userInfoError != NULL ) - return userInfoError; - - G_LogPrintf( "ClientConnect: %i [%s] (%s) \"%s^7\" \"%c%s%c^7\"\n", - clientNum, client->pers.ip.str, client->pers.guid, - client->pers.netname, - DECOLOR_OFF, client->pers.netname, DECOLOR_ON ); - - // don't do the "xxx connected" messages if they were caried over from previous level - if( firstTime ) - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " connected\n\"", - client->pers.netname ) ); - - if( client->pers.admin ) - G_admin_authlog( ent ); - - // count current clients and rank for scoreboard - CalculateRanks( ); - - - // if this is after !restart keepteams or !restart switchteams, apply said selection - if ( client->sess.restartTeam != TEAM_NONE ) - { - G_ChangeTeam( ent, client->sess.restartTeam ); - client->sess.restartTeam = TEAM_NONE; - } - - - return NULL; -} - -/* -=========== -ClientBegin - -called when a client has finished connecting, and is ready -to be placed into the level. This will happen every level load, -and on transition between teams, but doesn't happen on respawns -============ -*/ -void ClientBegin( int clientNum ) -{ - gentity_t *ent; - gclient_t *client; - int flags; - - ent = g_entities + clientNum; - - client = level.clients + clientNum; - - // ignore if client already entered the game - if( client->pers.connected != CON_CONNECTING ) - return; - - if( ent->r.linked ) - trap_UnlinkEntity( ent ); - - G_InitGentity( ent ); - ent->touch = 0; - ent->pain = 0; - ent->client = client; - - client->pers.connected = CON_CONNECTED; - client->pers.enterTime = level.time; - - // save eflags around this, because changing teams will - // cause this to happen with a valid entity, and we - // want to make sure the teleport bit is set right - // so the viewpoint doesn't interpolate through the - // world to the new position - flags = client->ps.eFlags; - memset( &client->ps, 0, sizeof( client->ps ) ); - memset( &client->pmext, 0, sizeof( client->pmext ) ); - client->ps.eFlags = flags; - - // locate ent at a spawn point - ClientSpawn( ent, NULL, NULL, NULL ); - - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE " entered the game\n\"", client->pers.netname ) ); - - G_namelog_restore( client ); - - G_LogPrintf( "ClientBegin: %i\n", clientNum ); - - // count current clients and rank for scoreboard - CalculateRanks( ); - - // send the client a list of commands that can be used - G_ListCommands( ent ); - - // reset cuboidSelection - client->cuboidSelection[ 0 ] = - client->cuboidSelection[ 1 ] = - client->cuboidSelection[ 2 ] = 32; -} - -/* -=========== -ClientSpawn - -Called every time a client is placed fresh in the world: -after the first ClientBegin, and after each respawn -Initializes all non-persistant parts of playerState -============ -*/ -void ClientSpawn( gentity_t *ent, gentity_t *spawn, vec3_t origin, vec3_t angles ) -{ - int index; - vec3_t spawn_origin, spawn_angles; - gclient_t *client; - int i; - clientPersistant_t saved; - clientSession_t savedSess; - int persistant[ MAX_PERSISTANT ]; - gentity_t *spawnPoint = NULL; - int flags; - int savedPing; - int teamLocal; - int eventSequence; - char userinfo[ MAX_INFO_STRING ]; - vec3_t up = { 0.0f, 0.0f, 1.0f }; - int maxAmmo, maxClips; - weapon_t weapon; - - index = ent - g_entities; - client = ent->client; - - teamLocal = client->pers.teamSelection; - - //if client is dead and following teammate, stop following before spawning - if( client->sess.spectatorClient != -1 ) - { - client->sess.spectatorClient = -1; - client->sess.spectatorState = SPECTATOR_FREE; - } - - // only start client if chosen a class and joined a team - if( client->pers.classSelection == PCL_NONE && teamLocal == TEAM_NONE ) - client->sess.spectatorState = SPECTATOR_FREE; - else if( client->pers.classSelection == PCL_NONE ) - client->sess.spectatorState = SPECTATOR_LOCKED; - - // if client is dead and following teammate, stop following before spawning - if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) - G_StopFollowing( ent ); - - if( origin != NULL ) - VectorCopy( origin, spawn_origin ); - - if( angles != NULL ) - VectorCopy( angles, spawn_angles ); - - // find a spawn point - // do it before setting health back up, so farthest - // ranging doesn't count this client - if( client->sess.spectatorState != SPECTATOR_NOT ) - { - if( teamLocal == TEAM_NONE ) - spawnPoint = G_SelectSpectatorSpawnPoint( spawn_origin, spawn_angles ); - else if( teamLocal == TEAM_ALIENS ) - spawnPoint = G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); - else if( teamLocal == TEAM_HUMANS ) - spawnPoint = G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); - } - else - { - if( spawn == NULL ) - { - G_Error( "ClientSpawn: spawn is NULL\n" ); - return; - } - - spawnPoint = spawn; - - if( ent != spawn ) - { - //start spawn animation on spawnPoint - G_SetBuildableAnim( spawnPoint, BANIM_SPAWN1, qtrue ); - - if( spawnPoint->buildableTeam == TEAM_ALIENS ) - spawnPoint->clientSpawnTime = ALIEN_SPAWN_REPEAT_TIME; - else if( spawnPoint->buildableTeam == TEAM_HUMANS ) - spawnPoint->clientSpawnTime = HUMAN_SPAWN_REPEAT_TIME; - } - } - - // toggle the teleport bit so the client knows to not lerp - flags = ( ent->client->ps.eFlags & EF_TELEPORT_BIT ) ^ EF_TELEPORT_BIT; - G_UnlaggedClear( ent ); - - // clear everything but the persistant data - - saved = client->pers; - savedSess = client->sess; - savedPing = client->ps.ping; - - for( i = 0; i < MAX_PERSISTANT; i++ ) - persistant[ i ] = client->ps.persistant[ i ]; - - eventSequence = client->ps.eventSequence; - memset( client, 0, sizeof( *client ) ); - - client->pers = saved; - client->sess = savedSess; - client->ps.ping = savedPing; - client->lastkilled_client = -1; - - for( i = 0; i < MAX_PERSISTANT; i++ ) - client->ps.persistant[ i ] = persistant[ i ]; - - client->ps.eventSequence = eventSequence; - - // increment the spawncount so the client will detect the respawn - client->ps.persistant[ PERS_SPAWN_COUNT ]++; - client->ps.persistant[ PERS_SPECSTATE ] = client->sess.spectatorState; - - client->airOutTime = level.time + 12000; - - trap_GetUserinfo( index, userinfo, sizeof( userinfo ) ); - client->ps.eFlags = flags; - - //Com_Printf( "ent->client->pers->pclass = %i\n", ent->client->pers.classSelection ); - - ent->s.groundEntityNum = ENTITYNUM_NONE; - ent->client = &level.clients[ index ]; - ent->takedamage = qtrue; - ent->inuse = qtrue; - ent->classname = "player"; - ent->r.contents = CONTENTS_BODY; - ent->clipmask = MASK_PLAYERSOLID; - ent->die = player_die; - ent->waterlevel = 0; - ent->watertype = 0; - ent->flags = 0; - - // calculate each client's acceleration - ent->evaluateAcceleration = qtrue; - - client->ps.stats[ STAT_MISC ] = 0; - client->buildTimer = 0; - - client->ps.eFlags = flags; - client->ps.clientNum = index; - - BG_ClassBoundingBox( ent->client->pers.classSelection, ent->r.mins, ent->r.maxs, NULL, NULL, NULL ); - - if( client->sess.spectatorState == SPECTATOR_NOT ) - client->ps.stats[ STAT_MAX_HEALTH ] = - BG_Class( ent->client->pers.classSelection )->health; - else - client->ps.stats[ STAT_MAX_HEALTH ] = 100; - - // clear entity values - if( ent->client->pers.classSelection == PCL_HUMAN ) - { - BG_AddUpgradeToInventory( UP_MEDKIT, client->ps.stats ); - weapon = client->pers.humanItemSelection; - } - else if( client->sess.spectatorState == SPECTATOR_NOT ) - weapon = BG_Class( ent->client->pers.classSelection )->startWeapon; - else - weapon = WP_NONE; - - maxAmmo = BG_Weapon( weapon )->maxAmmo; - maxClips = BG_Weapon( weapon )->maxClips; - client->ps.stats[ STAT_WEAPON ] = weapon; - client->ps.ammo = maxAmmo; - client->ps.clips = maxClips; - - // We just spawned, not changing weapons - client->ps.persistant[ PERS_NEWWEAPON ] = 0; - - ent->client->ps.stats[ STAT_CLASS ] = ent->client->pers.classSelection; - ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; - - ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; - ent->client->ps.stats[ STAT_STATE ] = 0; - VectorSet( ent->client->ps.grapplePoint, 0.0f, 0.0f, 1.0f ); - - // health will count down towards max_health - ent->health = client->ps.stats[ STAT_HEALTH ] = client->ps.stats[ STAT_MAX_HEALTH ]; //* 1.25; - - //if evolving scale health - if( ent == spawn ) - { - ent->health *= ent->client->pers.evolveHealthFraction; - client->ps.stats[ STAT_HEALTH ] *= ent->client->pers.evolveHealthFraction; - } - - //clear the credits array - for( i = 0; i < MAX_CLIENTS; i++ ) - ent->credits[ i ] = 0; - - client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; - - G_SetOrigin( ent, spawn_origin ); - VectorCopy( spawn_origin, client->ps.origin ); - -#define UP_VEL 150.0f -#define F_VEL 50.0f - - //give aliens some spawn velocity - if( client->sess.spectatorState == SPECTATOR_NOT && - client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - { - if( ent == spawn ) - { - //evolution particle system - G_AddPredictableEvent( ent, EV_ALIEN_EVOLVE, DirToByte( up ) ); - } - else - { - spawn_angles[ YAW ] += 180.0f; - AngleNormalize360( spawn_angles[ YAW ] ); - - if( spawnPoint->s.origin2[ 2 ] > 0.0f ) - { - vec3_t forward, dir; - - AngleVectors( spawn_angles, forward, NULL, NULL ); - VectorScale( forward, F_VEL, forward ); - VectorAdd( spawnPoint->s.origin2, forward, dir ); - VectorNormalize( dir ); - - VectorScale( dir, UP_VEL, client->ps.velocity ); - } - - G_AddPredictableEvent( ent, EV_PLAYER_RESPAWN, 0 ); - } - } - else if( client->sess.spectatorState == SPECTATOR_NOT && - client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - { - spawn_angles[ YAW ] += 180.0f; - AngleNormalize360( spawn_angles[ YAW ] ); - } - - // the respawned flag will be cleared after the attack and jump keys come up - client->ps.pm_flags |= PMF_RESPAWNED; - - trap_GetUsercmd( client - level.clients, &ent->client->pers.cmd ); - G_SetClientViewAngle( ent, spawn_angles ); - - if( client->sess.spectatorState == SPECTATOR_NOT ) - { - trap_LinkEntity( ent ); - - // force the base weapon up - if( client->pers.teamSelection == TEAM_HUMANS ) - G_ForceWeaponChange( ent, weapon ); - - client->ps.weaponstate = WEAPON_READY; - } - - // don't allow full run speed for a bit - client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - client->ps.pm_time = 100; - - client->respawnTime = level.time; - ent->nextRegenTime = level.time; - - client->inactivityTime = level.time + g_inactivity.integer * 1000; - client->latched_buttons = 0; - - // set default animations - client->ps.torsoAnim = TORSO_STAND; - client->ps.legsAnim = LEGS_IDLE; - - if( level.intermissiontime ) - MoveClientToIntermission( ent ); - else - { - // fire the targets of the spawn point - if( !spawn ) - G_UseTargets( spawnPoint, ent ); - - // select the highest weapon number available, after any - // spawn given items have fired - client->ps.weapon = 1; - - for( i = WP_NUM_WEAPONS - 1; i > 0 ; i-- ) - { - if( BG_InventoryContainsWeapon( i, client->ps.stats ) ) - { - client->ps.weapon = i; - break; - } - } - } - - client->lastRantBombTime = level.time; - - // run a client frame to drop exactly to the floor, - // initialize animations and other things - client->ps.commandTime = level.time - 100; - ent->client->pers.cmd.serverTime = level.time; - ClientThink( ent-g_entities ); - - // positively link the client, even if the command times are weird - if( client->sess.spectatorState == SPECTATOR_NOT ) - { - BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); - VectorCopy( ent->client->ps.origin, ent->r.currentOrigin ); - trap_LinkEntity( ent ); - } - - // must do this here so the number of active clients is calculated - CalculateRanks( ); - - // run the presend to set anything else - ClientEndFrame( ent ); - - // clear entity state values - BG_PlayerStateToEntityState( &client->ps, &ent->s, qtrue ); -} - - -/* -=========== -ClientDisconnect - -Called when a player drops from the server. -Will not be called between levels. - -This should NOT be called directly by any game logic, -call trap_DropClient(), which will call this and do -server system housekeeping. -============ -*/ -void ClientDisconnect( int clientNum ) -{ - gentity_t *ent; - gentity_t *tent; - int i; - - ent = g_entities + clientNum; - - if( !ent->client || ent->client->pers.connected == CON_DISCONNECTED ) - return; - - G_LeaveTeam( ent ); - G_namelog_disconnect( ent->client ); - G_Vote( ent, TEAM_NONE, qfalse ); - - // stop any following clients - for( i = 0; i < level.maxclients; i++ ) - { - // remove any /ignore settings for this clientNum - Com_ClientListRemove( &level.clients[ i ].sess.ignoreList, clientNum ); - } - - // send effect if they were completely connected - if( ent->client->pers.connected == CON_CONNECTED && - ent->client->sess.spectatorState == SPECTATOR_NOT ) - { - tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_OUT ); - tent->s.clientNum = ent->s.clientNum; - } - - G_LogPrintf( "ClientDisconnect: %i [%s] (%s) \"%s^7\"\n", clientNum, - ent->client->pers.ip.str, ent->client->pers.guid, ent->client->pers.netname ); - - trap_UnlinkEntity( ent ); - ent->s.modelindex = 0; - ent->inuse = qfalse; - ent->classname = "disconnected"; - ent->client->pers.connected = CON_DISCONNECTED; - ent->client->sess.spectatorState = - ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_NOT; - - trap_SetConfigstring( CS_PLAYERS + clientNum, ""); - - CalculateRanks( ); -} - -/* -=========== -G_RelayCuboidToSpectators - -Called everytime a player changes his cuboid size. -A server command is issued to everyone spectating him -so that their clients can know the cuboid size as well. -============ -*/ -void G_RelayCuboidToSpectators(gentity_t *self) -{ -} - diff --git a/src/game/g_cmds.c.orig b/src/game/g_cmds.c.orig deleted file mode 100644 index 5a30f7b..0000000 --- a/src/game/g_cmds.c.orig +++ /dev/null @@ -1,3588 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2009 Darklegion Development - -This file is part of Tremulous. - -Tremulous is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Tremulous is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "g_local.h" - -/* -================== -G_SanitiseString - -Remove color codes and non-alphanumeric characters from a string -================== -*/ -void G_SanitiseString( char *in, char *out, int len ) -{ - len--; - - while( *in && len > 0 ) - { - if( Q_IsColorString( in ) ) - { - in += 2; // skip color code - continue; - } - - if( isalnum( *in ) ) - { - *out++ = tolower( *in ); - len--; - } - in++; - } - *out = 0; -} - -/* -================== -G_ClientNumberFromString - -Returns a player number for either a number or name string -Returns -1 and optionally sets err if invalid or not exactly 1 match -err will have a trailing \n if set -================== -*/ -int G_ClientNumberFromString( char *s, char *err, int len ) -{ - gclient_t *cl; - int i, found = 0, m = -1; - char s2[ MAX_NAME_LENGTH ]; - char n2[ MAX_NAME_LENGTH ]; - char *p = err; - int l, l2 = len; - - if( !s[ 0 ] ) - { - if( p ) - Q_strncpyz( p, "no player name or slot # provided\n", len ); - - return -1; - } - - // numeric values are just slot numbers - for( i = 0; s[ i ] && isdigit( s[ i ] ); i++ ); - if( !s[ i ] ) - { - i = atoi( s ); - - if( i < 0 || i >= level.maxclients ) - return -1; - - cl = &level.clients[ i ]; - - if( cl->pers.connected == CON_DISCONNECTED ) - { - if( p ) - Q_strncpyz( p, "no player connected in that slot #\n", len ); - - return -1; - } - - return i; - } - - G_SanitiseString( s, s2, sizeof( s2 ) ); - if( !s2[ 0 ] ) - { - if( p ) - Q_strncpyz( p, "no player name provided\n", len ); - - return -1; - } - - if( p ) - { - Q_strncpyz( p, "more than one player name matches. " - "be more specific or use the slot #:\n", l2 ); - l = strlen( p ); - p += l; - l2 -= l; - } - - // check for a name match - for( i = 0, cl = level.clients; i < level.maxclients; i++, cl++ ) - { - if( cl->pers.connected == CON_DISCONNECTED ) - continue; - - G_SanitiseString( cl->pers.netname, n2, sizeof( n2 ) ); - - if( !strcmp( n2, s2 ) ) - return i; - - if( strstr( n2, s2 ) ) - { - if( p ) - { - l = Q_snprintf( p, l2, "%-2d - %s^7\n", i, cl->pers.netname ); - p += l; - l2 -= l; - } - - found++; - m = i; - } - } - - if( found == 1 ) - return m; - - if( found == 0 && err ) - Q_strncpyz( err, "no connected player by that name or slot #\n", len ); - - return -1; -} - -/* -================== -G_ClientNumbersFromString - -Sets plist to an array of integers that represent client numbers that have -names that are a partial match for s. - -Returns number of matching clientids up to max. -================== -*/ -int G_ClientNumbersFromString( char *s, int *plist, int max ) -{ - gclient_t *p; - int i, found = 0; - char *endptr; - char n2[ MAX_NAME_LENGTH ] = {""}; - char s2[ MAX_NAME_LENGTH ] = {""}; - - if( max == 0 ) - return 0; - - if( !s[ 0 ] ) - return 0; - - // if a number is provided, it is a clientnum - i = strtol( s, &endptr, 10 ); - if( *endptr == '\0' ) - { - if( i >= 0 && i < level.maxclients ) - { - p = &level.clients[ i ]; - if( p->pers.connected != CON_DISCONNECTED ) - { - *plist = i; - return 1; - } - } - // we must assume that if only a number is provided, it is a clientNum - return 0; - } - - // now look for name matches - G_SanitiseString( s, s2, sizeof( s2 ) ); - if( !s2[ 0 ] ) - return 0; - for( i = 0; i < level.maxclients && found < max; i++ ) - { - p = &level.clients[ i ]; - if( p->pers.connected == CON_DISCONNECTED ) - { - continue; - } - G_SanitiseString( p->pers.netname, n2, sizeof( n2 ) ); - if( strstr( n2, s2 ) ) - { - *plist++ = i; - found++; - } - } - return found; -} - -/* -================== -ScoreboardMessage - -================== -*/ -void ScoreboardMessage( gentity_t *ent ) -{ - char entry[ 1024 ]; - char string[ 1400 ]; - int stringlength; - int i, j; - gclient_t *cl; - int numSorted; - weapon_t weapon = WP_NONE; - upgrade_t upgrade = UP_NONE; - - // send the latest information on all clients - string[ 0 ] = 0; - stringlength = 0; - - numSorted = level.numConnectedClients; - - for( i = 0; i < numSorted; i++ ) - { - int ping; - - cl = &level.clients[ level.sortedClients[ i ] ]; - - if( cl->pers.connected == CON_CONNECTING ) - ping = -1; - else - ping = cl->ps.ping < 999 ? cl->ps.ping : 999; - - if( cl->sess.spectatorState == SPECTATOR_NOT && - ( ent->client->pers.teamSelection == TEAM_NONE || - cl->pers.teamSelection == ent->client->pers.teamSelection ) ) - { - weapon = cl->ps.weapon; - - if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) ) - upgrade = UP_BATTLESUIT; - else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) ) - upgrade = UP_JETPACK; - else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) ) - upgrade = UP_BATTPACK; - else if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, cl->ps.stats ) ) - upgrade = UP_HELMET_MK1; - else if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, cl->ps.stats ) ) - upgrade = UP_HELMET_MK2; - else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) ) - upgrade = UP_LIGHTARMOUR; - else - upgrade = UP_NONE; - } - else - { - weapon = WP_NONE; - upgrade = UP_NONE; - } - - Com_sprintf( entry, sizeof( entry ), - " %d %d %d %d %d %d", level.sortedClients[ i ], cl->ps.persistant[ PERS_SCORE ], - ping, ( level.time - cl->pers.enterTime ) / 60000, weapon, upgrade ); - - j = strlen( entry ); - - if( stringlength + j >= 1024 ) - break; - - strcpy( string + stringlength, entry ); - stringlength += j; - } - - trap_SendServerCommand( ent-g_entities, va( "scores %i %i%s", - level.alienKills, level.humanKills, string ) ); -} - - -/* -================== -ConcatArgs -================== -*/ -char *ConcatArgs( int start ) -{ - int i, c, tlen; - static char line[ MAX_STRING_CHARS ]; - int len; - char arg[ MAX_STRING_CHARS ]; - - len = 0; - c = trap_Argc( ); - - for( i = start; i < c; i++ ) - { - trap_Argv( i, arg, sizeof( arg ) ); - tlen = strlen( arg ); - - if( len + tlen >= MAX_STRING_CHARS - 1 ) - break; - - memcpy( line + len, arg, tlen ); - len += tlen; - - if( len == MAX_STRING_CHARS - 1 ) - break; - - if( i != c - 1 ) - { - line[ len ] = ' '; - len++; - } - } - - line[ len ] = 0; - - return line; -} - -/* -================== -ConcatArgsPrintable -Duplicate of concatargs but enquotes things that need to be -Used to log command arguments in a way that preserves user intended tokenizing -================== -*/ -char *ConcatArgsPrintable( int start ) -{ - int i, c, tlen; - static char line[ MAX_STRING_CHARS ]; - int len; - char arg[ MAX_STRING_CHARS + 2 ]; - char *printArg; - - len = 0; - c = trap_Argc( ); - - for( i = start; i < c; i++ ) - { - printArg = arg; - trap_Argv( i, arg, sizeof( arg ) ); - if( strchr( arg, ' ' ) ) - printArg = va( "\"%s\"", arg ); - tlen = strlen( printArg ); - - if( len + tlen >= MAX_STRING_CHARS - 1 ) - break; - - memcpy( line + len, printArg, tlen ); - len += tlen; - - if( len == MAX_STRING_CHARS - 1 ) - break; - - if( i != c - 1 ) - { - line[ len ] = ' '; - len++; - } - } - - line[ len ] = 0; - - return line; -} - - -/* -================== -Cmd_Give_f - -Give items to a client -================== -*/ -void Cmd_Give_f( gentity_t *ent ) -{ - char *name; - qboolean give_all = qfalse; - - if( trap_Argc( ) < 2 ) - { - ADMP( "usage: give [what]\n" ); - ADMP( "usage: valid choices are: all, health, funds [amount], stamina, " - "poison, gas, ammo\n" ); - return; - } - - name = ConcatArgs( 1 ); - if( Q_stricmp( name, "all" ) == 0 ) - give_all = qtrue; - - if( give_all || Q_stricmp( name, "health" ) == 0 ) - { - ent->health = ent->client->ps.stats[ STAT_MAX_HEALTH ]; - BG_AddUpgradeToInventory( UP_MEDKIT, ent->client->ps.stats ); - } - - if( give_all || Q_stricmpn( name, "funds", 5 ) == 0 ) - { - float credits; - - if( give_all || trap_Argc( ) < 3 ) - credits = 30000.0f; - else - { - credits = atof( name + 6 ) * - ( ent->client->pers.teamSelection == - TEAM_ALIENS ? ALIEN_CREDITS_PER_KILL : 1.0f ); - - // clamp credits manually, as G_AddCreditToClient() expects a short int - if( credits > SHRT_MAX ) - credits = 30000.0f; - else if( credits < SHRT_MIN ) - credits = -30000.0f; - } - - G_AddCreditToClient( ent->client, (short)credits, qtrue ); - } - - if( give_all || Q_stricmp( name, "stamina" ) == 0 ) - ent->client->ps.stats[ STAT_STAMINA ] = STAMINA_MAX; - - if( Q_stricmp( name, "poison" ) == 0 ) - { - if( ent->client->pers.teamSelection == TEAM_HUMANS ) - { - ent->client->ps.stats[ STAT_STATE ] |= SS_POISONED; - ent->client->lastPoisonTime = level.time; - ent->client->lastPoisonClient = ent; - } - else - { - ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; - ent->client->boostedTime = level.time; - } - } - - if( Q_stricmp( name, "gas" ) == 0 ) - { - ent->client->ps.eFlags |= EF_POISONCLOUDED; - ent->client->lastPoisonCloudedTime = level.time; - trap_SendServerCommand( ent->client->ps.clientNum, - "poisoncloud" ); - } - - if( give_all || Q_stricmp( name, "ammo" ) == 0 ) - { - gclient_t *client = ent->client; - - if( client->ps.weapon != WP_ALEVEL3_UPG && - BG_Weapon( client->ps.weapon )->infiniteAmmo ) - return; - - client->ps.ammo = BG_Weapon( client->ps.weapon )->maxAmmo; - client->ps.clips = BG_Weapon( client->ps.weapon )->maxClips; - - if( BG_Weapon( client->ps.weapon )->usesEnergy && - BG_InventoryContainsUpgrade( UP_BATTPACK, client->ps.stats ) ) - client->ps.ammo = (int)( (float)client->ps.ammo * BATTPACK_MODIFIER ); - } - - if( give_all || Q_stricmp( name, "fuel" ) == 0 ) - { - if( BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) ) - ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; - } -} - - -/* -================== -Cmd_God_f - -Sets client to godmode - -argv(0) god -================== -*/ -void Cmd_God_f( gentity_t *ent ) -{ - char *msg; - - ent->flags ^= FL_GODMODE; - - if( !( ent->flags & FL_GODMODE ) ) - msg = "godmode OFF\n"; - else - msg = "godmode ON\n"; - - trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); -} - - -/* -================== -Cmd_Notarget_f - -Sets client to notarget - -argv(0) notarget -================== -*/ -void Cmd_Notarget_f( gentity_t *ent ) -{ - char *msg; - - ent->flags ^= FL_NOTARGET; - - if( !( ent->flags & FL_NOTARGET ) ) - msg = "notarget OFF\n"; - else - msg = "notarget ON\n"; - - trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); -} - - -/* -================== -Cmd_Noclip_f - -argv(0) noclip -================== -*/ -void Cmd_Noclip_f( gentity_t *ent ) -{ - char *msg; - - if( ent->client->noclip ) - msg = "noclip OFF\n"; - else - msg = "noclip ON\n"; - - ent->client->noclip = !ent->client->noclip; - - trap_SendServerCommand( ent - g_entities, va( "print \"%s\"", msg ) ); -} - - -/* -================== -Cmd_LevelShot_f - -This is just to help generate the level pictures -for the menus. It goes to the intermission immediately -and sends over a command to the client to resize the view, -hide the scoreboard, and take a special screenshot -================== -*/ -void Cmd_LevelShot_f( gentity_t *ent ) -{ - BeginIntermission( ); - trap_SendServerCommand( ent - g_entities, "clientLevelShot" ); -} - -/* -================= -Cmd_Kill_f -================= -*/ -void Cmd_Kill_f( gentity_t *ent ) -{ - if( g_cheats.integer ) - { - ent->flags &= ~FL_GODMODE; - ent->client->ps.stats[ STAT_HEALTH ] = ent->health = 0; - player_die( ent, ent, ent, 100000, MOD_SUICIDE ); - } - else - { - if( ent->suicideTime == 0 ) - { - trap_SendServerCommand( ent-g_entities, "print \"You will suicide in 20 seconds\n\"" ); - ent->suicideTime = level.time + 20000; - } - else if( ent->suicideTime > level.time ) - { - trap_SendServerCommand( ent-g_entities, "print \"Suicide cancelled\n\"" ); - ent->suicideTime = 0; - } - } -} - -/* -================= -Cmd_Team_f -================= -*/ -void Cmd_Team_f( gentity_t *ent ) -{ - team_t team; - team_t oldteam = ent->client->pers.teamSelection; - char s[ MAX_TOKEN_CHARS ]; - qboolean force = G_admin_permission( ent, ADMF_FORCETEAMCHANGE ); - int aliens = level.numAlienClients; - int humans = level.numHumanClients; - - if( oldteam == TEAM_ALIENS ) - aliens--; - else if( oldteam == TEAM_HUMANS ) - humans--; - - // stop team join spam - if( ent->client->pers.teamChangeTime && - level.time - ent->client->pers.teamChangeTime < 1000 ) - return; - - // stop switching teams for gameplay exploit reasons by enforcing a long - // wait before they can come back - if( !force && !g_cheats.integer && ent->client->pers.aliveSeconds && - level.time - ent->client->pers.teamChangeTime < 30000 ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"You must wait another %d seconds before changing teams again\n\"", - (int) ( ( 30000 - ( level.time - ent->client->pers.teamChangeTime ) ) / 1000.f ) ) ); - return; - } - - // disallow joining teams during warmup - if( g_doWarmup.integer && ( ( level.warmupTime - level.time ) / 1000 ) > 0 ) - { - G_TriggerMenu( ent - g_entities, MN_WARMUP ); - return; - } - - trap_Argv( 1, s, sizeof( s ) ); - - if( !s[ 0 ] ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"team: %s\n\"", - BG_TeamName( oldteam ) ) ); - return; - } - - if( !Q_stricmp( s, "auto" ) ) - { - if( level.humanTeamLocked && level.alienTeamLocked ) - team = TEAM_NONE; - else if( level.humanTeamLocked || humans > aliens ) - team = TEAM_ALIENS; - - else if( level.alienTeamLocked || aliens > humans ) - team = TEAM_HUMANS; - else - team = TEAM_ALIENS + rand( ) / ( RAND_MAX / 2 + 1 ); - } - else switch( G_TeamFromString( s ) ) - { - case TEAM_NONE: - team = TEAM_NONE; - break; - - case TEAM_ALIENS: - if( level.alienTeamLocked ) - { - G_TriggerMenu( ent - g_entities, MN_A_TEAMLOCKED ); - return; - } - else if( level.humanTeamLocked ) - force = qtrue; - - if( !force && g_teamForceBalance.integer && aliens > humans ) - { - G_TriggerMenu( ent - g_entities, MN_A_TEAMFULL ); - return; - } - - team = TEAM_ALIENS; - break; - - case TEAM_HUMANS: - if( level.humanTeamLocked ) - { - G_TriggerMenu( ent - g_entities, MN_H_TEAMLOCKED ); - return; - } - else if( level.alienTeamLocked ) - force = qtrue; - - if( !force && g_teamForceBalance.integer && humans > aliens ) - { - G_TriggerMenu( ent - g_entities, MN_H_TEAMFULL ); - return; - } - - team = TEAM_HUMANS; - break; - - default: - trap_SendServerCommand( ent-g_entities, - va( "print \"Unknown team: %s\n\"", s ) ); - return; - } - - // stop team join spam - if( oldteam == team ) - return; - - if( team != TEAM_NONE && g_maxGameClients.integer && - level.numPlayingClients >= g_maxGameClients.integer ) - { - G_TriggerMenu( ent - g_entities, MN_PLAYERLIMIT ); - return; - } - - // Apply the change - G_ChangeTeam( ent, team ); -} - -/* -================== -G_CensorString -================== -*/ -static char censors[ 20000 ]; -static int numcensors; - -void G_LoadCensors( void ) -{ - char *text_p, *token; - char text[ 20000 ]; - char *term; - int len; - fileHandle_t f; - - numcensors = 0; - - if( !g_censorship.string[ 0 ] ) - return; - - len = trap_FS_FOpenFile( g_censorship.string, &f, FS_READ ); - if( len < 0 ) - { - Com_Printf( S_COLOR_RED "ERROR: Censors file %s doesn't exist\n", - g_censorship.string ); - return; - } - if( len == 0 || len >= sizeof( text ) - 1 ) - { - trap_FS_FCloseFile( f ); - Com_Printf( S_COLOR_RED "ERROR: Censors file %s is %s\n", - g_censorship.string, len == 0 ? "empty" : "too long" ); - return; - } - trap_FS_Read( text, len, f ); - trap_FS_FCloseFile( f ); - text[ len ] = 0; - - term = censors; - - text_p = text; - while( 1 ) - { - token = COM_Parse( &text_p ); - if( !*token || sizeof( censors ) - ( term - censors ) < 4 ) - break; - Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); - Q_strlwr( term ); - term += strlen( term ) + 1; - if( sizeof( censors ) - ( term - censors ) == 0 ) - break; - token = COM_ParseExt( &text_p, qfalse ); - Q_strncpyz( term, token, sizeof( censors ) - ( term - censors ) ); - term += strlen( term ) + 1; - numcensors++; - } - G_Printf( "Parsed %d string replacements\n", numcensors ); -} - -void G_CensorString( char *out, const char *in, int len, gentity_t *ent ) -{ - const char *s, *m; - int i; - - if( !numcensors || G_admin_permission( ent, ADMF_NOCENSORFLOOD) ) - { - Q_strncpyz( out, in, len ); - return; - } - - len--; - while( *in ) - { - if( Q_IsColorString( in ) ) - { - if( len < 2 ) - break; - *out++ = *in++; - *out++ = *in++; - len -= 2; - continue; - } - if( !isalnum( *in ) ) - { - if( len < 1 ) - break; - *out++ = *in++; - len--; - continue; - } - m = censors; - for( i = 0; i < numcensors; i++, m++ ) - { - s = in; - while( *s && *m ) - { - if( Q_IsColorString( s ) ) - { - s += 2; - continue; - } - if( !isalnum( *s ) ) - { - s++; - continue; - } - if( tolower( *s ) != *m ) - break; - s++; - m++; - } - // match - if( !*m ) - { - in = s; - m++; - while( *m ) - { - if( len < 1 ) - break; - *out++ = *m++; - len--; - } - break; - } - else - { - while( *m ) - m++; - m++; - while( *m ) - m++; - } - } - if( len < 1 ) - break; - // no match - if( i == numcensors ) - { - *out++ = *in++; - len--; - } - } - *out = 0; -} - -/* -================== -G_Say -================== -*/ -static qboolean G_SayTo( gentity_t *ent, gentity_t *other, saymode_t mode, const char *message ) -{ - if( !other ) - return qfalse; - - if( !other->inuse ) - return qfalse; - - if( !other->client ) - return qfalse; - - if( other->client->pers.connected != CON_CONNECTED ) - return qfalse; - - if( ( ent && !OnSameTeam( ent, other ) ) && - ( mode == SAY_TEAM || mode == SAY_AREA || mode == SAY_TPRIVMSG ) ) - { - if( other->client->pers.teamSelection != TEAM_NONE ) - return qfalse; - - // specs with ADMF_SPEC_ALLCHAT flag can see team chat - if( !G_admin_permission( other, ADMF_SPEC_ALLCHAT ) && mode != SAY_TPRIVMSG ) - return qfalse; - } - - trap_SendServerCommand( other-g_entities, va( "chat %d %d \"%s\"", - ent ? ent-g_entities : -1, - mode, - message ) ); - - return qtrue; -} - -void G_Say( gentity_t *ent, saymode_t mode, const char *chatText ) -{ - int j; - gentity_t *other; - // don't let text be too long for malicious reasons - char text[ MAX_SAY_TEXT ]; - - // check if blocked by g_specChat 0 - if( ( !g_specChat.integer ) && ( mode != SAY_TEAM ) && - ( ent ) && ( ent->client->pers.teamSelection == TEAM_NONE ) && - ( !G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"say: Global chatting for " - "spectators has been disabled. You may only use team chat.\n\"" ); - mode = SAY_TEAM; - } - - switch( mode ) - { - case SAY_ALL: - G_LogPrintf( "Say: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_GREEN "%s\n", - ( ent ) ? ent - g_entities : -1, - ( ent ) ? ent->client->pers.netname : "console", chatText ); - break; - case SAY_TEAM: - // console say_team is handled in g_svscmds, not here - if( !ent || !ent->client ) - Com_Error( ERR_FATAL, "SAY_TEAM by non-client entity\n" ); - G_LogPrintf( "SayTeam: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_CYAN "%s\n", - ent - g_entities, ent->client->pers.netname, chatText ); - break; - case SAY_RAW: - if( ent ) - Com_Error( ERR_FATAL, "SAY_RAW by client entity\n" ); - G_LogPrintf( "Chat: -1 \"console\": %s\n", chatText ); - default: - break; - } - - G_CensorString( text, chatText, sizeof( text ), ent ); - - // send it to all the apropriate clients - for( j = 0; j < level.maxclients; j++ ) - { - other = &g_entities[ j ]; - G_SayTo( ent, other, mode, text ); - } -} - -/* -================== -Cmd_SayArea_f -================== -*/ -static void Cmd_SayArea_f( gentity_t *ent ) -{ - int entityList[ MAX_GENTITIES ]; - int num, i; - vec3_t range = { 1000.0f, 1000.0f, 1000.0f }; - vec3_t mins, maxs; - char *msg; - - if( trap_Argc( ) < 2 ) - { - ADMP( "usage: say_area [message]\n" ); - return; - } - - msg = ConcatArgs( 1 ); - - for(i = 0; i < 3; i++ ) - range[ i ] = g_sayAreaRange.value; - - G_LogPrintf( "SayArea: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_BLUE "%s\n", - ent - g_entities, ent->client->pers.netname, msg ); - - VectorAdd( ent->s.origin, range, maxs ); - VectorSubtract( ent->s.origin, range, mins ); - - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - G_SayTo( ent, &g_entities[ entityList[ i ] ], SAY_AREA, msg ); - - //Send to ADMF_SPEC_ALLCHAT candidates - for( i = 0; i < level.maxclients; i++ ) - { - if( g_entities[ i ].client->pers.teamSelection == TEAM_NONE && - G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) - { - G_SayTo( ent, &g_entities[ i ], SAY_AREA, msg ); - } - } -} - - -/* -================== -Cmd_Say_f -================== -*/ -static void Cmd_Say_f( gentity_t *ent ) -{ - char *p; - char cmd[ MAX_TOKEN_CHARS ]; - saymode_t mode = SAY_ALL; - - if( trap_Argc( ) < 2 ) - return; - - trap_Argv( 0, cmd, sizeof( cmd ) ); - if( Q_stricmp( cmd, "say_team" ) == 0 ) - mode = SAY_TEAM; - - p = ConcatArgs( 1 ); - - G_Say( ent, mode, p ); -} - -/* -================== -Cmd_VSay_f -================== -*/ -void Cmd_VSay_f( gentity_t *ent ) -{ - char arg[MAX_TOKEN_CHARS]; - char text[ MAX_TOKEN_CHARS ]; - voiceChannel_t vchan; - voice_t *voice; - voiceCmd_t *cmd; - voiceTrack_t *track; - int cmdNum = 0; - int trackNum = 0; - char voiceName[ MAX_VOICE_NAME_LEN ] = {"default"}; - char voiceCmd[ MAX_VOICE_CMD_LEN ] = {""}; - char vsay[ 12 ] = {""}; - weapon_t weapon; - - if( !ent || !ent->client ) - Com_Error( ERR_FATAL, "Cmd_VSay_f() called by non-client entity\n" ); - - trap_Argv( 0, arg, sizeof( arg ) ); - if( trap_Argc( ) < 2 ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"usage: %s command [text] \n\"", arg ) ); - return; - } - if( !level.voices ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"%s: voice system is not installed on this server\n\"", arg ) ); - return; - } - if( !g_voiceChats.integer ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"%s: voice system administratively disabled on this server\n\"", - arg ) ); - return; - } - if( !Q_stricmp( arg, "vsay" ) ) - vchan = VOICE_CHAN_ALL; - else if( !Q_stricmp( arg, "vsay_team" ) ) - vchan = VOICE_CHAN_TEAM; - else if( !Q_stricmp( arg, "vsay_local" ) ) - vchan = VOICE_CHAN_LOCAL; - else - return; - Q_strncpyz( vsay, arg, sizeof( vsay ) ); - - if( ent->client->pers.voice[ 0 ] ) - Q_strncpyz( voiceName, ent->client->pers.voice, sizeof( voiceName ) ); - voice = BG_VoiceByName( level.voices, voiceName ); - if( !voice ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"%s: voice '%s' not found\n\"", vsay, voiceName ) ); - return; - } - - trap_Argv( 1, voiceCmd, sizeof( voiceCmd ) ) ; - cmd = BG_VoiceCmdFind( voice->cmds, voiceCmd, &cmdNum ); - if( !cmd ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"%s: command '%s' not found in voice '%s'\n\"", - vsay, voiceCmd, voiceName ) ); - return; - } - - // filter non-spec humans by their primary weapon as well - weapon = WP_NONE; - if( ent->client->sess.spectatorState == SPECTATOR_NOT ) - { - weapon = BG_PrimaryWeapon( ent->client->ps.stats ); - } - - track = BG_VoiceTrackFind( cmd->tracks, ent->client->pers.teamSelection, - ent->client->pers.classSelection, weapon, (int)ent->client->voiceEnthusiasm, - &trackNum ); - if( !track ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"%s: no available track for command '%s', team %d, " - "class %d, weapon %d, and enthusiasm %d in voice '%s'\n\"", - vsay, voiceCmd, ent->client->pers.teamSelection, - ent->client->pers.classSelection, weapon, - (int)ent->client->voiceEnthusiasm, voiceName ) ); - return; - } - - if( !Q_stricmp( ent->client->lastVoiceCmd, cmd->cmd ) ) - ent->client->voiceEnthusiasm++; - - Q_strncpyz( ent->client->lastVoiceCmd, cmd->cmd, - sizeof( ent->client->lastVoiceCmd ) ); - - // optional user supplied text - trap_Argv( 2, arg, sizeof( arg ) ); - G_CensorString( text, arg, sizeof( text ), ent ); - - switch( vchan ) - { - case VOICE_CHAN_ALL: - case VOICE_CHAN_LOCAL: - trap_SendServerCommand( -1, va( - "voice %d %d %d %d \"%s\"\n", - ent-g_entities, vchan, cmdNum, trackNum, text ) ); - break; - case VOICE_CHAN_TEAM: - G_TeamCommand( ent->client->pers.teamSelection, va( - "voice %d %d %d %d \"%s\"\n", - ent-g_entities, vchan, cmdNum, trackNum, text ) ); - break; - default: - break; - } -} - -/* -================== -Cmd_Where_f -================== -*/ -void Cmd_Where_f( gentity_t *ent ) -{ - if( !ent->client ) - return; - trap_SendServerCommand( ent - g_entities, - va( "print \"origin: %f %f %f\n\"", - ent->s.origin[ 0 ], ent->s.origin[ 1 ], - ent->s.origin[ 2 ] ) ); -} - -/* -================== -Cmd_CallVote_f -================== -*/ -void Cmd_CallVote_f( gentity_t *ent ) -{ - char cmd[ MAX_TOKEN_CHARS ], - vote[ MAX_TOKEN_CHARS ], - arg[ MAX_TOKEN_CHARS ]; - char name[ MAX_NAME_LENGTH ] = ""; - char caller[ MAX_NAME_LENGTH ] = ""; - char reason[ MAX_TOKEN_CHARS ]; - char *creason; - int clientNum = -1; - int id = -1; - team_t team; - - trap_Argv( 0, cmd, sizeof( cmd ) ); - trap_Argv( 1, vote, sizeof( vote ) ); - trap_Argv( 2, arg, sizeof( arg ) ); - creason = ConcatArgs( 3 ); - G_DecolorString( creason, reason, sizeof( reason ) ); - - if( !Q_stricmp( cmd, "callteamvote" ) ) - team = ent->client->pers.teamSelection; - else - team = TEAM_NONE; - - if( !g_allowVote.integer ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: voting not allowed here\n\"", cmd ) ); - return; - } - - if( level.voteTime[ team ] ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: a vote is already in progress\n\"", cmd ) ); - return; - } - - if( level.voteExecuteTime[ team ] ) - G_ExecuteVote( team ); - - level.voteDelay[ team ] = 0; - level.voteThreshold[ team ] = 50; - - if( g_voteLimit.integer > 0 && - ent->client->pers.namelog->voteCount >= g_voteLimit.integer && - !G_admin_permission( ent, ADMF_NO_VOTE_LIMIT ) ) - { - trap_SendServerCommand( ent-g_entities, va( - "print \"%s: you have already called the maximum number of votes (%d)\n\"", - cmd, g_voteLimit.integer ) ); - return; - } - - // kick, mute, unmute, denybuild, allowbuild - if( !Q_stricmp( vote, "kick" ) || - !Q_stricmp( vote, "mute" ) || !Q_stricmp( vote, "unmute" ) || - !Q_stricmp( vote, "denybuild" ) || !Q_stricmp( vote, "allowbuild" ) ) - { - char err[ MAX_STRING_CHARS ]; - - if( !arg[ 0 ] ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: no target\n\"", cmd ) ); - return; - } - - // with a little extra work only players from the right team are considered - clientNum = G_ClientNumberFromString( arg, err, sizeof( err ) ); - - if( clientNum == -1 ) - { - ADMP( va( "%s: %s", cmd, err ) ); - return; - } - - G_DecolorString( level.clients[ clientNum ].pers.netname, name, sizeof( name ) ); - id = level.clients[ clientNum ].pers.namelog->id; - - if( !Q_stricmp( vote, "kick" ) || !Q_stricmp( vote, "mute" ) || - !Q_stricmp( vote, "denybuild" ) ) - { - if( G_admin_permission( g_entities + clientNum, ADMF_IMMUNITY ) ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: admin is immune\n\"", cmd ) ); - - G_AdminMessage( NULL, va( S_COLOR_WHITE "%s" S_COLOR_YELLOW " attempted %s %s" - " on immune admin " S_COLOR_WHITE "%s" S_COLOR_YELLOW - " for: %s", - ent->client->pers.netname, cmd, vote, - g_entities[ clientNum ].client->pers.netname, - reason[ 0 ] ? reason : "no reason" ) ); - return; - } - - if( team != TEAM_NONE && - ( ent->client->pers.teamSelection != - level.clients[ clientNum ].pers.teamSelection ) ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: player is not on your team\n\"", cmd ) ); - return; - } - - if( !reason[ 0 ] && !G_admin_permission( ent, ADMF_UNACCOUNTABLE ) ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: You must provide a reason\n\"", cmd ) ); - return; - } - } - } - - if( !Q_stricmp( vote, "kick" ) ) - { - if( level.clients[ clientNum ].pers.localClient ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: admin is immune\n\"", cmd ) ); - return; - } - - Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), - "ban %s \"1s%s\" vote kick (%s)", level.clients[ clientNum ].pers.ip.str, - g_adminTempBan.string, reason ); - Com_sprintf( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), "Kick player '%s'", name ); - if( reason[ 0 ] ) - { - Q_strcat( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); - } - } - else if( team == TEAM_NONE ) - { - if( !Q_stricmp( vote, "mute" ) ) - { - if( level.clients[ clientNum ].pers.namelog->muted ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: player is already muted\n\"", cmd ) ); - return; - } - - Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), - "mute %d", id ); - Com_sprintf( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), - "Mute player '%s'", name ); - if( reason[ 0 ] ) - { - Q_strcat( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); - } - } - else if( !Q_stricmp( vote, "unmute" ) ) - { - if( !level.clients[ clientNum ].pers.namelog->muted ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: player is not currently muted\n\"", cmd ) ); - return; - } - - Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), - "unmute %d", id ); - Com_sprintf( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), - "Unmute player '%s'", name ); - } - else if( !Q_stricmp( vote, "map_restart" ) ) - { - strcpy( level.voteString[ team ], vote ); - strcpy( level.voteDisplayString[ team ], "Restart current map" ); - // map_restart comes with a default delay - } - else if( !Q_stricmp( vote, "map" ) ) - { - if( !G_MapExists( arg ) ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", - cmd, arg ) ); - return; - } - - Com_sprintf( level.voteString[ team ], sizeof( level.voteString ), - "%s \"%s\"", vote, arg ); - Com_sprintf( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), - "Change to map '%s'", arg ); - level.voteDelay[ team ] = 3000; - } - else if( !Q_stricmp( vote, "nextmap" ) ) - { - if( G_MapExists( g_nextMap.string ) ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: the next map is already set to '%s'\n\"", - cmd, g_nextMap.string ) ); - return; - } - - if( !G_MapExists( arg ) ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: 'maps/%s.bsp' could not be found on the server\n\"", - cmd, arg ) ); - return; - } - - Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), - "set g_nextMap \"%s\"", arg ); - Com_sprintf( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), - "Set the next map to '%s'", arg ); - } - else if( !Q_stricmp( vote, "draw" ) ) - { - strcpy( level.voteString[ team ], "evacuation" ); - strcpy( level.voteDisplayString[ team ], "End match in a draw" ); - level.voteDelay[ team ] = 3000; - } - else if( !Q_stricmp( vote, "sudden_death" ) ) - { - if(!g_suddenDeathVotePercent.integer) - { - trap_SendServerCommand( ent-g_entities, - "print \"Sudden Death votes have been disabled\n\"" ); - return; - } - if( G_TimeTilSuddenDeath( ) <= 0 ) - { - trap_SendServerCommand( ent - g_entities, - va( "print \"callvote: Sudden Death has already begun\n\"") ); - return; - } - if( level.suddenDeathBeginTime > 0 && - G_TimeTilSuddenDeath() <= g_suddenDeathVoteDelay.integer * 1000 ) - { - trap_SendServerCommand( ent - g_entities, - va( "print \"callvote: Sudden Death is imminent\n\"") ); - return; - } - level.voteThreshold[ team ] = g_suddenDeathVotePercent.integer; - Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), - "suddendeath %d", g_suddenDeathVoteDelay.integer ); - Com_sprintf( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), - "Begin sudden death in %d seconds", - g_suddenDeathVoteDelay.integer ); - } - else - { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); - trap_SendServerCommand( ent-g_entities, "print \"Valid vote commands are: " - "map, nextmap, map_restart, draw, sudden_death, kick, mute and unmute\n" ); - return; - } - } - else if( !Q_stricmp( vote, "denybuild" ) ) - { - if( level.clients[ clientNum ].pers.namelog->denyBuild ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: player already lost building rights\n\"", cmd ) ); - return; - } - - Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), - "denybuild %d", id ); - Com_sprintf( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), - "Take away building rights from '%s'", name ); - if( reason[ 0 ] ) - { - Q_strcat( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), va( " for '%s'", reason ) ); - } - } - else if( !Q_stricmp( vote, "allowbuild" ) ) - { - if( !level.clients[ clientNum ].pers.namelog->denyBuild ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: player already has building rights\n\"", cmd ) ); - return; - } - - Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), - "allowbuild %d", id ); - Com_sprintf( level.voteDisplayString[ team ], - sizeof( level.voteDisplayString[ team ] ), - "Allow '%s' to build", name ); - } - else if( !Q_stricmp( vote, "admitdefeat" ) ) - { - Com_sprintf( level.voteString[ team ], sizeof( level.voteString[ team ] ), - "admitdefeat %d", team ); - strcpy( level.voteDisplayString[ team ], "Admit Defeat" ); - level.voteDelay[ team ] = 3000; - } - else - { - trap_SendServerCommand( ent-g_entities, "print \"Invalid vote string\n\"" ); - trap_SendServerCommand( ent-g_entities, - "print \"Valid team vote commands are: " - "kick, denybuild, allowbuild and admitdefeat\n\"" ); - return; - } - - G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": %s\n", - team == TEAM_NONE ? "CallVote" : "CallTeamVote", - ent - g_entities, ent->client->pers.netname, level.voteString[ team ] ); - - if( team == TEAM_NONE ) - { - trap_SendServerCommand( -1, va( "print \"%s" S_COLOR_WHITE - " called a vote: %s\n\"", ent->client->pers.netname, - level.voteDisplayString[ team ] ) ); - } - else - { - int i; - - for( i = 0 ; i < level.maxclients ; i++ ) - { - if( level.clients[ i ].pers.connected == CON_CONNECTED ) - { - if( level.clients[ i ].pers.teamSelection == team || - ( level.clients[ i ].pers.teamSelection == TEAM_NONE && - G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) ) - { - trap_SendServerCommand( i, va( "print \"%s" S_COLOR_WHITE - " called a team vote: %s\n\"", ent->client->pers.netname, - level.voteDisplayString[ team ] ) ); - } - else if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) - { - trap_SendServerCommand( i, va( "chat -1 %d \"" S_COLOR_YELLOW "%s" - S_COLOR_YELLOW " called a team vote (%ss): %s\"", SAY_ADMINS, - ent->client->pers.netname, BG_TeamName( team ), - level.voteDisplayString[ team ] ) ); - } - } - } - } - - G_DecolorString( ent->client->pers.netname, caller, sizeof( caller ) ); - - level.voteTime[ team ] = level.time; - trap_SetConfigstring( CS_VOTE_TIME + team, - va( "%d", level.voteTime[ team ] ) ); - trap_SetConfigstring( CS_VOTE_STRING + team, - level.voteDisplayString[ team ] ); - trap_SetConfigstring( CS_VOTE_CALLER + team, - caller ); - - ent->client->pers.namelog->voteCount++; - ent->client->pers.vote |= 1 << team; - G_Vote( ent, team, qtrue ); -} - -/* -================== -Cmd_Vote_f -================== -*/ -void Cmd_Vote_f( gentity_t *ent ) -{ - char cmd[ MAX_TOKEN_CHARS ], vote[ MAX_TOKEN_CHARS ]; - team_t team = ent->client->pers.teamSelection; - - trap_Argv( 0, cmd, sizeof( cmd ) ); - if( Q_stricmp( cmd, "teamvote" ) ) - team = TEAM_NONE; - - if( !level.voteTime[ team ] ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: no vote in progress\n\"", cmd ) ); - return; - } - - if( ent->client->pers.voted & ( 1 << team ) ) - { - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: vote already cast\n\"", cmd ) ); - return; - } - - trap_SendServerCommand( ent-g_entities, - va( "print \"%s: vote cast\n\"", cmd ) ); - - trap_Argv( 1, vote, sizeof( vote ) ); - if( vote[ 0 ] == 'y' ) - ent->client->pers.vote |= 1 << team; - else - ent->client->pers.vote &= ~( 1 << team ); - G_Vote( ent, team, qtrue ); -} - - -/* -================= -Cmd_SetViewpos_f -================= -*/ -void Cmd_SetViewpos_f( gentity_t *ent ) -{ - vec3_t origin, angles; - char buffer[ MAX_TOKEN_CHARS ]; - int i; - - if( trap_Argc( ) != 5 ) - { - trap_SendServerCommand( ent-g_entities, "print \"usage: setviewpos x y z yaw\n\"" ); - return; - } - - VectorClear( angles ); - - for( i = 0; i < 3; i++ ) - { - trap_Argv( i + 1, buffer, sizeof( buffer ) ); - origin[ i ] = atof( buffer ); - } - - trap_Argv( 4, buffer, sizeof( buffer ) ); - angles[ YAW ] = atof( buffer ); - - TeleportPlayer( ent, origin, angles ); -} - -#define AS_OVER_RT3 ((ALIENSENSE_RANGE*0.5f)/M_ROOT3) - -static qboolean G_RoomForClassChange( gentity_t *ent, class_t class, - vec3_t newOrigin ) -{ - vec3_t fromMins, fromMaxs; - vec3_t toMins, toMaxs; - vec3_t temp; - trace_t tr; - float nudgeHeight; - float maxHorizGrowth; - class_t oldClass = ent->client->ps.stats[ STAT_CLASS ]; - - BG_ClassBoundingBox( oldClass, fromMins, fromMaxs, NULL, NULL, NULL ); - BG_ClassBoundingBox( class, toMins, toMaxs, NULL, NULL, NULL ); - - VectorCopy( ent->s.origin, newOrigin ); - - // find max x/y diff - maxHorizGrowth = toMaxs[ 0 ] - fromMaxs[ 0 ]; - if( toMaxs[ 1 ] - fromMaxs[ 1 ] > maxHorizGrowth ) - maxHorizGrowth = toMaxs[ 1 ] - fromMaxs[ 1 ]; - if( toMins[ 0 ] - fromMins[ 0 ] > -maxHorizGrowth ) - maxHorizGrowth = -( toMins[ 0 ] - fromMins[ 0 ] ); - if( toMins[ 1 ] - fromMins[ 1 ] > -maxHorizGrowth ) - maxHorizGrowth = -( toMins[ 1 ] - fromMins[ 1 ] ); - - if( maxHorizGrowth > 0.0f ) - { - // test by moving the player up the max required on a 60 degree slope - nudgeHeight = maxHorizGrowth * 2.0f; - } - else - { - // player is shrinking, so there's no need to nudge them upwards - nudgeHeight = 0.0f; - } - - // find what the new origin would be on a level surface - newOrigin[ 2 ] -= toMins[ 2 ] - fromMins[ 2 ]; - - //compute a place up in the air to start the real trace - VectorCopy( newOrigin, temp ); - temp[ 2 ] += nudgeHeight; - trap_Trace( &tr, newOrigin, toMins, toMaxs, temp, ent->s.number, MASK_PLAYERSOLID ); - - //trace down to the ground so that we can evolve on slopes - VectorCopy( newOrigin, temp ); - temp[ 2 ] += ( nudgeHeight * tr.fraction ); - trap_Trace( &tr, temp, toMins, toMaxs, newOrigin, ent->s.number, MASK_PLAYERSOLID ); - VectorCopy( tr.endpos, newOrigin ); - - //make REALLY sure - trap_Trace( &tr, newOrigin, toMins, toMaxs, newOrigin, - ent->s.number, MASK_PLAYERSOLID ); - - //check there is room to evolve - return ( !tr.startsolid && tr.fraction == 1.0f ); -} - -/* -================= -Cmd_Class_f -================= -*/ -void Cmd_Class_f( gentity_t *ent ) -{ - char s[ MAX_TOKEN_CHARS ]; - int clientNum; - int i; - vec3_t infestOrigin; - class_t currentClass = ent->client->pers.classSelection; - class_t newClass; - int entityList[ MAX_GENTITIES ]; - vec3_t range = { AS_OVER_RT3, AS_OVER_RT3, AS_OVER_RT3 }; - vec3_t mins, maxs; - int num; - gentity_t *other; - int oldBoostTime = -1; - vec3_t oldVel; - - clientNum = ent->client - level.clients; - trap_Argv( 1, s, sizeof( s ) ); - newClass = BG_ClassByName( s )->number; - - if( ent->client->sess.spectatorState != SPECTATOR_NOT ) - { - if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) - G_StopFollowing( ent ); - if( ent->client->pers.teamSelection == TEAM_ALIENS ) - { - if( newClass != PCL_ALIEN_BUILDER0 && - newClass != PCL_ALIEN_BUILDER0_UPG && - newClass != PCL_ALIEN_LEVEL0 ) - { - G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTSPAWN, newClass ); - return; - } - - if( !BG_ClassIsAllowed( newClass ) ) - { - G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTALLOWED, newClass ); - return; - } - - if( !BG_ClassAllowedInStage( newClass, g_alienStage.integer ) ) - { - G_TriggerMenuArgs( ent->client->ps.clientNum, MN_A_CLASSNOTATSTAGE, newClass ); - return; - } - - // spawn from an egg - if( G_PushSpawnQueue( &level.alienSpawnQueue, clientNum ) ) - { - ent->client->pers.classSelection = newClass; - ent->client->ps.stats[ STAT_CLASS ] = newClass; - } - } - else if( ent->client->pers.teamSelection == TEAM_HUMANS ) - { - //set the item to spawn with - if( !Q_stricmp( s, BG_Weapon( WP_MACHINEGUN )->name ) && - BG_WeaponIsAllowed( WP_MACHINEGUN ) ) - { - ent->client->pers.humanItemSelection = WP_MACHINEGUN; - } - else if( !Q_stricmp( s, BG_Weapon( WP_HBUILD )->name ) && - BG_WeaponIsAllowed( WP_HBUILD ) ) - { - ent->client->pers.humanItemSelection = WP_HBUILD; - } - else - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNSPAWNITEM ); - return; - } - // spawn from a telenode - if( G_PushSpawnQueue( &level.humanSpawnQueue, clientNum ) ) - { - ent->client->pers.classSelection = PCL_HUMAN; - ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; - } - } - return; - } - - if( ent->health <= 0 ) - return; - - if( ent->client->pers.teamSelection == TEAM_ALIENS ) - { - if( newClass == PCL_NONE ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_A_UNKNOWNCLASS ); - return; - } - - //if we are not currently spectating, we are attempting evolution - if( ent->client->pers.classSelection != PCL_NONE ) - { - int cost; - - //check that we have an overmind - if( !G_Overmind( ) ) - { - G_TriggerMenu( clientNum, MN_A_NOOVMND_EVOLVE ); - return; - } - - //check there are no humans nearby - VectorAdd( ent->client->ps.origin, range, maxs ); - VectorSubtract( ent->client->ps.origin, range, mins ); - - num = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - for( i = 0; i < num; i++ ) - { - other = &g_entities[ entityList[ i ] ]; - - if( ( other->client && other->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) || - ( other->s.eType == ET_BUILDABLE && other->buildableTeam == TEAM_HUMANS && - other->powered ) ) - { - trace_t tr; - - trap_Trace( &tr, ent->client->ps.origin, NULL, NULL, other->s.origin, ent->client->ps.clientNum, MASK_SOLID ); - - if( tr.fraction > 0.99f ) - { - G_TriggerMenu( clientNum, MN_A_TOOCLOSE ); - return; - } - } - } - - //check that we are not wallwalking - if( ent->client->ps.eFlags & EF_WALLCLIMB ) - { - G_TriggerMenu( clientNum, MN_A_EVOLVEWALLWALK ); - return; - } - - //guard against selling the HBUILD weapons exploit - if( ent->client->sess.spectatorState == SPECTATOR_NOT && - ( currentClass == PCL_ALIEN_BUILDER0 || - currentClass == PCL_ALIEN_BUILDER0_UPG ) && - ent->client->buildTimer ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_A_EVOLVEBUILDTIMER ); - return; - } - - cost = BG_ClassCanEvolveFromTo( currentClass, newClass, - ent->client->pers.credit, - g_alienStage.integer, 0 ); - - if( G_RoomForClassChange( ent, newClass, infestOrigin ) ) - { - if( cost >= 0 ) - { - - ent->client->pers.evolveHealthFraction = (float)ent->client->ps.stats[ STAT_HEALTH ] / - (float)BG_Class( currentClass )->health; - - if( ent->client->pers.evolveHealthFraction < 0.0f ) - ent->client->pers.evolveHealthFraction = 0.0f; - else if( ent->client->pers.evolveHealthFraction > 1.0f ) - ent->client->pers.evolveHealthFraction = 1.0f; - - //remove credit - G_AddCreditToClient( ent->client, -cost, qtrue ); - ent->client->pers.classSelection = newClass; - ClientUserinfoChanged( clientNum, qfalse ); - VectorCopy( infestOrigin, ent->s.pos.trBase ); - VectorCopy( ent->client->ps.velocity, oldVel ); - - if( ent->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) - oldBoostTime = ent->client->boostedTime; - - ClientSpawn( ent, ent, ent->s.pos.trBase, ent->s.apos.trBase ); - - VectorCopy( oldVel, ent->client->ps.velocity ); - if( oldBoostTime > 0 ) - { - ent->client->boostedTime = oldBoostTime; - ent->client->ps.stats[ STAT_STATE ] |= SS_BOOSTED; - } - } - else - G_TriggerMenuArgs( clientNum, MN_A_CANTEVOLVE, newClass ); - } - else - G_TriggerMenu( clientNum, MN_A_NOEROOM ); - } - } - else if( ent->client->pers.teamSelection == TEAM_HUMANS ) - G_TriggerMenu( clientNum, MN_H_DEADTOCLASS ); -} - - -/* -================= -Cmd_Destroy_f -================= -*/ -void Cmd_Destroy_f( gentity_t *ent ) -{ - vec3_t viewOrigin, forward, end; - trace_t tr; - gentity_t *traceEnt; - char cmd[ 12 ]; - qboolean deconstruct = qtrue; - qboolean lastSpawn = qfalse; - - if( ent->client->pers.namelog->denyBuild ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED ); - return; - } - - trap_Argv( 0, cmd, sizeof( cmd ) ); - if( Q_stricmp( cmd, "destroy" ) == 0 ) - deconstruct = qfalse; - - BG_GetClientViewOrigin( &ent->client->ps, viewOrigin ); - AngleVectors( ent->client->ps.viewangles, forward, NULL, NULL ); - VectorMA( viewOrigin, 100, forward, end ); - - trap_Trace( &tr, viewOrigin, NULL, NULL, end, ent->s.number, MASK_PLAYERSOLID ); - traceEnt = &g_entities[ tr.entityNum ]; - - if( tr.fraction < 1.0f && - ( traceEnt->s.eType == ET_BUILDABLE ) && - ( traceEnt->buildableTeam == ent->client->pers.teamSelection ) && - ( ( ent->client->ps.weapon >= WP_ABUILD ) && - ( ent->client->ps.weapon <= WP_HBUILD ) ) ) - { - // Always let the builder prevent the explosion - if( traceEnt->health <= 0 ) - { - G_QueueBuildPoints( traceEnt ); - G_RewardAttackers( traceEnt ); - G_FreeEntity( traceEnt ); - return; - } - - // Cancel deconstruction (unmark) - if( deconstruct && g_markDeconstruct.integer && traceEnt->deconstruct ) - { - traceEnt->deconstruct = qfalse; - return; - } - - // Prevent destruction of the last spawn - if( ent->client->pers.teamSelection == TEAM_ALIENS && - traceEnt->s.modelindex == BA_A_SPAWN ) - { - if( level.numAlienSpawns <= 1 ) - lastSpawn = qtrue; - } - else if( ent->client->pers.teamSelection == TEAM_HUMANS && - traceEnt->s.modelindex == BA_H_SPAWN ) - { - if( level.numHumanSpawns <= 1 ) - lastSpawn = qtrue; - } - - if( lastSpawn && !g_cheats.integer && - !g_markDeconstruct.integer ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_B_LASTSPAWN ); - return; - } - - // Don't allow destruction of buildables that cannot be rebuilt - if( G_TimeTilSuddenDeath( ) <= 0 ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); - return; - } - - if( !g_markDeconstruct.integer || - ( ent->client->pers.teamSelection == TEAM_HUMANS && - !G_FindPower( traceEnt, qtrue ) ) ) - { - if( ent->client->buildTimer ) - { - G_AddEvent( ent, EV_BUILD_DELAY, ent->client->ps.clientNum ); - return; - } - } - - if( traceEnt->health > 0 ) - { - if( !deconstruct ) - { - G_Damage( traceEnt, ent, ent, forward, tr.endpos, - traceEnt->health, 0, MOD_SUICIDE ); - } - else if( g_markDeconstruct.integer && - ( ent->client->pers.teamSelection != TEAM_HUMANS || - G_FindPower( traceEnt , qtrue ) || lastSpawn ) ) - { - traceEnt->deconstruct = qtrue; // Mark buildable for deconstruction - traceEnt->deconstructTime = level.time; - } - else - { - if( !g_cheats.integer && !g_instantBuild.integer ) // add a bit to the build timer - { - ent->client->buildTimer += - BG_Buildable( traceEnt->s.modelindex, traceEnt->cuboidSize )->buildTime / 4; - G_RecalcBuildTimer(ent->client); - } - G_Damage( traceEnt, ent, ent, forward, tr.endpos, - traceEnt->health, 0, MOD_DECONSTRUCT ); - G_FreeEntity( traceEnt ); - } - } - } -} - -/* -================= -Cmd_ActivateItem_f - -Activate an item -================= -*/ -void Cmd_ActivateItem_f( gentity_t *ent ) -{ - char s[ MAX_TOKEN_CHARS ]; - int upgrade, weapon; - - trap_Argv( 1, s, sizeof( s ) ); - - // "weapon" aliased to whatever weapon you have - if( !Q_stricmp( "weapon", s ) ) - { - if( ent->client->ps.weapon == WP_BLASTER && - BG_PlayerCanChangeWeapon( &ent->client->ps ) ) - G_ForceWeaponChange( ent, WP_NONE ); - return; - } - - upgrade = BG_UpgradeByName( s )->number; - weapon = BG_WeaponByName( s )->number; - - if( upgrade != UP_NONE && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) - BG_ActivateUpgrade( upgrade, ent->client->ps.stats ); - else if( weapon != WP_NONE && - BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) - { - if( ent->client->ps.weapon != weapon && - BG_PlayerCanChangeWeapon( &ent->client->ps ) ) - G_ForceWeaponChange( ent, weapon ); - } - else - trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); -} - - -/* -================= -Cmd_DeActivateItem_f - -Deactivate an item -================= -*/ -void Cmd_DeActivateItem_f( gentity_t *ent ) -{ - char s[ MAX_TOKEN_CHARS ]; - upgrade_t upgrade; - - trap_Argv( 1, s, sizeof( s ) ); - upgrade = BG_UpgradeByName( s )->number; - - if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) - { - BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); - if( upgrade == UP_JETPACK ) - BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &ent->client->ps ); - } - else - trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); -} - - -/* -================= -Cmd_ToggleItem_f -================= -*/ -void Cmd_ToggleItem_f( gentity_t *ent ) -{ - char s[ MAX_TOKEN_CHARS ]; - weapon_t weapon; - upgrade_t upgrade; - - trap_Argv( 1, s, sizeof( s ) ); - upgrade = BG_UpgradeByName( s )->number; - weapon = BG_WeaponByName( s )->number; - - if( weapon != WP_NONE ) - { - if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) - return; - - //special case to allow switching between - //the blaster and the primary weapon - if( ent->client->ps.weapon != WP_BLASTER ) - weapon = WP_BLASTER; - else - weapon = WP_NONE; - - G_ForceWeaponChange( ent, weapon ); - } - else if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) - { - if( BG_UpgradeIsActive( upgrade, ent->client->ps.stats ) ) - { - BG_DeactivateUpgrade( upgrade, ent->client->ps.stats ); - if( upgrade == UP_JETPACK ) - BG_AddPredictableEventToPlayerstate( EV_JETPACK_DEACTIVATE, 0, &ent->client->ps ); - } - else - BG_ActivateUpgrade( upgrade, ent->client->ps.stats ); - } - else - trap_SendServerCommand( ent-g_entities, va( "print \"You don't have the %s\n\"", s ) ); -} - -/* -================= -Cmd_Buy_f -================= -*/ -void Cmd_Buy_f( gentity_t *ent ) -{ - char s[ MAX_TOKEN_CHARS ]; - weapon_t weapon; - upgrade_t upgrade; - qboolean energyOnly, sellHelmet = qfalse; - - trap_Argv( 1, s, sizeof( s ) ); - - weapon = BG_WeaponByName( s )->number; - upgrade = BG_UpgradeByName( s )->number; - - if( upgrade == UP_NONE && !Q_stricmp(s, "helmet") ) - { - if( g_humanStage.integer < S2 ) - upgrade = UP_HELMET_MK1; - else - upgrade = UP_HELMET_MK2; - sellHelmet = qtrue; - } - - // Only give energy from reactors or repeaters - if( G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) - energyOnly = qfalse; - else if( upgrade == UP_AMMO && - BG_Weapon( ent->client->ps.stats[ STAT_WEAPON ] )->usesEnergy && - ( G_BuildableRange( ent->client->ps.origin, 100, BA_H_REACTOR ) || - G_BuildableRange( ent->client->ps.origin, 100, BA_H_REPEATER ) ) ) - energyOnly = qtrue; - else - { - if( upgrade == UP_AMMO && - BG_Weapon( ent->client->ps.weapon )->usesEnergy ) - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOENERGYAMMOHERE ); - else - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); - return; - } - - if( weapon != WP_NONE ) - { - //already got this? - if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD ); - return; - } - - // Only humans can buy stuff - if( BG_Weapon( weapon )->team != TEAM_HUMANS ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" ); - return; - } - - //are we /allowed/ to buy this? - if( !BG_Weapon( weapon )->purchasable ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); - return; - } - - //are we /allowed/ to buy this? - if( !BG_WeaponAllowedInStage( weapon, g_humanStage.integer ) || !BG_WeaponIsAllowed( weapon ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); - return; - } - - //can afford this? - if( BG_Weapon( weapon )->price > (short)ent->client->pers.credit ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); - return; - } - - //have space to carry this? - if( BG_Weapon( weapon )->slots & BG_SlotsForInventory( ent->client->ps.stats ) ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); - return; - } - - // In some instances, weapons can't be changed - if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) - return; - - ent->client->ps.stats[ STAT_WEAPON ] = weapon; - ent->client->ps.ammo = BG_Weapon( weapon )->maxAmmo; - ent->client->ps.clips = BG_Weapon( weapon )->maxClips; - - if( BG_Weapon( weapon )->usesEnergy && - BG_InventoryContainsUpgrade( UP_BATTPACK, ent->client->ps.stats ) ) - ent->client->ps.ammo *= BATTPACK_MODIFIER; - - G_ForceWeaponChange( ent, weapon ); - - //set build delay/pounce etc to 0 - ent->client->ps.stats[ STAT_MISC ] = 0; - ent->client->buildTimer = 0; - - //subtract from funds - G_AddCreditToClient( ent->client, -(short)BG_Weapon( weapon )->price, qfalse ); - } - else if( upgrade != UP_NONE ) - { - //already got this? - if( !sellHelmet && BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_ITEMHELD ); - return; - } - - //can afford this? - if( BG_Upgrade( upgrade )->price > (short)ent->client->pers.credit ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOFUNDS ); - return; - } - - //have space to carry this? - if( !sellHelmet && BG_Upgrade( upgrade )->slots & BG_SlotsForInventory( ent->client->ps.stats ) ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOSLOTS ); - return; - } - - // Only humans can buy stuff - if( BG_Upgrade( upgrade )->team != TEAM_HUMANS ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't buy alien items\n\"" ); - return; - } - - //are we /allowed/ to buy this? - if( !BG_Upgrade( upgrade )->purchasable ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); - return; - } - - //are we /allowed/ to buy this? - if( !BG_UpgradeAllowedInStage( upgrade, g_humanStage.integer ) || !BG_UpgradeIsAllowed( upgrade ) ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't buy this item\n\"" ); - return; - } - - if( upgrade == UP_AMMO ) - { - G_GiveClientMaxAmmo( ent, energyOnly ); - if( !energyOnly && BG_InventoryContainsUpgrade( UP_JETPACK, ent->client->ps.stats ) && - ent->client->ps.stats[ STAT_FUEL ] < JETPACK_FUEL_FULL ) - { - G_AddEvent( ent, EV_JETPACK_REFUEL, 0) ; - ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; - } - } - else - { - if( upgrade == UP_BATTLESUIT ) - { - vec3_t newOrigin; - - if( !G_RoomForClassChange( ent, PCL_HUMAN_BSUIT, newOrigin ) ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITON ); - return; - } - VectorCopy( newOrigin, ent->client->ps.origin ); - ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN_BSUIT; - ent->client->pers.classSelection = PCL_HUMAN_BSUIT; - ent->client->ps.eFlags ^= EF_TELEPORT_BIT; - } - else if( upgrade == UP_JETPACK ) - ent->client->ps.stats[ STAT_FUEL ] = JETPACK_FUEL_FULL; - - if( sellHelmet ) - { - BG_RemoveUpgradeFromInventory( UP_HELMET_MK1, ent->client->ps.stats ); - BG_RemoveUpgradeFromInventory( UP_HELMET_MK2, ent->client->ps.stats ); - } - - //add to inventory - BG_AddUpgradeToInventory( upgrade, ent->client->ps.stats ); - } - - if( upgrade == UP_BATTPACK ) - G_GiveClientMaxAmmo( ent, qtrue ); - - //subtract from funds - G_AddCreditToClient( ent->client, -(short)BG_Upgrade( upgrade )->price, qfalse ); - } - else - G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); - - //update ClientInfo - ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); -} - - -/* -================= -Cmd_Sell_f -================= -*/ -void Cmd_Sell_f( gentity_t *ent ) -{ - char s[ MAX_TOKEN_CHARS ]; - int i; - weapon_t weapon; - upgrade_t upgrade; - - trap_Argv( 1, s, sizeof( s ) ); - - //no armoury nearby - if( !G_BuildableRange( ent->client->ps.origin, 100, BA_H_ARMOURY ) ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOARMOURYHERE ); - return; - } - - if( !Q_stricmpn( s, "weapon", 6 ) ) - weapon = ent->client->ps.stats[ STAT_WEAPON ]; - else - weapon = BG_WeaponByName( s )->number; - - upgrade = BG_UpgradeByName( s )->number; - - if( !upgrade && !Q_stricmp( s, "helmet" ) ) - { - if( BG_InventoryContainsUpgrade( UP_HELMET_MK1, ent->client->ps.stats ) ) - upgrade = UP_HELMET_MK1; - else if( BG_InventoryContainsUpgrade( UP_HELMET_MK2, ent->client->ps.stats ) ) - upgrade = UP_HELMET_MK2; - } - - if( weapon != WP_NONE ) - { - weapon_t selected = BG_GetPlayerWeapon( &ent->client->ps ); - - if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) - return; - - //are we /allowed/ to sell this? - if( !BG_Weapon( weapon )->purchasable ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't sell this weapon\n\"" ); - return; - } - - //remove weapon if carried - if( BG_InventoryContainsWeapon( weapon, ent->client->ps.stats ) ) - { - //guard against selling the HBUILD weapons exploit - if( weapon == WP_HBUILD && ent->client->buildTimer ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); - return; - } - - ent->client->ps.stats[ STAT_WEAPON ] = WP_NONE; - // Cancel ghost buildables - ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; - - //add to funds - G_AddCreditToClient( ent->client, (short)BG_Weapon( weapon )->price, qfalse ); - } - - //if we have this weapon selected, force a new selection - if( weapon == selected ) - G_ForceWeaponChange( ent, WP_NONE ); - } - else if( upgrade != UP_NONE ) - { - //are we /allowed/ to sell this? - if( !BG_Upgrade( upgrade )->purchasable ) - { - trap_SendServerCommand( ent-g_entities, "print \"You can't sell this item\n\"" ); - return; - } - //remove upgrade if carried - if( BG_InventoryContainsUpgrade( upgrade, ent->client->ps.stats ) ) - { - // shouldn't really need to test for this, but just to be safe - if( upgrade == UP_BATTLESUIT ) - { - vec3_t newOrigin; - - if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); - return; - } - VectorCopy( newOrigin, ent->client->ps.origin ); - ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; - ent->client->pers.classSelection = PCL_HUMAN; - ent->client->ps.eFlags ^= EF_TELEPORT_BIT; - } - - //add to inventory - BG_RemoveUpgradeFromInventory( upgrade, ent->client->ps.stats ); - - if( upgrade == UP_BATTPACK ) - G_GiveClientMaxAmmo( ent, qtrue ); - - //add to funds - G_AddCreditToClient( ent->client, (short)BG_Upgrade( upgrade )->price, qfalse ); - } - } - else if( !Q_stricmp( s, "weapons" ) ) - { - weapon_t selected = BG_GetPlayerWeapon( &ent->client->ps ); - - if( !BG_PlayerCanChangeWeapon( &ent->client->ps ) ) - return; - - for( i = WP_NONE + 1; i < WP_NUM_WEAPONS; i++ ) - { - //guard against selling the HBUILD weapons exploit - if( i == WP_HBUILD && ent->client->buildTimer ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_ARMOURYBUILDTIMER ); - continue; - } - - if( BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && - BG_Weapon( i )->purchasable ) - { - ent->client->ps.stats[ STAT_WEAPON ] = WP_NONE; - - //add to funds - G_AddCreditToClient( ent->client, (short)BG_Weapon( i )->price, qfalse ); - } - - //if we have this weapon selected, force a new selection - if( i == selected ) - G_ForceWeaponChange( ent, WP_NONE ); - } - } - else if( !Q_stricmp( s, "upgrades" ) ) - { - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - //remove upgrade if carried - if( BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) && - BG_Upgrade( i )->purchasable ) - { - - // shouldn't really need to test for this, but just to be safe - if( i == UP_BATTLESUIT ) - { - vec3_t newOrigin; - - if( !G_RoomForClassChange( ent, PCL_HUMAN, newOrigin ) ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_H_NOROOMBSUITOFF ); - continue; - } - VectorCopy( newOrigin, ent->client->ps.origin ); - ent->client->ps.stats[ STAT_CLASS ] = PCL_HUMAN; - ent->client->pers.classSelection = PCL_HUMAN; - ent->client->ps.eFlags ^= EF_TELEPORT_BIT; - } - - BG_RemoveUpgradeFromInventory( i, ent->client->ps.stats ); - - if( i == UP_BATTPACK ) - G_GiveClientMaxAmmo( ent, qtrue ); - - //add to funds - G_AddCreditToClient( ent->client, (short)BG_Upgrade( i )->price, qfalse ); - } - } - } - else - G_TriggerMenu( ent->client->ps.clientNum, MN_H_UNKNOWNITEM ); - - //update ClientInfo - ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); -} - -/* -================= -Cmd_CheckCuboidSize - -Check if the specified dimensions are valid. -================= -*/ -qboolean Cmd_CheckCuboidSize(vec3_t dims) -{ - if(g_cuboidSizeLimit.integer) - if(dims[0]>g_cuboidSizeLimit.integer||dims[1]>g_cuboidSizeLimit.integer||dims[2]>g_cuboidSizeLimit.integer) - return qfalse; - if(dims[0]*dims[1]*dims[2]client->cuboidSelection); - trap_SendServerCommand(ent->client-level.clients,va("cb3 %i\n",echo)); - G_RelayCuboidToSpectators(ent->client); - } - else - { - if(Cmd_CheckCuboidSize(ent->client->cuboidSelection)) - trap_SendServerCommand(ent->client-level.clients,va("cb3 %i %f %f %f\n",echo,ent->client->cuboidSelection[0],ent->client->cuboidSelection[1],ent->client->cuboidSelection[2])); - else - trap_SendServerCommand(ent->client-level.clients,va("cb3 %i %f %f %f\n",echo,g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer)); - } -} - - -/* -================= -Cmd_Build_f -================= -*/ -void Cmd_Build_f( gentity_t *ent ) -{ - char s[ MAX_TOKEN_CHARS ]; - buildable_t buildable; - float dist; - vec3_t origin, normal; - team_t team; - char buf[128]; - vec3_t dims; - - if( ent->client->pers.namelog->denyBuild ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_B_REVOKED ); - return; - } - - if( ent->client->pers.teamSelection == level.surrenderTeam ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_B_SURRENDER ); - return; - } - - trap_Argv( 1, s, sizeof( s ) ); - - buildable = BG_BuildableByName( s )->number; - - /* To allow players build cuboids with completely arbitrary - * dimensions (the current resizing method doesn't provide - * much precision) the build command was extended with the - * following syntax: - * build [building] [X] [Y] [Z] - * where X, Y and Z are respectively the cuboid's length - * on the X, Y and Z axis. - */ - if( BG_Buildable(buildable,NULL)->cuboid ) - { - if( trap_Argc() >= 5 ) - { - trap_Argv(2,s,sizeof(s)); - dims[0]=MAX(1,atof(s)); - trap_Argv(3,s,sizeof(s)); - dims[1]=MAX(1,atof(s)); - trap_Argv(4,s,sizeof(s)); - dims[2]=MAX(1,atof(s)); - if(!Cmd_CheckCuboidSize(dims)) - { - Com_sprintf(buf,sizeof(buf),"print \"^1error: invalid cuboid size (min volume: %i, max size: %s)\n\"", - CUBOID_MINVOLUME,(g_cuboidSizeLimit.integer?va("%ix%ix%i",g_cuboidSizeLimit.integer,g_cuboidSizeLimit.integer, g_cuboidSizeLimit.integer):"no limit")); - trap_SendServerCommand(ent->client-level.clients,buf); - return; - } - VectorCopy(dims,ent->client->cuboidSelection); - } - // client is building a cuboid for the first time so reset the selection to default - if(!Cmd_CheckCuboidSize(ent->client->cuboidSelection)) - { - ent->client->cuboidSelection[0]=32; - ent->client->cuboidSelection[1]=32; - ent->client->cuboidSelection[2]=32; - trap_SendServerCommand(ent->client-level.clients,"cb2 32 32 32"); - G_RelayCuboidToSpectators(ent->client); - } - - if(!BG_CuboidAllowed((team==TEAM_ALIENS?g_alienStage.integer:g_humanStage.integer))) - { - if(BG_CuboidMode()==1) - G_TriggerMenu(ent->client->ps.clientNum,MN_B_CUBOID_MODE1); - else - G_TriggerMenu(ent->client->ps.clientNum,MN_B_CUBOID_MODE2); - return; - } - } - - if( G_TimeTilSuddenDeath( ) <= 0 ) - { - G_TriggerMenu( ent->client->ps.clientNum, MN_B_SUDDENDEATH ); - return; - } - - team = ent->client->ps.stats[ STAT_TEAM ]; - - if( buildable != BA_NONE && - ( ( 1 << ent->client->ps.weapon ) & BG_Buildable( buildable, NULL )->buildWeapon ) && - BG_BuildableIsAllowed( buildable ) && - ( ( team == TEAM_ALIENS && BG_BuildableAllowedInStage( buildable, g_alienStage.integer ) ) || - ( team == TEAM_HUMANS && BG_BuildableAllowedInStage( buildable, g_humanStage.integer ) ) ) ) - { - dynMenu_t err; - dist = BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->buildDist; - - ent->client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; - - //these are the errors displayed when the builder first selects something to use - switch( G_CanBuild( ent, buildable, dist, origin, normal, ent->client->cuboidSelection ) ) - { - // can place right away, set the blueprint and the valid togglebit - case IBE_NONE: - case IBE_TNODEWARN: - case IBE_RPTNOREAC: - case IBE_RPTPOWERHERE: - case IBE_SPWNWARN: - err = MN_NONE; - // we OR-in the selected builable later - ent->client->ps.stats[ STAT_BUILDABLE ] = SB_VALID_TOGGLEBIT; - break; - - // can't place yet but maybe soon: start with valid togglebit off - case IBE_NORMAL: - case IBE_NOCREEP: - case IBE_NOROOM: - case IBE_NOOVERMIND: - case IBE_NOPOWERHERE: - case IBE_NOSURF: - err = MN_NONE; - break; - - // more serious errors just pop a menu - case IBE_NOALIENBP: - err = MN_A_NOBP; - break; - - case IBE_ONEOVERMIND: - err = MN_A_ONEOVERMIND; - break; - - case IBE_ONEREACTOR: - err = MN_H_ONEREACTOR; - break; - - case IBE_NOHUMANBP: - err = MN_H_NOBP; - break; - - case IBE_NODCC: - err = MN_H_NODCC; - break; - - case IBE_PERMISSION: - err = MN_B_CANNOT; - break; - - case IBE_LASTSPAWN: - err = MN_B_LASTSPAWN; - break; - - case IBE_TOODENSE: - err = MN_B_TOODENSE; - break; - - default: - err = -1; // stop uninitialised warning - break; - } - - if( ( err == MN_A_NOBP || err == MN_H_NOBP ) && BG_Buildable(buildable,NULL)->cuboid ) - { - err = MN_NONE; - ent->client->ps.stats[ STAT_BUILDABLE ] = SB_VALID_TOGGLEBIT; - } - - if( err == MN_NONE || ent->client->pers.disableBlueprintErrors ) - { - trap_SendServerCommand(ent->client-level.clients,va("cb2 %f %f %f\n", - ent->client->cuboidSelection[0], - ent->client->cuboidSelection[1], - ent->client->cuboidSelection[2])); - G_RelayCuboidToSpectators(ent->client); - ent->client->ps.stats[ STAT_BUILDABLE ] |= buildable; - } - else - G_TriggerMenu( ent->client->ps.clientNum, err ); - } - else - G_TriggerMenu( ent->client->ps.clientNum, MN_B_CANNOT ); -} - -/* -================= -Cmd_Reload_f -================= -*/ -void Cmd_Reload_f( gentity_t *ent ) -{ - playerState_t *ps = &ent->client->ps; - int ammo; - - // weapon doesn't ever need reloading - if( BG_Weapon( ps->weapon )->infiniteAmmo ) - return; - - if( ps->clips <= 0 ) - return; - - if( BG_Weapon( ps->weapon )->usesEnergy && - BG_InventoryContainsUpgrade( UP_BATTPACK, ps->stats ) ) - ammo = BG_Weapon( ps->weapon )->maxAmmo * BATTPACK_MODIFIER; - else - ammo = BG_Weapon( ps->weapon )->maxAmmo; - - // don't reload when full - if( ps->ammo >= ammo ) - return; - - // the animation, ammo refilling etc. is handled by PM_Weapon - if( ent->client->ps.weaponstate != WEAPON_RELOADING ) - ent->client->ps.pm_flags |= PMF_WEAPON_RELOAD; -} - -/* -================= -G_StopFromFollowing - -stops any other clients from following this one -called when a player leaves a team or dies -================= -*/ -void G_StopFromFollowing( gentity_t *ent ) -{ - int i; - - for( i = 0; i < level.maxclients; i++ ) - { - if( level.clients[ i ].sess.spectatorState == SPECTATOR_FOLLOW && - level.clients[ i ].sess.spectatorClient == ent->client->ps.clientNum ) - { - if( !G_FollowNewClient( &g_entities[ i ], 1 ) ) - G_StopFollowing( &g_entities[ i ] ); - } - } -} - -/* -================= -G_StopFollowing - -If the client being followed leaves the game, or you just want to drop -to free floating spectator mode -================= -*/ -void G_StopFollowing( gentity_t *ent ) -{ - ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; - - if( ent->client->pers.teamSelection == TEAM_NONE ) - { - ent->client->sess.spectatorState = - ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FREE; - } - else - { - vec3_t spawn_origin, spawn_angles; - - ent->client->sess.spectatorState = - ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_LOCKED; - - if( ent->client->pers.teamSelection == TEAM_ALIENS ) - G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); - else if( ent->client->pers.teamSelection == TEAM_HUMANS ) - G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); - - G_SetOrigin( ent, spawn_origin ); - VectorCopy( spawn_origin, ent->client->ps.origin ); - G_SetClientViewAngle( ent, spawn_angles ); - } - ent->client->sess.spectatorClient = -1; - ent->client->ps.pm_flags &= ~PMF_FOLLOW; - ent->client->ps.groundEntityNum = ENTITYNUM_NONE; - ent->client->ps.stats[ STAT_STATE ] = 0; - ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; - ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); - ent->client->ps.viewangles[ PITCH ] = 0.0f; - ent->client->ps.clientNum = ent - g_entities; - ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit; - - CalculateRanks( ); -} - -/* -================= -G_FollowLockView - -Client is still following a player, but that player has gone to spectator -mode and cannot be followed for the moment -================= -*/ -void G_FollowLockView( gentity_t *ent ) -{ - vec3_t spawn_origin, spawn_angles; - int clientNum; - - clientNum = ent->client->sess.spectatorClient; - ent->client->sess.spectatorState = - ent->client->ps.persistant[ PERS_SPECSTATE ] = SPECTATOR_FOLLOW; - ent->client->ps.clientNum = clientNum; - ent->client->ps.pm_flags &= ~PMF_FOLLOW; - ent->client->ps.stats[ STAT_TEAM ] = ent->client->pers.teamSelection; - ent->client->ps.stats[ STAT_STATE ] &= ~SS_WALLCLIMBING; - ent->client->ps.stats[ STAT_VIEWLOCK ] = 0; - ent->client->ps.eFlags &= ~( EF_WALLCLIMB | EF_WALLCLIMBCEILING ); - ent->client->ps.eFlags ^= EF_TELEPORT_BIT; - ent->client->ps.viewangles[ PITCH ] = 0.0f; - - // Put the view at the team spectator lock position - if( level.clients[ clientNum ].pers.teamSelection == TEAM_ALIENS ) - G_SelectAlienLockSpawnPoint( spawn_origin, spawn_angles ); - else if( level.clients[ clientNum ].pers.teamSelection == TEAM_HUMANS ) - G_SelectHumanLockSpawnPoint( spawn_origin, spawn_angles ); - - G_SetOrigin( ent, spawn_origin ); - VectorCopy( spawn_origin, ent->client->ps.origin ); - G_SetClientViewAngle( ent, spawn_angles ); -} - -/* -================= -G_FollowNewClient - -This was a really nice, elegant function. Then I fucked it up. -================= -*/ -qboolean G_FollowNewClient( gentity_t *ent, int dir ) -{ - int clientnum = ent->client->sess.spectatorClient; - int original = clientnum; - qboolean selectAny = qfalse; - - if( dir > 1 ) - dir = 1; - else if( dir < -1 ) - dir = -1; - else if( dir == 0 ) - return qtrue; - - if( ent->client->sess.spectatorState == SPECTATOR_NOT ) - return qfalse; - - // select any if no target exists - if( clientnum < 0 || clientnum >= level.maxclients ) - { - clientnum = original = 0; - selectAny = qtrue; - } - - do - { - clientnum += dir; - - if( clientnum >= level.maxclients ) - clientnum = 0; - - if( clientnum < 0 ) - clientnum = level.maxclients - 1; - - // can't follow self - if( &g_entities[ clientnum ] == ent ) - continue; - - // avoid selecting existing follow target - if( clientnum == original && !selectAny ) - continue; //effectively break; - - // can only follow connected clients - if( level.clients[ clientnum ].pers.connected != CON_CONNECTED ) - continue; - - // can't follow a spectator - if( level.clients[ clientnum ].pers.teamSelection == TEAM_NONE ) - continue; - - // if stickyspec is disabled, can't follow someone in queue either - if( !ent->client->pers.stickySpec && - level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) - continue; - - // can only follow teammates when dead and on a team - if( ent->client->pers.teamSelection != TEAM_NONE && - ( level.clients[ clientnum ].pers.teamSelection != - ent->client->pers.teamSelection ) ) - continue; - - // this is good, we can use it - ent->client->sess.spectatorClient = clientnum; - ent->client->sess.spectatorState = SPECTATOR_FOLLOW; - - // if this client is in the spawn queue, we need to do something special - if( level.clients[ clientnum ].sess.spectatorState != SPECTATOR_NOT ) - G_FollowLockView( ent ); - - return qtrue; - - } while( clientnum != original ); - - return qfalse; -} - -/* -================= -G_ToggleFollow -================= -*/ -void G_ToggleFollow( gentity_t *ent ) -{ - if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) - G_StopFollowing( ent ); - else - G_FollowNewClient( ent, 1 ); -} - -/* -================= -Cmd_Follow_f -================= -*/ -void Cmd_Follow_f( gentity_t *ent ) -{ - int i; - char arg[ MAX_NAME_LENGTH ]; - - // won't work unless spectating - if( ent->client->sess.spectatorState == SPECTATOR_NOT ) - return; - - if( trap_Argc( ) != 2 ) - { - G_ToggleFollow( ent ); - } - else - { - char err[ MAX_STRING_CHARS ]; - trap_Argv( 1, arg, sizeof( arg ) ); - - i = G_ClientNumberFromString( arg, err, sizeof( err ) ); - - if( i == -1 ) - { - trap_SendServerCommand( ent - g_entities, - va( "print \"follow: %s\"", err ) ); - return; - } - - // can't follow self - if( &level.clients[ i ] == ent->client ) - return; - - // can't follow another spectator if sticky spec is off - if( !ent->client->pers.stickySpec && - level.clients[ i ].sess.spectatorState != SPECTATOR_NOT ) - return; - - // if not on team spectator, you can only follow teammates - if( ent->client->pers.teamSelection != TEAM_NONE && - ( level.clients[ i ].pers.teamSelection != - ent->client->pers.teamSelection ) ) - return; - - ent->client->sess.spectatorState = SPECTATOR_FOLLOW; - ent->client->sess.spectatorClient = i; - } -} - -/* -================= -Cmd_FollowCycle_f -================= -*/ -void Cmd_FollowCycle_f( gentity_t *ent ) -{ - char args[ 11 ]; - int dir = 1; - - trap_Argv( 0, args, sizeof( args ) ); - if( Q_stricmp( args, "followprev" ) == 0 ) - dir = -1; - - // won't work unless spectating - if( ent->client->sess.spectatorState == SPECTATOR_NOT ) - return; - - G_FollowNewClient( ent, dir ); -} - -static void Cmd_Ignore_f( gentity_t *ent ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ]; - char cmd[ 9 ]; - int matches = 0; - int i; - qboolean ignore = qfalse; - - trap_Argv( 0, cmd, sizeof( cmd ) ); - if( Q_stricmp( cmd, "ignore" ) == 0 ) - ignore = qtrue; - - if( trap_Argc() < 2 ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "usage: %s [clientNum | partial name match]\n\"", cmd ) ); - return; - } - - Q_strncpyz( name, ConcatArgs( 1 ), sizeof( name ) ); - matches = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); - if( matches < 1 ) - { - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "%s: no clients match the name '%s'\n\"", cmd, name ) ); - return; - } - - for( i = 0; i < matches; i++ ) - { - if( ignore ) - { - if( !Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) ) - { - Com_ClientListAdd( &ent->client->sess.ignoreList, pids[ i ] ); - ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "ignore: added %s^7 to your ignore list\n\"", - level.clients[ pids[ i ] ].pers.netname ) ); - } - else - { - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "ignore: %s^7 is already on your ignore list\n\"", - level.clients[ pids[ i ] ].pers.netname ) ); - } - } - else - { - if( Com_ClientListContains( &ent->client->sess.ignoreList, pids[ i ] ) ) - { - Com_ClientListRemove( &ent->client->sess.ignoreList, pids[ i ] ); - ClientUserinfoChanged( ent->client->ps.clientNum, qfalse ); - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "unignore: removed %s^7 from your ignore list\n\"", - level.clients[ pids[ i ] ].pers.netname ) ); - } - else - { - trap_SendServerCommand( ent-g_entities, va( "print \"[skipnotify]" - "unignore: %s^7 is not on your ignore list\n\"", - level.clients[ pids[ i ] ].pers.netname ) ); - } - } - } -} - -/* -================= -Cmd_ListMaps_f - -List all maps on the server -================= -*/ - -static int SortMaps( const void *a, const void *b ) -{ - return strcmp( *(char **)a, *(char **)b ); -} - -#define MAX_MAPLIST_MAPS 256 -#define MAX_MAPLIST_ROWS 9 -void Cmd_ListMaps_f( gentity_t *ent ) -{ - char search[ 16 ] = {""}; - char fileList[ 4096 ] = {""}; - char *fileSort[ MAX_MAPLIST_MAPS ]; - char *filePtr, *p; - int numFiles; - int fileLen = 0; - int shown = 0; - int count = 0; - int page = 0; - int pages; - int row, rows; - int start, i, j; - - if( trap_Argc( ) > 1 ) - { - trap_Argv( 1, search, sizeof( search ) ); - for( p = search; ( *p ) && isdigit( *p ); p++ ); - if( !( *p ) ) - { - page = atoi( search ); - search[ 0 ] = '\0'; - } - else if( trap_Argc( ) > 2 ) - { - char lp[ 8 ]; - trap_Argv( 2, lp, sizeof( lp ) ); - page = atoi( lp ); - } - - if( page > 0 ) - page--; - else if( page < 0 ) - page = 0; - } - - numFiles = trap_FS_GetFileList( "maps/", ".bsp", - fileList, sizeof( fileList ) ); - filePtr = fileList; - for( i = 0; i < numFiles && count < MAX_MAPLIST_MAPS; i++, filePtr += fileLen + 1 ) - { - fileLen = strlen( filePtr ); - if ( fileLen < 5 ) - continue; - - filePtr[ fileLen - 4 ] = '\0'; - - if( search[ 0 ] && !strstr( filePtr, search ) ) - continue; - - fileSort[ count ] = filePtr; - count++; - } - qsort( fileSort, count, sizeof( fileSort[ 0 ] ), SortMaps ); - - rows = ( count + 2 ) / 3; - pages = MAX( 1, ( rows + MAX_MAPLIST_ROWS - 1 ) / MAX_MAPLIST_ROWS ); - if( page >= pages ) - page = pages - 1; - - start = page * MAX_MAPLIST_ROWS * 3; - if( count < start + ( 3 * MAX_MAPLIST_ROWS ) ) - rows = ( count - start + 2 ) / 3; - else - rows = MAX_MAPLIST_ROWS; - - ADMBP_begin( ); - for( row = 0; row < rows; row++ ) - { - for( i = start + row, j = 0; i < count && j < 3; i += rows, j++ ) - { - ADMBP( va( "^7 %-20s", fileSort[ i ] ) ); - shown++; - } - ADMBP( "\n" ); - } - if ( search[ 0 ] ) - ADMBP( va( "^3listmaps: ^7found %d maps matching '%s^7'", count, search ) ); - else - ADMBP( va( "^3listmaps: ^7listing %d of %d maps", shown, count ) ); - if( pages > 1 ) - ADMBP( va( ", page %d of %d", page + 1, pages ) ); - if( page + 1 < pages ) - ADMBP( va( ", use 'listmaps %s%s%d' to see more", - search, ( search[ 0 ] ) ? " ": "", page + 2 ) ); - ADMBP( ".\n" ); - ADMBP_end( ); -} - -/* -================= -Cmd_Test_f -================= -*/ -void Cmd_Test_f( gentity_t *humanPlayer ) -{ -} - -/* -================= -Cmd_Damage_f - -Deals damage to you (for testing), arguments: [damage] [dx] [dy] [dz] -The dx/dy arguments describe the damage point's offset from the entity origin -================= -*/ -void Cmd_Damage_f( gentity_t *ent ) -{ - vec3_t point; - char arg[ 16 ]; - float dx = 0.0f, dy = 0.0f, dz = 100.0f; - int damage = 100; - qboolean nonloc = qtrue; - - if( trap_Argc() > 1 ) - { - trap_Argv( 1, arg, sizeof( arg ) ); - damage = atoi( arg ); - } - if( trap_Argc() > 4 ) - { - trap_Argv( 2, arg, sizeof( arg ) ); - dx = atof( arg ); - trap_Argv( 3, arg, sizeof( arg ) ); - dy = atof( arg ); - trap_Argv( 4, arg, sizeof( arg ) ); - dz = atof( arg ); - nonloc = qfalse; - } - VectorCopy( ent->s.origin, point ); - point[ 0 ] += dx; - point[ 1 ] += dy; - point[ 2 ] += dz; - G_Damage( ent, NULL, NULL, NULL, point, damage, - ( nonloc ? DAMAGE_NO_LOCDAMAGE : 0 ), MOD_TARGET_LASER ); -} - -/* -================== -G_FloodLimited - -Determine whether a user is flood limited, and adjust their flood demerits -Print them a warning message if they are over the limit -Return is time in msec until the user can speak again -================== -*/ -int G_FloodLimited( gentity_t *ent ) -{ - int deltatime, ms; - - if( g_floodMinTime.integer <= 0 ) - return 0; - - // handles !ent - if( G_admin_permission( ent, ADMF_NOCENSORFLOOD ) ) - return 0; - - deltatime = level.time - ent->client->pers.floodTime; - - ent->client->pers.floodDemerits += g_floodMinTime.integer - deltatime; - if( ent->client->pers.floodDemerits < 0 ) - ent->client->pers.floodDemerits = 0; - ent->client->pers.floodTime = level.time; - - ms = ent->client->pers.floodDemerits - g_floodMaxDemerits.integer; - if( ms <= 0 ) - return 0; - trap_SendServerCommand( ent - g_entities, va( "print \"You are flooding: " - "please wait %d second%s before trying again\n", - ( ms + 999 ) / 1000, ( ms > 1000 ) ? "s" : "" ) ); - return ms; -} - -commands_t cmds[ ] = { - { "a", CMD_MESSAGE|CMD_INTERMISSION, Cmd_AdminMessage_f }, - { "build", CMD_TEAM|CMD_LIVING, Cmd_Build_f }, - { "buy", CMD_HUMAN|CMD_LIVING, Cmd_Buy_f }, - { "callteamvote", CMD_MESSAGE|CMD_TEAM, Cmd_CallVote_f }, - { "callvote", CMD_MESSAGE, Cmd_CallVote_f }, - { "cb", 0, Cmd_Cb_f }, //NOTE: it's a command used only by cgame - { "class", CMD_TEAM, Cmd_Class_f }, - { "damage", CMD_CHEAT|CMD_LIVING, Cmd_Damage_f }, - { "deconstruct", CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, - { "destroy", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Destroy_f }, - { "follow", CMD_SPEC, Cmd_Follow_f }, - { "follownext", CMD_SPEC, Cmd_FollowCycle_f }, - { "followprev", CMD_SPEC, Cmd_FollowCycle_f }, - { "give", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Give_f }, - { "god", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_God_f }, - { "ignore", 0, Cmd_Ignore_f }, - { "itemact", CMD_HUMAN|CMD_LIVING, Cmd_ActivateItem_f }, - { "itemdeact", CMD_HUMAN|CMD_LIVING, Cmd_DeActivateItem_f }, - { "itemtoggle", CMD_HUMAN|CMD_LIVING, Cmd_ToggleItem_f }, - { "kill", CMD_TEAM|CMD_LIVING, Cmd_Kill_f }, - { "levelshot", CMD_CHEAT, Cmd_LevelShot_f }, - { "listmaps", CMD_MESSAGE|CMD_INTERMISSION, Cmd_ListMaps_f }, - { "m", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, - { "mt", CMD_MESSAGE|CMD_INTERMISSION, Cmd_PrivateMessage_f }, - { "noclip", CMD_CHEAT_TEAM, Cmd_Noclip_f }, - { "notarget", CMD_CHEAT|CMD_TEAM|CMD_LIVING, Cmd_Notarget_f }, - { "reload", CMD_HUMAN|CMD_LIVING, Cmd_Reload_f }, - { "say", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "say_area", CMD_MESSAGE|CMD_TEAM|CMD_LIVING, Cmd_SayArea_f }, - { "say_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_Say_f }, - { "score", CMD_INTERMISSION, ScoreboardMessage }, - { "sell", CMD_HUMAN|CMD_LIVING, Cmd_Sell_f }, - { "setviewpos", CMD_CHEAT_TEAM, Cmd_SetViewpos_f }, - { "team", 0, Cmd_Team_f }, - { "teamvote", CMD_TEAM, Cmd_Vote_f }, - { "test", CMD_CHEAT, Cmd_Test_f }, - { "unignore", 0, Cmd_Ignore_f }, - { "vote", 0, Cmd_Vote_f }, - { "vsay", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, - { "vsay_local", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, - { "vsay_team", CMD_MESSAGE|CMD_INTERMISSION, Cmd_VSay_f }, - { "where", 0, Cmd_Where_f } -}; -static size_t numCmds = sizeof( cmds ) / sizeof( cmds[ 0 ] ); - -/* -================= -ClientCommand -================= -*/ -void ClientCommand( int clientNum ) -{ - gentity_t *ent; - char cmd[ MAX_TOKEN_CHARS ]; - commands_t *command; - - ent = g_entities + clientNum; - if( !ent->client || ent->client->pers.connected != CON_CONNECTED ) - return; // not fully in game yet - - trap_Argv( 0, cmd, sizeof( cmd ) ); - - command = bsearch( cmd, cmds, numCmds, sizeof( cmds[ 0 ] ), cmdcmp ); - - if( !command ) - { - if( !G_admin_cmd_check( ent ) ) - trap_SendServerCommand( clientNum, - va( "print \"Unknown command %s\n\"", cmd ) ); - return; - } - - // do tests here to reduce the amount of repeated code - - if( !( command->cmdFlags & CMD_INTERMISSION ) && - ( level.intermissiontime || level.pausedTime ) ) - return; - - if( command->cmdFlags & CMD_CHEAT && !g_cheats.integer ) - { - G_TriggerMenu( clientNum, MN_CMD_CHEAT ); - return; - } - - if( command->cmdFlags & CMD_MESSAGE && ( ent->client->pers.namelog->muted || - G_FloodLimited( ent ) ) ) - return; - - if( command->cmdFlags & CMD_TEAM && - ent->client->pers.teamSelection == TEAM_NONE ) - { - G_TriggerMenu( clientNum, MN_CMD_TEAM ); - return; - } - - if( command->cmdFlags & CMD_CHEAT_TEAM && !g_cheats.integer && - ent->client->pers.teamSelection != TEAM_NONE ) - { - G_TriggerMenu( clientNum, MN_CMD_CHEAT_TEAM ); - return; - } - - if( command->cmdFlags & CMD_SPEC && - ent->client->sess.spectatorState == SPECTATOR_NOT ) - { - G_TriggerMenu( clientNum, MN_CMD_SPEC ); - return; - } - - if( command->cmdFlags & CMD_ALIEN && - ent->client->pers.teamSelection != TEAM_ALIENS ) - { - G_TriggerMenu( clientNum, MN_CMD_ALIEN ); - return; - } - - if( command->cmdFlags & CMD_HUMAN && - ent->client->pers.teamSelection != TEAM_HUMANS ) - { - G_TriggerMenu( clientNum, MN_CMD_HUMAN ); - return; - } - - if( command->cmdFlags & CMD_LIVING && - ( ent->client->ps.stats[ STAT_HEALTH ] <= 0 || - ent->client->sess.spectatorState != SPECTATOR_NOT ) ) - { - G_TriggerMenu( clientNum, MN_CMD_LIVING ); - return; - } - - command->cmdHandler( ent ); -} - -void G_ListCommands( gentity_t *ent ) -{ - int i; - char out[ MAX_STRING_CHARS ] = ""; - int len, outlen; - - outlen = 0; - - for( i = 0; i < numCmds; i++ ) - { - len = strlen( cmds[ i ].cmdName ) + 1; - if( len + outlen >= sizeof( out ) - 1 ) - { - trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); - outlen = 0; - } - - strcpy( out + outlen, va( " %s", cmds[ i ].cmdName ) ); - outlen += len; - } - - trap_SendServerCommand( ent - g_entities, va( "cmds%s\n", out ) ); - G_admin_cmdlist( ent ); -} - -void G_DecolorString( char *in, char *out, int len ) -{ - qboolean decolor = qtrue; - - len--; - - while( *in && len > 0 ) { - if( *in == DECOLOR_OFF || *in == DECOLOR_ON ) - { - decolor = ( *in == DECOLOR_ON ); - in++; - continue; - } - if( Q_IsColorString( in ) && decolor ) { - in += 2; - continue; - } - *out++ = *in++; - len--; - } - *out = '\0'; -} - -void G_UnEscapeString( char *in, char *out, int len ) -{ - len--; - - while( *in && len > 0 ) - { - if( *in >= ' ' || *in == '\n' ) - { - *out++ = *in; - len--; - } - in++; - } - *out = '\0'; -} - -void Cmd_PrivateMessage_f( gentity_t *ent ) -{ - int pids[ MAX_CLIENTS ]; - char name[ MAX_NAME_LENGTH ]; - char cmd[ 12 ]; - char text[ MAX_STRING_CHARS ]; - char *msg; - char color; - int i, pcount; - int count = 0; - qboolean teamonly = qfalse; - char recipients[ MAX_STRING_CHARS ] = ""; - - if( !g_privateMessages.integer && ent ) - { - ADMP( "Sorry, but private messages have been disabled\n" ); - return; - } - - trap_Argv( 0, cmd, sizeof( cmd ) ); - if( trap_Argc( ) < 3 ) - { - ADMP( va( "usage: %s [name|slot#] [message]\n", cmd ) ); - return; - } - - if( !Q_stricmp( cmd, "mt" ) ) - teamonly = qtrue; - - trap_Argv( 1, name, sizeof( name ) ); - msg = ConcatArgs( 2 ); - pcount = G_ClientNumbersFromString( name, pids, MAX_CLIENTS ); - - G_CensorString( text, msg, sizeof( text ), ent ); - - // send the message - for( i = 0; i < pcount; i++ ) - { - if( G_SayTo( ent, &g_entities[ pids[ i ] ], - teamonly ? SAY_TPRIVMSG : SAY_PRIVMSG, text ) ) - { - count++; - Q_strcat( recipients, sizeof( recipients ), va( "%s" S_COLOR_WHITE ", ", - level.clients[ pids[ i ] ].pers.netname ) ); - } - } - - // report the results - color = teamonly ? COLOR_CYAN : COLOR_YELLOW; - - if( !count ) - ADMP( va( "^3No player matching ^7\'%s^7\' ^3to send message to.\n", - name ) ); - else - { - ADMP( va( "^%cPrivate message: ^7%s\n", color, text ) ); - // remove trailing ", " - recipients[ strlen( recipients ) - 2 ] = '\0'; - ADMP( va( "^%csent to %i player%s: " S_COLOR_WHITE "%s\n", color, count, - count == 1 ? "" : "s", recipients ) ); - - G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\" \"%s\": ^%c%s\n", - ( teamonly ) ? "TPrivMsg" : "PrivMsg", - ( ent ) ? ent - g_entities : -1, - ( ent ) ? ent->client->pers.netname : "console", - name, color, msg ); - } -} - -/* -================= -Cmd_AdminMessage_f - -Send a message to all active admins -================= -*/ -void Cmd_AdminMessage_f( gentity_t *ent ) -{ - // Check permissions and add the appropriate user [prefix] - if( !G_admin_permission( ent, ADMF_ADMINCHAT ) ) - { - if( !g_publicAdminMessages.integer ) - { - ADMP( "Sorry, but use of /a by non-admins has been disabled.\n" ); - return; - } - else - { - ADMP( "Your message has been sent to any available admins " - "and to the server logs.\n" ); - } - } - - if( trap_Argc( ) < 2 ) - { - ADMP( "usage: a [message]\n" ); - return; - } - - G_AdminMessage( ent, ConcatArgs( 1 ) ); -} - diff --git a/src/game/g_combat.c.orig b/src/game/g_combat.c.orig deleted file mode 100644 index f4e89ee..0000000 --- a/src/game/g_combat.c.orig +++ /dev/null @@ -1,1468 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2009 Darklegion Development - -This file is part of Tremulous. - -Tremulous is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Tremulous is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "g_local.h" - -damageRegion_t g_damageRegions[ PCL_NUM_CLASSES ][ MAX_DAMAGE_REGIONS ]; -int g_numDamageRegions[ PCL_NUM_CLASSES ]; - -damageRegion_t g_armourRegions[ UP_NUM_UPGRADES ][ MAX_DAMAGE_REGIONS ]; -int g_numArmourRegions[ UP_NUM_UPGRADES ]; - -/* -============ -AddScore - -Adds score to the client -============ -*/ -void AddScore( gentity_t *ent, int score ) -{ - if( !ent->client ) - return; - - // make alien and human scores equivalent - if ( ent->client->pers.teamSelection == TEAM_ALIENS ) - { - score = rint( ((float)score) / 2.0f ); - } - - // scale values down to fit the scoreboard better - score = rint( ((float)score) / 50.0f ); - - ent->client->ps.persistant[ PERS_SCORE ] += score; - CalculateRanks( ); -} - -/* -================== -LookAtKiller -================== -*/ -void LookAtKiller( gentity_t *self, gentity_t *inflictor, gentity_t *attacker ) -{ - - if ( attacker && attacker != self ) - self->client->ps.stats[ STAT_VIEWLOCK ] = attacker - g_entities; - else if( inflictor && inflictor != self ) - self->client->ps.stats[ STAT_VIEWLOCK ] = inflictor - g_entities; - else - self->client->ps.stats[ STAT_VIEWLOCK ] = self - g_entities; -} - -// these are just for logging, the client prints its own messages -char *modNames[ ] = -{ - "MOD_UNKNOWN", - "MOD_SHOTGUN", - "MOD_BLASTER", - "MOD_PAINSAW", - "MOD_MACHINEGUN", - "MOD_CHAINGUN", - "MOD_PRIFLE", - "MOD_MDRIVER", - "MOD_LASGUN", - "MOD_LCANNON", - "MOD_LCANNON_SPLASH", - "MOD_FLAMER", - "MOD_FLAMER_SPLASH", - "MOD_GRENADE", - "MOD_WATER", - "MOD_SLIME", - "MOD_LAVA", - "MOD_CRUSH", - "MOD_TELEFRAG", - "MOD_FALLING", - "MOD_SUICIDE", - "MOD_TARGET_LASER", - "MOD_TRIGGER_HURT", - - "MOD_ABUILDER_CLAW", - "MOD_LEVEL0_BITE", - "MOD_LEVEL1_CLAW", - "MOD_LEVEL1_PCLOUD", - "MOD_LEVEL3_CLAW", - "MOD_LEVEL3_POUNCE", - "MOD_LEVEL3_BOUNCEBALL", - "MOD_LEVEL2_CLAW", - "MOD_LEVEL2_ZAP", - "MOD_LEVEL4_CLAW", - "MOD_LEVEL4_TRAMPLE", - "MOD_LEVEL4_CRUSH", - - "MOD_SLOWBLOB", - "MOD_POISON", - "MOD_SWARM", - - "MOD_HSPAWN", - "MOD_TESLAGEN", - "MOD_MGTURRET", - "MOD_REACTOR", - - "MOD_ASPAWN", - "MOD_ATUBE", - "MOD_OVERMIND", - "MOD_DECONSTRUCT", - "MOD_REPLACE", - "MOD_NOCREEP" -}; - -/* -================== -G_RewardAttackers - -Function to distribute rewards to entities that killed this one. -Returns the total damage dealt. -================== -*/ -float G_RewardAttackers( gentity_t *self ) -{ - float value, totalDamage = 0; - int team, i, maxHealth = 0; - int alienCredits = 0, humanCredits = 0; - gentity_t *player; - - // Total up all the damage done by non-teammates - for( i = 0; i < level.maxclients; i++ ) - { - player = g_entities + i; - - if( !OnSameTeam( self, player ) || - self->buildableTeam != player->client->ps.stats[ STAT_TEAM ] ) - totalDamage += (float)self->credits[ i ]; - } - - if( totalDamage <= 0.0f ) - return 0.0f; - - // Only give credits for killing players and buildables - if( self->client ) - { - value = BG_GetValueOfPlayer( &self->client->ps ); - team = self->client->pers.teamSelection; - maxHealth = self->client->ps.stats[ STAT_MAX_HEALTH ]; - } - else if( self->s.eType == ET_BUILDABLE ) - { - value = BG_Buildable( self->s.modelindex, self->cuboidSize )->value; - - // only give partial credits for a buildable not yet completed - if( !self->spawned ) - { - value *= (float)( level.time - self->buildTime ) / - BG_Buildable( self->s.modelindex, self->cuboidSize )->buildTime; - } - - team = self->buildableTeam; - maxHealth = BG_Buildable( self->s.modelindex, self->cuboidSize )->health; - } - else - return totalDamage; - - // Give credits and empty the array - for( i = 0; i < level.maxclients; i++ ) - { - int stageValue = value * self->credits[ i ] / totalDamage; - player = g_entities + i; - - if( player->client->pers.teamSelection != team ) - { - if( totalDamage < maxHealth ) - stageValue *= totalDamage / maxHealth; - - if( !self->credits[ i ] || player->client->ps.stats[ STAT_TEAM ] == team ) - continue; - - AddScore( player, stageValue ); - - // killing buildables earns score, but not credits - if( self->s.eType != ET_BUILDABLE ) - { - G_AddCreditToClient( player->client, stageValue, qtrue ); - - // add to stage counters - if( player->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - alienCredits += stageValue; - else if( player->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - humanCredits += stageValue; - } - } - self->credits[ i ] = 0; - } - - if( alienCredits ) - { - trap_Cvar_Set( "g_alienCredits", - va( "%d", g_alienCredits.integer + alienCredits ) ); - trap_Cvar_Update( &g_alienCredits ); - } - if( humanCredits ) - { - trap_Cvar_Set( "g_humanCredits", - va( "%d", g_humanCredits.integer + humanCredits ) ); - trap_Cvar_Update( &g_humanCredits ); - } - - return totalDamage; -} - -/* -================== -player_die -================== -*/ -void player_die( gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int meansOfDeath ) -{ - gentity_t *ent, *ent2; - int anim; - int killer; - int i; - char *killerName, *obit; - vec3_t dir; - - if( self->client->ps.pm_type == PM_DEAD ) - return; - - if( level.intermissiontime ) - return; - - self->client->ps.pm_type = PM_DEAD; - self->suicideTime = 0; - - if( attacker ) - { - killer = attacker->s.number; - - if( attacker->client ) - killerName = attacker->client->pers.netname; - else - killerName = ""; - } - else - { - killer = ENTITYNUM_WORLD; - killerName = ""; - } - - if( meansOfDeath < 0 || meansOfDeath >= sizeof( modNames ) / sizeof( modNames[0] ) ) - // fall back on the number - obit = va( "%d", meansOfDeath ); - else - obit = modNames[ meansOfDeath ]; - - G_LogPrintf( "Die: %d %d %s: %s" S_COLOR_WHITE " killed %s\n", - killer, - self - g_entities, - obit, - killerName, - self->client->pers.netname ); - - // deactivate all upgrades - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - BG_DeactivateUpgrade( i, self->client->ps.stats ); - - // kill all player's buildables if they havent spawned yet - // this should eliminate build timer hacks for ever - dir[0] = dir[1] = 0.0f; - dir[2] = 1.0f; - - for( i = MAX_CLIENTS, ent = g_entities + i; i < level.num_entities; i++, ent++ ) - { - if( ent->s.eType != ET_BUILDABLE ) - continue; - - if( ent == self ) - continue; - - if( ent->spawned ) - continue; - - if( ent->builtBy != self->client->ps.clientNum ) - continue; - - G_Damage( ent, self, attacker, dir, dir, ent->health, 0, MOD_DECONSTRUCT ); - } - - // broadcast the death event to everyone - ent = G_TempEntity( self->r.currentOrigin, EV_OBITUARY ); - ent->s.eventParm = meansOfDeath; - ent->s.otherEntityNum = self->s.number; - ent->s.otherEntityNum2 = killer; - ent->r.svFlags = SVF_BROADCAST; // send to everyone - - self->enemy = attacker; - self->client->ps.persistant[ PERS_KILLED ]++; - - if( attacker && attacker->client ) - { - attacker->client->lastkilled_client = self->s.number; - - if( ( attacker == self || OnSameTeam( self, attacker ) ) && meansOfDeath != MOD_HSPAWN ) - { - //punish team kills and suicides - if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - { - G_AddCreditToClient( attacker->client, -ALIEN_TK_SUICIDE_PENALTY, qtrue ); - AddScore( attacker, -ALIEN_TK_SUICIDE_PENALTY ); - } - else if( attacker->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - { - G_AddCreditToClient( attacker->client, -HUMAN_TK_SUICIDE_PENALTY, qtrue ); - AddScore( attacker, -HUMAN_TK_SUICIDE_PENALTY ); - } - } - } - else if( attacker->s.eType != ET_BUILDABLE ) - { - if( self->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) - AddScore( self, -ALIEN_TK_SUICIDE_PENALTY ); - else if( self->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) - AddScore( self, -HUMAN_TK_SUICIDE_PENALTY ); - } - - // give credits for killing this player - G_RewardAttackers( self ); - - ScoreboardMessage( self ); // show scores - - // send updated scores to any clients that are following this one, - // or they would get stale scoreboards - for( i = 0 ; i < level.maxclients ; i++ ) - { - gclient_t *client; - - client = &level.clients[ i ]; - if( client->pers.connected != CON_CONNECTED ) - continue; - - if( client->sess.spectatorState == SPECTATOR_NOT ) - continue; - - if( client->sess.spectatorClient == self->s.number ) - ScoreboardMessage( g_entities + i ); - } - - VectorCopy( self->s.origin, self->client->pers.lastDeathLocation ); - - self->takedamage = qfalse; // can still be gibbed - - self->s.weapon = WP_NONE; - self->r.contents = CONTENTS_CORPSE; - - self->s.angles[ PITCH ] = 0; - self->s.angles[ ROLL ] = 0; - self->s.angles[ YAW ] = self->s.apos.trBase[ YAW ]; - LookAtKiller( self, inflictor, attacker ); - - VectorCopy( self->s.angles, self->client->ps.viewangles ); - - self->s.loopSound = 0; - - self->r.maxs[ 2 ] = -8; - - // don't allow respawn until the death anim is done - // g_forcerespawn may force spawning at some later time - self->client->respawnTime = level.time + 1700; - - // clear misc - memset( self->client->ps.misc, 0, sizeof( self->client->ps.misc ) ); - - { - // normal death - static int i; - - if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) - { - switch( i ) - { - case 0: - anim = BOTH_DEATH1; - break; - case 1: - anim = BOTH_DEATH2; - break; - case 2: - default: - anim = BOTH_DEATH3; - break; - } - } - else - { - switch( i ) - { - case 0: - anim = NSPA_DEATH1; - break; - case 1: - anim = NSPA_DEATH2; - break; - case 2: - default: - anim = NSPA_DEATH3; - break; - } - } - - self->client->ps.legsAnim = - ( ( self->client->ps.legsAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; - - if( !( self->client->ps.persistant[ PERS_STATE ] & PS_NONSEGMODEL ) ) - { - self->client->ps.torsoAnim = - ( ( self->client->ps.torsoAnim & ANIM_TOGGLEBIT ) ^ ANIM_TOGGLEBIT ) | anim; - } - - // use own entityid if killed by non-client to prevent uint8_t overflow - G_AddEvent( self, EV_DEATH1 + i, - ( killer < MAX_CLIENTS ) ? killer : self - g_entities ); - - // globally cycle through the different death animations - i = ( i + 1 ) % 3; - } - - trap_LinkEntity( self ); -} - -/* -=============== -G_ParseDmgScript -=============== -*/ -static int G_ParseDmgScript( damageRegion_t *regions, char *buf ) -{ - char *token; - float angleSpan, heightSpan; - int count; - - for( count = 0; ; count++ ) - { - token = COM_Parse( &buf ); - if( !token[ 0 ] ) - break; - - if( strcmp( token, "{" ) ) - { - COM_ParseError( "Missing {" ); - break; - } - - if( count >= MAX_DAMAGE_REGIONS ) - { - COM_ParseError( "Max damage regions exceeded" ); - break; - } - - // defaults - regions[ count ].name[ 0 ] = '\0'; - regions[ count ].minHeight = 0.0f; - regions[ count ].maxHeight = 1.0f; - regions[ count ].minAngle = 0.0f; - regions[ count ].maxAngle = 360.0f; - regions[ count ].modifier = 1.0f; - regions[ count ].crouch = qfalse; - - while( 1 ) - { - token = COM_ParseExt( &buf, qtrue ); - - if( !token[ 0 ] ) - { - COM_ParseError( "Unexpected end of file" ); - break; - } - - if( !Q_stricmp( token, "}" ) ) - { - break; - } - else if( !strcmp( token, "name" ) ) - { - token = COM_ParseExt( &buf, qfalse ); - if( token[ 0 ] ) - Q_strncpyz( regions[ count ].name, token, - sizeof( regions[ count ].name ) ); - } - else if( !strcmp( token, "minHeight" ) ) - { - token = COM_ParseExt( &buf, qfalse ); - if( !token[ 0 ] ) - strcpy( token, "0" ); - regions[ count ].minHeight = atof( token ); - } - else if( !strcmp( token, "maxHeight" ) ) - { - token = COM_ParseExt( &buf, qfalse ); - if( !token[ 0 ] ) - strcpy( token, "100" ); - regions[ count ].maxHeight = atof( token ); - } - else if( !strcmp( token, "minAngle" ) ) - { - token = COM_ParseExt( &buf, qfalse ); - if( !token[ 0 ] ) - strcpy( token, "0" ); - regions[ count ].minAngle = atoi( token ); - } - else if( !strcmp( token, "maxAngle" ) ) - { - token = COM_ParseExt( &buf, qfalse ); - if( !token[ 0 ] ) - strcpy( token, "360" ); - regions[ count ].maxAngle = atoi( token ); - } - else if( !strcmp( token, "modifier" ) ) - { - token = COM_ParseExt( &buf, qfalse ); - if( !token[ 0 ] ) - strcpy( token, "1.0" ); - regions[ count ].modifier = atof( token ); - } - else if( !strcmp( token, "crouch" ) ) - { - regions[ count ].crouch = qtrue; - } - else - { - COM_ParseWarning("Unknown token \"%s\"", token); - } - } - - // Angle portion covered - angleSpan = regions[ count ].maxAngle - regions[ count ].minAngle; - if( angleSpan < 0.0f ) - angleSpan += 360.0f; - angleSpan /= 360.0f; - - // Height portion covered - heightSpan = regions[ count ].maxHeight - regions[ count ].minHeight; - if( heightSpan < 0.0f ) - heightSpan = -heightSpan; - if( heightSpan > 1.0f ) - heightSpan = 1.0f; - - regions[ count ].area = angleSpan * heightSpan; - if( !regions[ count ].area ) - regions[ count ].area = 0.00001f; - } - - return count; -} - -/* -============ -GetRegionDamageModifier -============ -*/ -static float GetRegionDamageModifier( gentity_t *targ, int class, int piece ) -{ - damageRegion_t *regions, *overlap; - float modifier = 0.0f, areaSum = 0.0f; - int j, i; - qboolean crouch; - - crouch = targ->client->ps.pm_flags & PMF_DUCKED; - overlap = &g_damageRegions[ class ][ piece ]; - - if( g_debugDamage.integer > 2 ) - G_Printf( "GetRegionDamageModifier():\n" - ". bodyRegion = [%d %d %f %f] (%s)\n" - ". modifier = %f\n", - overlap->minAngle, overlap->maxAngle, - overlap->minHeight, overlap->maxHeight, - overlap->name, overlap->modifier ); - - // Find the armour layer modifier, assuming that none of the armour regions - // overlap and that any areas that are not covered have a modifier of 1.0 - for( j = UP_NONE + 1; j < UP_NUM_UPGRADES; j++ ) - { - if( !BG_InventoryContainsUpgrade( j, targ->client->ps.stats ) || - !g_numArmourRegions[ j ] ) - continue; - regions = g_armourRegions[ j ]; - - for( i = 0; i < g_numArmourRegions[ j ]; i++ ) - { - float overlapMaxA, regionMinA, regionMaxA, angleSpan, heightSpan, area; - - if( regions[ i ].crouch != crouch ) - continue; - - // Convert overlap angle to 0 to max - overlapMaxA = overlap->maxAngle - overlap->minAngle; - if( overlapMaxA < 0.0f ) - overlapMaxA += 360.0f; - - // Convert region angles to match overlap - regionMinA = regions[ i ].minAngle - overlap->minAngle; - if( regionMinA < 0.0f ) - regionMinA += 360.0f; - regionMaxA = regions[ i ].maxAngle - overlap->minAngle; - if( regionMaxA < 0.0f ) - regionMaxA += 360.0f; - - // Overlapping Angle portion - if( regionMinA <= regionMaxA ) - { - angleSpan = 0.0f; - if( regionMinA < overlapMaxA ) - { - if( regionMaxA > overlapMaxA ) - regionMaxA = overlapMaxA; - angleSpan = regionMaxA - regionMinA; - } - } - else - { - if( regionMaxA > overlapMaxA ) - regionMaxA = overlapMaxA; - angleSpan = regionMaxA; - if( regionMinA < overlapMaxA ) - angleSpan += overlapMaxA - regionMinA; - } - angleSpan /= 360.0f; - - // Overlapping height portion - heightSpan = MIN( overlap->maxHeight, regions[ i ].maxHeight ) - - MAX( overlap->minHeight, regions[ i ].minHeight ); - if( heightSpan < 0.0f ) - heightSpan = 0.0f; - if( heightSpan > 1.0f ) - heightSpan = 1.0f; - - if( g_debugDamage.integer > 2 ) - G_Printf( ". armourRegion = [%d %d %f %f] (%s)\n" - ". . modifier = %f\n" - ". . angleSpan = %f\n" - ". . heightSpan = %f\n", - regions[ i ].minAngle, regions[ i ].maxAngle, - regions[ i ].minHeight, regions[ i ].maxHeight, - regions[ i ].name, regions[ i ].modifier, - angleSpan, heightSpan ); - - areaSum += area = angleSpan * heightSpan; - modifier += regions[ i ].modifier * area; - } - } - - if( g_debugDamage.integer > 2 ) - G_Printf( ". areaSum = %f\n" - ". armourModifier = %f\n", areaSum, modifier ); - - return overlap->modifier * ( overlap->area + modifier - areaSum ); -} - -/* -============ -GetNonLocDamageModifier -============ -*/ -static float GetNonLocDamageModifier( gentity_t *targ, int class ) -{ - float modifier = 0.0f, area = 0.0f, scale = 0.0f; - int i; - qboolean crouch; - - // For every body region, use stretch-armor formula to apply armour modifier - // for any overlapping area that armour shares with the body region - crouch = targ->client->ps.pm_flags & PMF_DUCKED; - for( i = 0; i < g_numDamageRegions[ class ]; i++ ) - { - damageRegion_t *region; - - region = &g_damageRegions[ class ][ i ]; - - if( region->crouch != crouch ) - continue; - - modifier += GetRegionDamageModifier( targ, class, i ); - - scale += region->modifier * region->area; - area += region->area; - - } - - modifier = !scale ? 1.0f : 1.0f + ( modifier / scale - 1.0f ) * area; - - if( g_debugDamage.integer > 1 ) - G_Printf( "GetNonLocDamageModifier() modifier:%f, area:%f, scale:%f\n", - modifier, area, scale ); - - return modifier; -} - -/* -============ -GetPointDamageModifier - -Returns the damage region given an angle and a height proportion -============ -*/ -static float GetPointDamageModifier( gentity_t *targ, damageRegion_t *regions, - int len, float angle, float height ) -{ - float modifier = 1.0f; - int i; - - for( i = 0; i < len; i++ ) - { - if( regions[ i ].crouch != ( targ->client->ps.pm_flags & PMF_DUCKED ) ) - continue; - - // Angle must be within range - if( ( regions[ i ].minAngle <= regions[ i ].maxAngle && - ( angle < regions[ i ].minAngle || - angle > regions[ i ].maxAngle ) ) || - ( regions[ i ].minAngle > regions[ i ].maxAngle && - angle > regions[ i ].maxAngle && angle < regions[ i ].minAngle ) ) - continue; - - // Height must be within range - if( height < regions[ i ].minHeight || height > regions[ i ].maxHeight ) - continue; - - modifier *= regions[ i ].modifier; - } - - if( g_debugDamage.integer ) - G_Printf( "GetDamageRegionModifier(angle = %f, height = %f): %f\n", - angle, height, modifier ); - - return modifier; -} - -/* -============ -G_CalcDamageModifier -============ -*/ -static float G_CalcDamageModifier( vec3_t point, gentity_t *targ, gentity_t *attacker, int class, int dflags ) -{ - vec3_t targOrigin, bulletPath, bulletAngle, pMINUSfloor, floor, normal; - float clientHeight, hitRelative, hitRatio, modifier; - int hitRotation, i; - - if( point == NULL ) - return 1.0f; - - // Don't need to calculate angles and height for non-locational damage - if( dflags & DAMAGE_NO_LOCDAMAGE ) - return GetNonLocDamageModifier( targ, class ); - - // Get the point location relative to the floor under the target - if( g_unlagged.integer && targ->client && targ->client->unlaggedCalc.used ) - VectorCopy( targ->client->unlaggedCalc.origin, targOrigin ); - else - VectorCopy( targ->r.currentOrigin, targOrigin ); - - BG_GetClientNormal( &targ->client->ps, normal ); - VectorMA( targOrigin, targ->r.mins[ 2 ], normal, floor ); - VectorSubtract( point, floor, pMINUSfloor ); - - // Get the proportion of the target height where the hit landed - clientHeight = targ->r.maxs[ 2 ] - targ->r.mins[ 2 ]; - - if( !clientHeight ) - clientHeight = 1.0f; - - hitRelative = DotProduct( normal, pMINUSfloor ) / VectorLength( normal ); - - if( hitRelative < 0.0f ) - hitRelative = 0.0f; - - if( hitRelative > clientHeight ) - hitRelative = clientHeight; - - hitRatio = hitRelative / clientHeight; - - // Get the yaw of the attack relative to the target's view yaw - VectorSubtract( point, targOrigin, bulletPath ); - vectoangles( bulletPath, bulletAngle ); - - hitRotation = AngleNormalize360( targ->client->ps.viewangles[ YAW ] - - bulletAngle[ YAW ] ); - - // Get modifiers from the target's damage regions - modifier = GetPointDamageModifier( targ, g_damageRegions[ class ], - g_numDamageRegions[ class ], - hitRotation, hitRatio ); - - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - if( BG_InventoryContainsUpgrade( i, targ->client->ps.stats ) ) - { - modifier *= GetPointDamageModifier( targ, g_armourRegions[ i ], - g_numArmourRegions[ i ], - hitRotation, hitRatio ); - } - } - - return modifier; -} - - -/* -============ -G_InitDamageLocations -============ -*/ -void G_InitDamageLocations( void ) -{ - char *modelName; - char filename[ MAX_QPATH ]; - int i; - int len; - fileHandle_t fileHandle; - char buffer[ MAX_DAMAGE_REGION_TEXT ]; - - for( i = PCL_NONE + 1; i < PCL_NUM_CLASSES; i++ ) - { - modelName = BG_ClassConfig( i )->modelName; - Com_sprintf( filename, sizeof( filename ), - "models/players/%s/locdamage.cfg", modelName ); - - len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); - if ( !fileHandle ) - { - G_Printf( S_COLOR_RED "file not found: %s\n", filename ); - continue; - } - - if( len >= MAX_DAMAGE_REGION_TEXT ) - { - G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", - filename, len, MAX_DAMAGE_REGION_TEXT ); - trap_FS_FCloseFile( fileHandle ); - continue; - } - - COM_BeginParseSession( filename ); - - trap_FS_Read( buffer, len, fileHandle ); - buffer[len] = 0; - trap_FS_FCloseFile( fileHandle ); - - g_numDamageRegions[ i ] = G_ParseDmgScript( g_damageRegions[ i ], buffer ); - } - - for( i = UP_NONE + 1; i < UP_NUM_UPGRADES; i++ ) - { - modelName = BG_Upgrade( i )->name; - Com_sprintf( filename, sizeof( filename ), "armour/%s.armour", modelName ); - - len = trap_FS_FOpenFile( filename, &fileHandle, FS_READ ); - - //no file - no parsage - if ( !fileHandle ) - continue; - - if( len >= MAX_DAMAGE_REGION_TEXT ) - { - G_Printf( S_COLOR_RED "file too large: %s is %i, max allowed is %i", - filename, len, MAX_DAMAGE_REGION_TEXT ); - trap_FS_FCloseFile( fileHandle ); - continue; - } - - COM_BeginParseSession( filename ); - - trap_FS_Read( buffer, len, fileHandle ); - buffer[len] = 0; - trap_FS_FCloseFile( fileHandle ); - - g_numArmourRegions[ i ] = G_ParseDmgScript( g_armourRegions[ i ], buffer ); - } -} - - -/* -============ -T_Damage - -targ entity that is being damaged -inflictor entity that is causing the damage -attacker entity that caused the inflictor to damage targ - example: targ=monster, inflictor=rocket, attacker=player - -dir direction of the attack for knockback -point point at which the damage is being inflicted, used for headshots -damage amount of damage being inflicted -knockback force to be applied against targ as a result of the damage - -inflictor, attacker, dir, and point can be NULL for environmental effects - -dflags these flags are used to control how T_Damage works - DAMAGE_RADIUS damage was indirect (from a nearby explosion) - DAMAGE_NO_ARMOR armor does not protect from this damage - DAMAGE_NO_KNOCKBACK do not affect velocity, just view angles - DAMAGE_NO_PROTECTION kills godmode, armor, everything -============ -*/ - -// team is the team that is immune to this damage -void G_SelectiveDamage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, - vec3_t dir, vec3_t point, int damage, int dflags, int mod, int team ) -{ - if( targ->client && ( team != targ->client->ps.stats[ STAT_TEAM ] ) ) - G_Damage( targ, inflictor, attacker, dir, point, damage, dflags, mod ); -} - -void G_Damage( gentity_t *targ, gentity_t *inflictor, gentity_t *attacker, - vec3_t dir, vec3_t point, int damage, int dflags, int mod ) -{ - gclient_t *client; - int take; - int asave = 0; - int knockback; - - // Can't deal damage sometimes - if( !targ->takedamage || targ->health <= 0 || level.intermissionQueued ) - return; - - if( !inflictor ) - inflictor = &g_entities[ ENTITYNUM_WORLD ]; - - if( !attacker ) - attacker = &g_entities[ ENTITYNUM_WORLD ]; - - // shootable doors / buttons don't actually have any health - if( targ->s.eType == ET_MOVER ) - { - if( targ->use && ( targ->moverState == MOVER_POS1 || - targ->moverState == ROTATOR_POS1 ) ) - targ->use( targ, inflictor, attacker ); - - return; - } - - client = targ->client; - if( client && client->noclip ) - return; - - if( !dir ) - dflags |= DAMAGE_NO_KNOCKBACK; - else - VectorNormalize( dir ); - - knockback = damage; - - if( inflictor->s.weapon != WP_NONE ) - { - knockback = (int)( (float)knockback * - BG_Weapon( inflictor->s.weapon )->knockbackScale ); - } - - if( targ->client ) - { - knockback = (int)( (float)knockback * - BG_Class( targ->client->ps.stats[ STAT_CLASS ] )->knockbackScale ); - } - - // Too much knockback from falling really far makes you "bounce" and - // looks silly. However, none at all also looks bad. Cap it. - if( mod == MOD_FALLING && knockback > 50 ) - knockback = 50; - - if( knockback > 200 ) - knockback = 200; - - if( targ->flags & FL_NO_KNOCKBACK ) - knockback = 0; - - if( dflags & DAMAGE_NO_KNOCKBACK ) - knockback = 0; - - // figure momentum add, even if the damage won't be taken - if( knockback && targ->client ) - { - vec3_t kvel; - float mass; - - mass = 200; - - VectorScale( dir, g_knockback.value * (float)knockback / mass, kvel ); - VectorAdd( targ->client->ps.velocity, kvel, targ->client->ps.velocity ); - - // set the timer so that the other client can't cancel - // out the movement immediately - if( !targ->client->ps.pm_time ) - { - int t; - - t = knockback * 2; - if( t < 50 ) - t = 50; - - if( t > 200 ) - t = 200; - - targ->client->ps.pm_time = t; - targ->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - } - } - - // don't do friendly fire on movement attacks - if( ( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || - mod == MOD_LEVEL4_CRUSH ) && - targ->s.eType == ET_BUILDABLE && targ->buildableTeam == TEAM_ALIENS ) - { - return; - } - - // check for completely getting out of the damage - if( !( dflags & DAMAGE_NO_PROTECTION ) ) - { - - // if TF_NO_FRIENDLY_FIRE is set, don't do damage to the target - // if the attacker was on the same team - if( targ != attacker && OnSameTeam( targ, attacker ) ) - { - // don't do friendly fire on movement attacks - if( mod == MOD_LEVEL4_TRAMPLE || mod == MOD_LEVEL3_POUNCE || - mod == MOD_LEVEL4_CRUSH ) - return; - - // if dretchpunt is enabled and this is a dretch, do dretchpunt instead of damage - if( g_dretchPunt.integer && - targ->client->ps.stats[ STAT_CLASS ] == PCL_ALIEN_LEVEL0 ) - { - vec3_t dir, push; - - VectorSubtract( targ->r.currentOrigin, attacker->r.currentOrigin, dir ); - VectorNormalizeFast( dir ); - VectorScale( dir, ( damage * 10.0f ), push ); - push[2] = 64.0f; - VectorAdd( targ->client->ps.velocity, push, targ->client->ps.velocity ); - return; - } - - // check if friendly fire has been disabled - if( !g_friendlyFire.integer ) - { - return; - } - } - - if( targ->s.eType == ET_BUILDABLE && attacker->client && - mod != MOD_DECONSTRUCT && mod != MOD_SUICIDE && - mod != MOD_REPLACE && mod != MOD_NOCREEP ) - { - if( targ->buildableTeam == attacker->client->pers.teamSelection && - !g_friendlyBuildableFire.integer ) - { - return; - } - - // base is under attack warning if DCC'd - if( targ->buildableTeam == TEAM_HUMANS && G_FindDCC( targ ) && - level.time > level.humanBaseAttackTimer ) - { - level.humanBaseAttackTimer = level.time + DC_ATTACK_PERIOD; - G_BroadcastEvent( EV_DCC_ATTACK, 0 ); - } - } - - // check for godmode - if ( targ->flags & FL_GODMODE ) - return; - } - - // add to the attacker's hit counter - if( attacker->client && targ != attacker && targ->health > 0 - && targ->s.eType != ET_MISSILE - && targ->s.eType != ET_GENERAL ) - { - if( OnSameTeam( targ, attacker ) ) - attacker->client->ps.persistant[ PERS_HITS ]--; - else - attacker->client->ps.persistant[ PERS_HITS ]++; - } - - take = damage; - - // add to the damage inflicted on a player this frame - // the total will be turned into screen blends and view angle kicks - // at the end of the frame - if( client ) - { - if( attacker ) - client->ps.persistant[ PERS_ATTACKER ] = attacker->s.number; - else - client->ps.persistant[ PERS_ATTACKER ] = ENTITYNUM_WORLD; - - client->damage_armor += asave; - client->damage_blood += take; - client->damage_knockback += knockback; - - if( dir ) - { - VectorCopy ( dir, client->damage_from ); - client->damage_fromWorld = qfalse; - } - else - { - VectorCopy ( targ->r.currentOrigin, client->damage_from ); - client->damage_fromWorld = qtrue; - } - - // set the last client who damaged the target - targ->client->lasthurt_client = attacker->s.number; - targ->client->lasthurt_mod = mod; - take = (int)( take * G_CalcDamageModifier( point, targ, attacker, - client->ps.stats[ STAT_CLASS ], - dflags ) + 0.5f ); - - //if boosted poison every attack - if( attacker->client && attacker->client->ps.stats[ STAT_STATE ] & SS_BOOSTED ) - { - if( (targ->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ) && - mod != MOD_LEVEL2_ZAP && mod != MOD_POISON && - mod != MOD_LEVEL1_PCLOUD && mod != MOD_HSPAWN && - mod != MOD_ASPAWN && targ->client->poisonImmunityTime < level.time ) - { - targ->client->ps.stats[ STAT_STATE ] |= SS_POISONED; - targ->client->lastPoisonTime = level.time; - targ->client->lastPoisonClient = attacker; - } - } - } - - if( take < 1 ) - take = 1; - - if( g_debugDamage.integer ) - { - G_Printf( "%i: client:%i health:%i damage:%i armor:%i\n", level.time, targ->s.number, - targ->health, take, asave ); - } - - // do the damage - if( take ) - { - targ->health = targ->health - take; - - if( targ->client ) - targ->client->ps.stats[ STAT_HEALTH ] = targ->health; - - targ->lastDamageTime = level.time; - targ->nextRegenTime = level.time + ALIEN_REGEN_DAMAGE_TIME; - - // add to the attackers "account" on the target - if( attacker->client && attacker != targ ) - targ->credits[ attacker->client->ps.clientNum ] += take; - - if( targ->health <= 0 ) - { - if( client ) - targ->flags |= FL_NO_KNOCKBACK; - - if( targ->health < -999 ) - targ->health = -999; - - targ->enemy = attacker; - targ->die( targ, inflictor, attacker, take, mod ); - return; - } - else if( targ->pain ) - { - //if(targ->s.eType==ET_BUILDABLE&&BG_Buildable(targ->s.modelindex,NULL)->cuboid) - // VectorCopy(point,targ->s.angles2); - targ->pain( targ, attacker, take ); - } - } -} - - -/* -============ -CanDamage - -Returns qtrue if the inflictor can directly damage the target. Used for -explosions and melee attacks. -============ -*/ -qboolean CanDamage( gentity_t *targ, vec3_t origin ) -{ - vec3_t dest; - trace_t tr; - vec3_t midpoint; - - // use the midpoint of the bounds instead of the origin, because - // bmodels may have their origin is 0,0,0 - VectorAdd( targ->r.absmin, targ->r.absmax, midpoint ); - VectorScale( midpoint, 0.5, midpoint ); - - VectorCopy( midpoint, dest ); - trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); - if( tr.fraction == 1.0 || tr.entityNum == targ->s.number ) - return qtrue; - - // this should probably check in the plane of projection, - // rather than in world coordinate, and also include Z - VectorCopy( midpoint, dest ); - dest[ 0 ] += 15.0; - dest[ 1 ] += 15.0; - trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); - if( tr.fraction == 1.0 ) - return qtrue; - - VectorCopy( midpoint, dest ); - dest[ 0 ] += 15.0; - dest[ 1 ] -= 15.0; - trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); - if( tr.fraction == 1.0 ) - return qtrue; - - VectorCopy( midpoint, dest ); - dest[ 0 ] -= 15.0; - dest[ 1 ] += 15.0; - trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); - if( tr.fraction == 1.0 ) - return qtrue; - - VectorCopy( midpoint, dest ); - dest[ 0 ] -= 15.0; - dest[ 1 ] -= 15.0; - trap_Trace( &tr, origin, vec3_origin, vec3_origin, dest, ENTITYNUM_NONE, MASK_SOLID ); - if( tr.fraction == 1.0 ) - return qtrue; - - return qfalse; -} - -/* -============ -G_SelectiveRadiusDamage -============ -*/ -qboolean G_SelectiveRadiusDamage( vec3_t origin, gentity_t *attacker, float damage, - float radius, gentity_t *ignore, int mod, int team ) -{ - float points, dist; - gentity_t *ent; - int entityList[ MAX_GENTITIES ]; - int numListedEntities; - vec3_t mins, maxs; - vec3_t v; - vec3_t dir; - int i, e; - qboolean hitClient = qfalse; - - if( radius < 1 ) - radius = 1; - - for( i = 0; i < 3; i++ ) - { - mins[ i ] = origin[ i ] - radius; - maxs[ i ] = origin[ i ] + radius; - } - - numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - - for( e = 0; e < numListedEntities; e++ ) - { - ent = &g_entities[ entityList[ e ] ]; - - if( ent == ignore ) - continue; - - if( !ent->takedamage ) - continue; - - if( ent->flags & FL_NOTARGET ) - continue; - - // find the distance from the edge of the bounding box - for( i = 0 ; i < 3 ; i++ ) - { - if( origin[ i ] < ent->r.absmin[ i ] ) - v[ i ] = ent->r.absmin[ i ] - origin[ i ]; - else if( origin[ i ] > ent->r.absmax[ i ] ) - v[ i ] = origin[ i ] - ent->r.absmax[ i ]; - else - v[ i ] = 0; - } - - dist = VectorLength( v ); - if( dist >= radius ) - continue; - - points = damage * ( 1.0 - dist / radius ); - - if( CanDamage( ent, origin ) && ent->client && - ent->client->ps.stats[ STAT_TEAM ] != team ) - { - VectorSubtract( ent->r.currentOrigin, origin, dir ); - // push the center of mass higher than the origin so players - // get knocked into the air more - dir[ 2 ] += 24; - hitClient = qtrue; - G_Damage( ent, NULL, attacker, dir, origin, - (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod ); - } - } - - return hitClient; -} - - -/* -============ -G_RadiusDamage -============ -*/ -qboolean G_RadiusDamage( vec3_t origin, gentity_t *attacker, float damage, - float radius, gentity_t *ignore, int mod ) -{ - float points, dist; - gentity_t *ent; - int entityList[ MAX_GENTITIES ]; - int numListedEntities; - vec3_t mins, maxs; - vec3_t v; - vec3_t dir; - int i, e; - qboolean hitClient = qfalse; - - if( radius < 1 ) - radius = 1; - - for( i = 0; i < 3; i++ ) - { - mins[ i ] = origin[ i ] - radius; - maxs[ i ] = origin[ i ] + radius; - } - - numListedEntities = trap_EntitiesInBox( mins, maxs, entityList, MAX_GENTITIES ); - - for( e = 0; e < numListedEntities; e++ ) - { - ent = &g_entities[ entityList[ e ] ]; - - if( ent == ignore ) - continue; - - if( !ent->takedamage ) - continue; - - // find the distance from the edge of the bounding box - for( i = 0; i < 3; i++ ) - { - if( origin[ i ] < ent->r.absmin[ i ] ) - v[ i ] = ent->r.absmin[ i ] - origin[ i ]; - else if( origin[ i ] > ent->r.absmax[ i ] ) - v[ i ] = origin[ i ] - ent->r.absmax[ i ]; - else - v[ i ] = 0; - } - - dist = VectorLength( v ); - if( dist >= radius ) - continue; - - points = damage * ( 1.0 - dist / radius ); - - if( CanDamage( ent, origin ) ) - { - VectorSubtract( ent->r.currentOrigin, origin, dir ); - // push the center of mass higher than the origin so players - // get knocked into the air more - dir[ 2 ] += 24; - hitClient = qtrue; - G_Damage( ent, NULL, attacker, dir, origin, - (int)points, DAMAGE_RADIUS|DAMAGE_NO_LOCDAMAGE, mod ); - } - } - - return hitClient; -} - -/* -================ -G_LogDestruction - -Log deconstruct/destroy events -================ -*/ -void G_LogDestruction( gentity_t *self, gentity_t *actor, int mod ) -{ - buildFate_t fate; - - switch( mod ) - { - case MOD_DECONSTRUCT: - fate = BF_DECONSTRUCT; - break; - case MOD_REPLACE: - fate = BF_REPLACE; - break; - case MOD_NOCREEP: - fate = ( actor->client ) ? BF_UNPOWER : BF_AUTO; - break; - default: - if( actor->client ) - { - if( actor->client->pers.teamSelection == - BG_Buildable( self->s.modelindex, NULL )->team ) - { - fate = BF_TEAMKILL; - } - else - fate = BF_DESTROY; - } - else - fate = BF_AUTO; - break; - } - G_BuildLogAuto( actor, self, fate ); - - // don't log when marked structures are removed - if( mod == MOD_REPLACE ) - return; - - G_LogPrintf( S_COLOR_YELLOW "Deconstruct: %d %d %s %s: %s %s by %s\n", - actor - g_entities, - self - g_entities, - BG_Buildable( self->s.modelindex, NULL )->name, - modNames[ mod ], - G_CuboidName(self->s.modelindex,self->cuboidSize,qtrue), - mod == MOD_DECONSTRUCT ? "deconstructed" : "destroyed", - actor->client ? actor->client->pers.netname : "" ); - - // No-power deaths for humans come after some minutes and it's confusing - // when the messages appear attributed to the deconner. Just don't print them. - if( mod == MOD_NOCREEP && actor->client && - actor->client->pers.teamSelection == TEAM_HUMANS ) - return; - - if( actor->client && actor->client->pers.teamSelection == - BG_Buildable( self->s.modelindex, NULL )->team ) - { - G_TeamCommand( actor->client->ps.stats[ STAT_TEAM ], - va( "print \"%s ^3%s^7 by %s\n\"", - G_CuboidName(self->s.modelindex,self->cuboidSize,qfalse), - mod == MOD_DECONSTRUCT ? "DECONSTRUCTED" : "DESTROYED", - actor->client->pers.netname ) ); - } - -} diff --git a/src/game/g_main.c.orig b/src/game/g_main.c.orig deleted file mode 100644 index 33784c9..0000000 --- a/src/game/g_main.c.orig +++ /dev/null @@ -1,2548 +0,0 @@ -/* -=========================================================================== -Copyright (C) 1999-2005 Id Software, Inc. -Copyright (C) 2000-2009 Darklegion Development - -This file is part of Tremulous. - -Tremulous is free software; you can redistribute it -and/or modify it under the terms of the GNU General Public License as -published by the Free Software Foundation; either version 2 of the License, -or (at your option) any later version. - -Tremulous is distributed in the hope that it will be -useful, but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Tremulous; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA -=========================================================================== -*/ - -#include "g_local.h" - -level_locals_t level; - -typedef struct -{ - vmCvar_t *vmCvar; - char *cvarName; - char *defaultString; - int cvarFlags; - int modificationCount; // for tracking changes - qboolean trackChange; // track this variable, and announce if changed - /* certain cvars can be set in worldspawn, but we don't want those values to - persist, so keep track of non-worldspawn changes and restore that on map - end. unfortunately, if the server crashes, the value set in worldspawn may - persist */ - char *explicit; -} cvarTable_t; - -gentity_t g_entities[ MAX_GENTITIES ]; -gclient_t g_clients[ MAX_CLIENTS ]; - -vmCvar_t g_timelimit; -vmCvar_t g_suddenDeathTime; -vmCvar_t g_friendlyFire; -vmCvar_t g_friendlyBuildableFire; -vmCvar_t g_dretchPunt; -vmCvar_t g_password; -vmCvar_t g_needpass; -vmCvar_t g_maxclients; -vmCvar_t g_maxGameClients; -vmCvar_t g_dedicated; -vmCvar_t g_speed; -vmCvar_t g_gravity; -vmCvar_t g_cheats; -vmCvar_t g_knockback; -vmCvar_t g_inactivity; -vmCvar_t g_debugMove; -vmCvar_t g_debugDamage; -vmCvar_t g_motd; -vmCvar_t g_synchronousClients; -vmCvar_t g_warmup; -vmCvar_t g_doWarmup; -vmCvar_t g_restarted; -vmCvar_t g_lockTeamsAtStart; -vmCvar_t g_logFile; -vmCvar_t g_logFileSync; -vmCvar_t g_allowVote; -vmCvar_t g_voteLimit; -vmCvar_t g_suddenDeathVotePercent; -vmCvar_t g_suddenDeathVoteDelay; -vmCvar_t g_teamForceBalance; -vmCvar_t g_smoothClients; -vmCvar_t pmove_fixed; -vmCvar_t pmove_msec; -vmCvar_t g_minNameChangePeriod; -vmCvar_t g_maxNameChanges; - -vmCvar_t g_alienBuildPoints; -vmCvar_t g_alienBuildQueueTime; -vmCvar_t g_humanBuildPoints; -vmCvar_t g_humanBuildQueueTime; -vmCvar_t g_humanRepeaterBuildPoints; -vmCvar_t g_humanRepeaterBuildQueueTime; -vmCvar_t g_humanRepeaterMaxZones; -vmCvar_t g_humanStage; -vmCvar_t g_humanCredits; -vmCvar_t g_humanMaxStage; -vmCvar_t g_humanStage2Threshold; -vmCvar_t g_humanStage3Threshold; -vmCvar_t g_alienStage; -vmCvar_t g_alienCredits; -vmCvar_t g_alienMaxStage; -vmCvar_t g_alienStage2Threshold; -vmCvar_t g_alienStage3Threshold; -vmCvar_t g_teamImbalanceWarnings; -vmCvar_t g_freeFundPeriod; - -vmCvar_t g_unlagged; - -vmCvar_t g_disabledEquipment; -vmCvar_t g_disabledClasses; -vmCvar_t g_disabledBuildables; - -vmCvar_t g_markDeconstruct; - -vmCvar_t g_debugMapRotation; -vmCvar_t g_currentMapRotation; -vmCvar_t g_mapRotationNodes; -vmCvar_t g_mapRotationStack; -vmCvar_t g_nextMap; -vmCvar_t g_initialMapRotation; - -vmCvar_t g_debugVoices; -vmCvar_t g_voiceChats; - -vmCvar_t g_shove; - -vmCvar_t g_mapConfigs; -vmCvar_t g_sayAreaRange; - -vmCvar_t g_floodMaxDemerits; -vmCvar_t g_floodMinTime; - -vmCvar_t g_layouts; -vmCvar_t g_layoutAuto; - -vmCvar_t g_emoticonsAllowedInNames; - -vmCvar_t g_admin; -vmCvar_t g_adminTempBan; -vmCvar_t g_adminMaxBan; - -vmCvar_t g_privateMessages; -vmCvar_t g_specChat; -vmCvar_t g_publicAdminMessages; -vmCvar_t g_allowTeamOverlay; - -vmCvar_t g_censorship; - -vmCvar_t g_tag; - -vmCvar_t g_unlimited; -vmCvar_t g_instantBuild; -vmCvar_t g_cuboidSizeLimit; -vmCvar_t g_cuboidMode; - -vmCvar_t g_buildableDensityLimit; -vmCvar_t g_buildableDensityLimitRange; - -// copy cvars that can be set in worldspawn so they can be restored later -static char cv_gravity[ MAX_CVAR_VALUE_STRING ]; -static char cv_humanMaxStage[ MAX_CVAR_VALUE_STRING ]; -static char cv_alienMaxStage[ MAX_CVAR_VALUE_STRING ]; - -static cvarTable_t gameCvarTable[ ] = -{ - // don't override the cheat state set by the system - { &g_cheats, "sv_cheats", "", 0, 0, qfalse }, - - // noset vars - { NULL, "gamename", GAME_VERSION , CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - { NULL, "gamedate", __DATE__ , CVAR_ROM, 0, qfalse }, - { &g_restarted, "g_restarted", "0", CVAR_ROM, 0, qfalse }, - { &g_lockTeamsAtStart, "g_lockTeamsAtStart", "0", CVAR_ROM, 0, qfalse }, - { NULL, "sv_mapname", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - { NULL, "P", "", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - - // latched vars - - { &g_maxclients, "sv_maxclients", "8", CVAR_SERVERINFO | CVAR_LATCH | CVAR_ARCHIVE, 0, qfalse }, - - // change anytime vars - { &g_maxGameClients, "g_maxGameClients", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qfalse }, - - { &g_timelimit, "timelimit", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, - { &g_suddenDeathTime, "g_suddenDeathTime", "0", CVAR_SERVERINFO | CVAR_ARCHIVE | CVAR_NORESTART, 0, qtrue }, - - { &g_synchronousClients, "g_synchronousClients", "0", CVAR_SYSTEMINFO, 0, qfalse }, - - { &g_friendlyFire, "g_friendlyFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, - { &g_friendlyBuildableFire, "g_friendlyBuildableFire", "0", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, - { &g_dretchPunt, "g_dretchPunt", "1", CVAR_ARCHIVE, 0, qtrue }, - - { &g_teamForceBalance, "g_teamForceBalance", "0", CVAR_ARCHIVE, 0, qtrue }, - - { &g_warmup, "g_warmup", "10", CVAR_ARCHIVE, 0, qtrue }, - { &g_doWarmup, "g_doWarmup", "0", CVAR_ARCHIVE, 0, qtrue }, - { &g_logFile, "g_logFile", "games.log", CVAR_ARCHIVE, 0, qfalse }, - { &g_logFileSync, "g_logFileSync", "0", CVAR_ARCHIVE, 0, qfalse }, - - { &g_password, "g_password", "", CVAR_USERINFO, 0, qfalse }, - - { &g_needpass, "g_needpass", "0", CVAR_SERVERINFO | CVAR_ROM, 0, qfalse }, - - { &g_dedicated, "dedicated", "0", 0, 0, qfalse }, - - { &g_speed, "g_speed", "320", 0, 0, qtrue }, - { &g_gravity, "g_gravity", "800", 0, 0, qtrue, cv_gravity }, - { &g_knockback, "g_knockback", "1000", 0, 0, qtrue }, - { &g_inactivity, "g_inactivity", "0", 0, 0, qtrue }, - { &g_debugMove, "g_debugMove", "0", 0, 0, qfalse }, - { &g_debugDamage, "g_debugDamage", "0", 0, 0, qfalse }, - { &g_motd, "g_motd", "", 0, 0, qfalse }, - - { &g_allowVote, "g_allowVote", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_voteLimit, "g_voteLimit", "5", CVAR_ARCHIVE, 0, qfalse }, - { &g_suddenDeathVotePercent, "g_suddenDeathVotePercent", "74", CVAR_ARCHIVE, 0, qfalse }, - { &g_suddenDeathVoteDelay, "g_suddenDeathVoteDelay", "180", CVAR_ARCHIVE, 0, qfalse }, - { &g_minNameChangePeriod, "g_minNameChangePeriod", "5", 0, 0, qfalse}, - { &g_maxNameChanges, "g_maxNameChanges", "5", 0, 0, qfalse}, - - { &g_smoothClients, "g_smoothClients", "1", 0, 0, qfalse}, - { &pmove_fixed, "pmove_fixed", "0", CVAR_SYSTEMINFO, 0, qfalse}, - { &pmove_msec, "pmove_msec", "8", CVAR_SYSTEMINFO, 0, qfalse}, - - { &g_alienBuildPoints, "g_alienBuildPoints", DEFAULT_ALIEN_BUILDPOINTS, 0, 0, qfalse }, - { &g_alienBuildQueueTime, "g_alienBuildQueueTime", DEFAULT_ALIEN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanBuildPoints, "g_humanBuildPoints", DEFAULT_HUMAN_BUILDPOINTS, 0, 0, qfalse }, - { &g_humanBuildQueueTime, "g_humanBuildQueueTime", DEFAULT_HUMAN_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanRepeaterBuildPoints, "g_humanRepeaterBuildPoints", DEFAULT_HUMAN_REPEATER_BUILDPOINTS, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanRepeaterMaxZones, "g_humanRepeaterMaxZones", DEFAULT_HUMAN_REPEATER_MAX_ZONES, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanRepeaterBuildQueueTime, "g_humanRepeaterBuildQueueTime", DEFAULT_HUMAN_REPEATER_QUEUE_TIME, CVAR_ARCHIVE, 0, qfalse }, - { &g_humanStage, "g_humanStage", "0", 0, 0, qfalse }, - { &g_humanCredits, "g_humanCredits", "0", 0, 0, qfalse }, - { &g_humanMaxStage, "g_humanMaxStage", DEFAULT_HUMAN_MAX_STAGE, 0, 0, qfalse, cv_humanMaxStage }, - { &g_humanStage2Threshold, "g_humanStage2Threshold", DEFAULT_HUMAN_STAGE2_THRESH, 0, 0, qfalse }, - { &g_humanStage3Threshold, "g_humanStage3Threshold", DEFAULT_HUMAN_STAGE3_THRESH, 0, 0, qfalse }, - { &g_alienStage, "g_alienStage", "0", 0, 0, qfalse }, - { &g_alienCredits, "g_alienCredits", "0", 0, 0, qfalse }, - { &g_alienMaxStage, "g_alienMaxStage", DEFAULT_ALIEN_MAX_STAGE, 0, 0, qfalse, cv_alienMaxStage }, - { &g_alienStage2Threshold, "g_alienStage2Threshold", DEFAULT_ALIEN_STAGE2_THRESH, 0, 0, qfalse }, - { &g_alienStage3Threshold, "g_alienStage3Threshold", DEFAULT_ALIEN_STAGE3_THRESH, 0, 0, qfalse }, - { &g_teamImbalanceWarnings, "g_teamImbalanceWarnings", "30", CVAR_ARCHIVE, 0, qfalse }, - { &g_freeFundPeriod, "g_freeFundPeriod", DEFAULT_FREEKILL_PERIOD, CVAR_ARCHIVE, 0, qtrue }, - - { &g_unlagged, "g_unlagged", "1", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, - - { &g_disabledEquipment, "g_disabledEquipment", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, - { &g_disabledClasses, "g_disabledClasses", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, - { &g_disabledBuildables, "g_disabledBuildables", "", CVAR_ROM | CVAR_SYSTEMINFO, 0, qfalse }, - - { &g_sayAreaRange, "g_sayAreaRange", "1000", CVAR_ARCHIVE, 0, qtrue }, - - { &g_floodMaxDemerits, "g_floodMaxDemerits", "5000", CVAR_ARCHIVE, 0, qfalse }, - { &g_floodMinTime, "g_floodMinTime", "2000", CVAR_ARCHIVE, 0, qfalse }, - - { &g_markDeconstruct, "g_markDeconstruct", "3", CVAR_SERVERINFO | CVAR_ARCHIVE, 0, qtrue }, - - { &g_debugMapRotation, "g_debugMapRotation", "0", 0, 0, qfalse }, - { &g_currentMapRotation, "g_currentMapRotation", "-1", 0, 0, qfalse }, // -1 = NOT_ROTATING - { &g_mapRotationNodes, "g_mapRotationNodes", "", CVAR_ROM, 0, qfalse }, - { &g_mapRotationStack, "g_mapRotationStack", "", CVAR_ROM, 0, qfalse }, - { &g_nextMap, "g_nextMap", "", 0 , 0, qtrue }, - { &g_initialMapRotation, "g_initialMapRotation", "", CVAR_ARCHIVE, 0, qfalse }, - { &g_debugVoices, "g_debugVoices", "0", 0, 0, qfalse }, - { &g_voiceChats, "g_voiceChats", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_shove, "g_shove", "0.0", CVAR_ARCHIVE, 0, qfalse }, - { &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_emoticonsAllowedInNames, "g_emoticonsAllowedInNames", "1", CVAR_LATCH|CVAR_ARCHIVE, 0, qfalse }, - - { &g_admin, "g_admin", "admin.dat", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminTempBan, "g_adminTempBan", "2m", CVAR_ARCHIVE, 0, qfalse }, - { &g_adminMaxBan, "g_adminMaxBan", "2w", CVAR_ARCHIVE, 0, qfalse }, - - { &g_privateMessages, "g_privateMessages", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_specChat, "g_specChat", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_publicAdminMessages, "g_publicAdminMessages", "1", CVAR_ARCHIVE, 0, qfalse }, - { &g_allowTeamOverlay, "g_allowTeamOverlay", "1", CVAR_ARCHIVE, 0, qtrue }, - - { &g_censorship, "g_censorship", "", CVAR_ARCHIVE, 0, qfalse }, - - { &g_tag, "g_tag", "gpp", CVAR_INIT, 0, qfalse }, - - { &g_unlimited, "g_unlimited", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, - { &g_instantBuild, "g_instantBuild", "0", CVAR_ARCHIVE | CVAR_SERVERINFO, 0, qfalse }, - { &g_cuboidSizeLimit, "g_cuboidSizeLimit", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_cuboidMode, "g_cuboidMode", "0", CVAR_ARCHIVE, 0, qfalse }, - - { &g_buildableDensityLimit, "g_buildableDensityLimit", "0", CVAR_ARCHIVE, 0, qfalse }, - { &g_buildableDensityLimitRange, "g_buildableDensityLimitRange", "0", CVAR_ARCHIVE, 0, qfalse } -}; - -static int gameCvarTableSize = sizeof( gameCvarTable ) / sizeof( gameCvarTable[ 0 ] ); - - -void G_InitGame( int levelTime, int randomSeed, int restart ); -void G_RunFrame( int levelTime ); -void G_ShutdownGame( int restart ); -void CheckExitRules( void ); - -void G_CountSpawns( void ); -void G_CalculateBuildPoints( void ); - -/* -================ -vmMain - -This is the only way control passes into the module. -This must be the very first function compiled into the .q3vm file -================ -*/ -Q_EXPORT intptr_t vmMain( int command, int arg0, int arg1, int arg2, int arg3, int arg4, - int arg5, int arg6, int arg7, int arg8, int arg9, - int arg10, int arg11 ) -{ - switch( command ) - { - case GAME_INIT: - G_InitGame( arg0, arg1, arg2 ); - return 0; - - case GAME_SHUTDOWN: - G_ShutdownGame( arg0 ); - return 0; - - case GAME_CLIENT_CONNECT: - return (intptr_t)ClientConnect( arg0, arg1 ); - - case GAME_CLIENT_THINK: - ClientThink( arg0 ); - return 0; - - case GAME_CLIENT_USERINFO_CHANGED: - ClientUserinfoChanged( arg0, qfalse ); - return 0; - - case GAME_CLIENT_DISCONNECT: - ClientDisconnect( arg0 ); - return 0; - - case GAME_CLIENT_BEGIN: - ClientBegin( arg0 ); - return 0; - - case GAME_CLIENT_COMMAND: - ClientCommand( arg0 ); - return 0; - - case GAME_RUN_FRAME: - G_RunFrame( arg0 ); - return 0; - - case GAME_CONSOLE_COMMAND: - return ConsoleCommand( ); - } - - return -1; -} - - -void QDECL G_Printf( const char *fmt, ... ) -{ - va_list argptr; - char text[ 1024 ]; - - va_start( argptr, fmt ); - Q_vsnprintf( text, sizeof( text ), fmt, argptr ); - va_end( argptr ); - - trap_Print( text ); -} - -void QDECL G_Error( const char *fmt, ... ) -{ - va_list argptr; - char text[ 1024 ]; - - va_start( argptr, fmt ); - Q_vsnprintf( text, sizeof( text ), fmt, argptr ); - va_end( argptr ); - - trap_Error( text ); -} - -/* -================ -G_FindTeams - -Chain together all entities with a matching team field. -Entity teams are used for item groups and multi-entity mover groups. - -All but the first will have the FL_TEAMSLAVE flag set and teammaster field set -All but the last will have the teamchain field set to the next one -================ -*/ -void G_FindTeams( void ) -{ - gentity_t *e, *e2; - int i, j; - int c, c2; - - c = 0; - c2 = 0; - - for( i = 1, e = g_entities+i; i < level.num_entities; i++, e++ ) - { - if( !e->inuse ) - continue; - - if( !e->team ) - continue; - - if( e->flags & FL_TEAMSLAVE ) - continue; - - e->teammaster = e; - c++; - c2++; - - for( j = i + 1, e2 = e + 1; j < level.num_entities; j++, e2++ ) - { - if( !e2->inuse ) - continue; - - if( !e2->team ) - continue; - - if( e2->flags & FL_TEAMSLAVE ) - continue; - - if( !strcmp( e->team, e2->team ) ) - { - c2++; - e2->teamchain = e->teamchain; - e->teamchain = e2; - e2->teammaster = e; - e2->flags |= FL_TEAMSLAVE; - - // make sure that targets only point at the master - if( e2->targetname ) - { - e->targetname = e2->targetname; - e2->targetname = NULL; - } - } - } - } - - G_Printf( "%i teams with %i entities\n", c, c2 ); -} - - -/* -================= -G_RegisterCvars -================= -*/ -void G_RegisterCvars( void ) -{ - int i; - cvarTable_t *cv; - - for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) - { - trap_Cvar_Register( cv->vmCvar, cv->cvarName, - cv->defaultString, cv->cvarFlags ); - - if( cv->vmCvar ) - cv->modificationCount = cv->vmCvar->modificationCount; - - if( cv->explicit ) - strcpy( cv->explicit, cv->vmCvar->string ); - } -} - -/* -================= -G_UpdateCvars -================= -*/ -void G_UpdateCvars( void ) -{ - int i; - cvarTable_t *cv; - - for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) - { - if( cv->vmCvar ) - { - trap_Cvar_Update( cv->vmCvar ); - - if( cv->modificationCount != cv->vmCvar->modificationCount ) - { - cv->modificationCount = cv->vmCvar->modificationCount; - - if( cv->trackChange ) - trap_SendServerCommand( -1, va( "print \"Server: %s changed to %s\n\"", - cv->cvarName, cv->vmCvar->string ) ); - - if( !level.spawning && cv->explicit ) - strcpy( cv->explicit, cv->vmCvar->string ); - } - } - } -} - -/* -================= -G_RestoreCvars -================= -*/ -void G_RestoreCvars( void ) -{ - int i; - cvarTable_t *cv; - - for( i = 0, cv = gameCvarTable; i < gameCvarTableSize; i++, cv++ ) - { - if( cv->vmCvar && cv->explicit ) - trap_Cvar_Set( cv->cvarName, cv->explicit ); - } -} - -/* -================= -G_MapConfigs -================= -*/ -void G_MapConfigs( const char *mapname ) -{ - - if( !g_mapConfigs.string[0] ) - return; - - if( trap_Cvar_VariableIntegerValue( "g_mapConfigsLoaded" ) ) - return; - - trap_SendConsoleCommand( EXEC_APPEND, - va( "exec \"%s/default.cfg\"\n", g_mapConfigs.string ) ); - - trap_SendConsoleCommand( EXEC_APPEND, - va( "exec \"%s/%s.cfg\"\n", g_mapConfigs.string, mapname ) ); - - trap_Cvar_Set( "g_mapConfigsLoaded", "1" ); -} - -/* -================ -G_CheckCuboidConfig - -even tokens are map names -odd tokens are cuboid modes -================ -*/ -void G_CheckCuboidConfig(char* mapname) -{ - char* token; - char config[MAX_CVAR_VALUE_STRING]; //should be enough for few maps - qboolean type=qfalse; - qboolean found=qfalse; - int mode; - - mode=trap_Cvar_VariableIntegerValue("g_cuboidDefaultMode"); - trap_Cvar_VariableStringBuffer("g_cuboidConfig",config,sizeof(config)); - - while(1) - { - COM_Parse(&token); - - if(!token) - break; - - if(!type) - if(!Q_stricmp(token,mapname)) - found=qtrue; - else - if(found) - { - mode=atoi(token); - break; - } - } - Com_Printf("cuboids are %s%s^7 on %s\n",(mode==0?"^2ENABLED":(mode==1?"^1DISABLED ON S1":"^1DISABLED")),(found?"":" BY DEFAULT"),mapname); -} - -/* -============ -G_InitGame - -============ -*/ -void G_InitGame( int levelTime, int randomSeed, int restart ) -{ - int i; - char map[ MAX_CVAR_VALUE_STRING ] = {""}; - - srand( randomSeed ); - - G_RegisterCvars( ); - - G_Printf( "------- Game Initialization -------\n" ); - G_Printf( "gamename: %s\n", GAME_VERSION ); - G_Printf( "gamedate: %s\n", __DATE__ ); - - BG_InitMemory( ); - - // set some level globals - memset( &level, 0, sizeof( level ) ); - level.time = levelTime; - level.startTime = levelTime; - level.alienStage2Time = level.alienStage3Time = - level.humanStage2Time = level.humanStage3Time = level.startTime; - - level.snd_fry = G_SoundIndex( "sound/misc/fry.wav" ); // FIXME standing in lava / slime - - if( g_logFile.string[ 0 ] ) - { - if( g_logFileSync.integer ) - trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND_SYNC ); - else - trap_FS_FOpenFile( g_logFile.string, &level.logFile, FS_APPEND ); - - if( !level.logFile ) - G_Printf( "WARNING: Couldn't open logfile: %s\n", g_logFile.string ); - else - { - char serverinfo[ MAX_INFO_STRING ]; - qtime_t qt; - int t; - - trap_GetServerinfo( serverinfo, sizeof( serverinfo ) ); - - G_LogPrintf( "------------------------------------------------------------\n" ); - G_LogPrintf( "InitGame: %s\n", serverinfo ); - - t = trap_RealTime( &qt ); - G_LogPrintf("RealTime: %04i/%02i/%02i %02i:%02i:%02i\n", - qt.tm_year+1900, qt.tm_mon+1, qt.tm_mday, - qt.tm_hour, qt.tm_min, qt.tm_sec ); - - } - } - else - G_Printf( "Not logging to disk\n" ); - - trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - G_MapConfigs( map ); - - // we're done with g_mapConfigs, so reset this for the next map - trap_Cvar_Set( "g_mapConfigsLoaded", "0" ); - - G_RegisterCommands( ); - G_admin_readconfig( NULL ); - G_LoadCensors( ); - - // initialize all entities for this game - memset( g_entities, 0, MAX_GENTITIES * sizeof( g_entities[ 0 ] ) ); - level.gentities = g_entities; - - // initialize all clients for this game - level.maxclients = g_maxclients.integer; - memset( g_clients, 0, MAX_CLIENTS * sizeof( g_clients[ 0 ] ) ); - level.clients = g_clients; - - // set client fields on player ents - for( i = 0; i < level.maxclients; i++ ) - g_entities[ i ].client = level.clients + i; - - // always leave room for the max number of clients, - // even if they aren't all used, so numbers inside that - // range are NEVER anything but clients - level.num_entities = MAX_CLIENTS; - - // let the server system know where the entites are - trap_LocateGameData( level.gentities, level.num_entities, sizeof( gentity_t ), - &level.clients[ 0 ].ps, sizeof( level.clients[ 0 ] ) ); - - level.emoticonCount = BG_LoadEmoticons( level.emoticons, MAX_EMOTICONS ); - - trap_SetConfigstring( CS_INTERMISSION, "0" ); - - // test to see if a custom buildable layout will be loaded - G_LayoutSelect( ); - - // this has to be flipped after the first UpdateCvars - level.spawning = qtrue; - // parse the key/value pairs and spawn gentities - G_SpawnEntitiesFromString( ); - - // load up a custom building layout if there is one - G_LayoutLoad( ); - - // find out g_cuboidMode value - G_CheckCuboidConfig( map ); - - // the map might disable some things - BG_InitAllowedGameElements( ); - - // general initialization - G_FindTeams( ); - - BG_InitClassConfigs( ); - BG_InitBuildableConfigs( ); - G_InitDamageLocations( ); - G_InitMapRotations( ); - G_InitSpawnQueue( &level.alienSpawnQueue ); - G_InitSpawnQueue( &level.humanSpawnQueue ); - - if( g_debugMapRotation.integer ) - G_PrintRotations( ); - - level.voices = BG_VoiceInit( ); - BG_PrintVoices( level.voices, g_debugVoices.integer ); - - //reset stages - trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) ); - trap_Cvar_Set( "g_humanStage", va( "%d", S1 ) ); - trap_Cvar_Set( "g_alienCredits", 0 ); - trap_Cvar_Set( "g_humanCredits", 0 ); - level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; - - G_Printf( "-----------------------------------\n" ); - - // So the server counts the spawns without a client attached - G_CountSpawns( ); - - G_UpdateTeamConfigStrings( ); - - if( g_lockTeamsAtStart.integer ) - { - level.alienTeamLocked = qtrue; - level.humanTeamLocked = qtrue; - trap_Cvar_Set( "g_lockTeamsAtStart", "0" ); - } -} - -/* -================== -G_ClearVotes - -remove all currently active votes -================== -*/ -static void G_ClearVotes( void ) -{ - int i; - memset( level.voteTime, 0, sizeof( level.voteTime ) ); - for( i = 0; i < NUM_TEAMS; i++ ) - { - trap_SetConfigstring( CS_VOTE_TIME + i, "" ); - trap_SetConfigstring( CS_VOTE_STRING + i, "" ); - } -} - -/* -================= -G_ShutdownGame -================= -*/ -void G_ShutdownGame( int restart ) -{ - // in case of a map_restart - G_ClearVotes( ); - - G_RestoreCvars( ); - - G_Printf( "==== ShutdownGame ====\n" ); - - if( level.logFile ) - { - G_LogPrintf( "ShutdownGame:\n" ); - G_LogPrintf( "------------------------------------------------------------\n" ); - trap_FS_FCloseFile( level.logFile ); - level.logFile = 0; - } - - // write all the client session data so we can get it back - G_WriteSessionData( ); - - G_admin_cleanup( ); - G_namelog_cleanup( ); - G_UnregisterCommands( ); - - G_ShutdownMapRotations( ); - - level.restarted = qfalse; - level.surrenderTeam = TEAM_NONE; - trap_SetConfigstring( CS_WINNER, "" ); -} - - - -//=================================================================== - -void QDECL Com_Error( int level, const char *error, ... ) -{ - va_list argptr; - char text[ 1024 ]; - - va_start( argptr, error ); - Q_vsnprintf( text, sizeof( text ), error, argptr ); - va_end( argptr ); - - G_Error( "%s", text ); -} - -void QDECL Com_Printf( const char *msg, ... ) -{ - va_list argptr; - char text[ 1024 ]; - - va_start( argptr, msg ); - Q_vsnprintf( text, sizeof( text ), msg, argptr ); - va_end( argptr ); - - G_Printf( "%s", text ); -} - -/* -======================================================================== - -PLAYER COUNTING / SCORE SORTING - -======================================================================== -*/ - - -/* -============= -SortRanks - -============= -*/ -int QDECL SortRanks( const void *a, const void *b ) -{ - gclient_t *ca, *cb; - - ca = &level.clients[ *(int *)a ]; - cb = &level.clients[ *(int *)b ]; - - // then sort by score - if( ca->ps.persistant[ PERS_SCORE ] > cb->ps.persistant[ PERS_SCORE ] ) - return -1; - if( ca->ps.persistant[ PERS_SCORE ] < cb->ps.persistant[ PERS_SCORE ] ) - return 1; - else - return 0; -} - -/* -============ -G_InitSpawnQueue - -Initialise a spawn queue -============ -*/ -void G_InitSpawnQueue( spawnQueue_t *sq ) -{ - int i; - - sq->back = sq->front = 0; - sq->back = QUEUE_MINUS1( sq->back ); - - //0 is a valid clientNum, so use something else - for( i = 0; i < MAX_CLIENTS; i++ ) - sq->clients[ i ] = -1; -} - -/* -============ -G_GetSpawnQueueLength - -Return the length of a spawn queue -============ -*/ -int G_GetSpawnQueueLength( spawnQueue_t *sq ) -{ - int length = sq->back - sq->front + 1; - - while( length < 0 ) - length += MAX_CLIENTS; - - while( length >= MAX_CLIENTS ) - length -= MAX_CLIENTS; - - return length; -} - -/* -============ -G_PopSpawnQueue - -Remove from front element from a spawn queue -============ -*/ -int G_PopSpawnQueue( spawnQueue_t *sq ) -{ - int clientNum = sq->clients[ sq->front ]; - - if( G_GetSpawnQueueLength( sq ) > 0 ) - { - sq->clients[ sq->front ] = -1; - sq->front = QUEUE_PLUS1( sq->front ); - G_StopFollowing( g_entities + clientNum ); - g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED; - - return clientNum; - } - else - return -1; -} - -/* -============ -G_PeekSpawnQueue - -Look at front element from a spawn queue -============ -*/ -int G_PeekSpawnQueue( spawnQueue_t *sq ) -{ - return sq->clients[ sq->front ]; -} - -/* -============ -G_SearchSpawnQueue - -Look to see if clientNum is already in the spawnQueue -============ -*/ -qboolean G_SearchSpawnQueue( spawnQueue_t *sq, int clientNum ) -{ - int i; - - for( i = 0; i < MAX_CLIENTS; i++ ) - { - if( sq->clients[ i ] == clientNum ) - return qtrue; - } - - return qfalse; -} - -/* -============ -G_PushSpawnQueue - -Add an element to the back of the spawn queue -============ -*/ -qboolean G_PushSpawnQueue( spawnQueue_t *sq, int clientNum ) -{ - // don't add the same client more than once - if( G_SearchSpawnQueue( sq, clientNum ) ) - return qfalse; - - sq->back = QUEUE_PLUS1( sq->back ); - sq->clients[ sq->back ] = clientNum; - - g_entities[ clientNum ].client->ps.pm_flags |= PMF_QUEUED; - return qtrue; -} - -/* -============ -G_RemoveFromSpawnQueue - -remove a specific client from a spawn queue -============ -*/ -qboolean G_RemoveFromSpawnQueue( spawnQueue_t *sq, int clientNum ) -{ - int i = sq->front; - - if( G_GetSpawnQueueLength( sq ) ) - { - do - { - if( sq->clients[ i ] == clientNum ) - { - //and this kids is why it would have - //been better to use an LL for internal - //representation - do - { - sq->clients[ i ] = sq->clients[ QUEUE_PLUS1( i ) ]; - - i = QUEUE_PLUS1( i ); - } while( i != QUEUE_PLUS1( sq->back ) ); - - sq->back = QUEUE_MINUS1( sq->back ); - g_entities[ clientNum ].client->ps.pm_flags &= ~PMF_QUEUED; - - return qtrue; - } - - i = QUEUE_PLUS1( i ); - } while( i != QUEUE_PLUS1( sq->back ) ); - } - - return qfalse; -} - -/* -============ -G_GetPosInSpawnQueue - -Get the position of a client in a spawn queue -============ -*/ -int G_GetPosInSpawnQueue( spawnQueue_t *sq, int clientNum ) -{ - int i = sq->front; - - if( G_GetSpawnQueueLength( sq ) ) - { - do - { - if( sq->clients[ i ] == clientNum ) - { - if( i < sq->front ) - return i + MAX_CLIENTS - sq->front; - else - return i - sq->front; - } - - i = QUEUE_PLUS1( i ); - } while( i != QUEUE_PLUS1( sq->back ) ); - } - - return -1; -} - -/* -============ -G_PrintSpawnQueue - -Print the contents of a spawn queue -============ -*/ -void G_PrintSpawnQueue( spawnQueue_t *sq ) -{ - int i = sq->front; - int length = G_GetSpawnQueueLength( sq ); - - G_Printf( "l:%d f:%d b:%d :", length, sq->front, sq->back ); - - if( length > 0 ) - { - do - { - if( sq->clients[ i ] == -1 ) - G_Printf( "*:" ); - else - G_Printf( "%d:", sq->clients[ i ] ); - - i = QUEUE_PLUS1( i ); - } while( i != QUEUE_PLUS1( sq->back ) ); - } - - G_Printf( "\n" ); -} - -/* -============ -G_SpawnClients - -Spawn queued clients -============ -*/ -void G_SpawnClients( team_t team ) -{ - int clientNum; - gentity_t *ent, *spawn; - vec3_t spawn_origin, spawn_angles; - spawnQueue_t *sq = NULL; - int numSpawns = 0; - - if( team == TEAM_ALIENS ) - { - sq = &level.alienSpawnQueue; - numSpawns = level.numAlienSpawns; - } - else if( team == TEAM_HUMANS ) - { - sq = &level.humanSpawnQueue; - numSpawns = level.numHumanSpawns; - } - - if( G_GetSpawnQueueLength( sq ) > 0 && numSpawns > 0 ) - { - clientNum = G_PeekSpawnQueue( sq ); - ent = &g_entities[ clientNum ]; - - if( ( spawn = G_SelectTremulousSpawnPoint( team, - ent->client->pers.lastDeathLocation, - spawn_origin, spawn_angles ) ) ) - { - clientNum = G_PopSpawnQueue( sq ); - - if( clientNum < 0 ) - return; - - ent = &g_entities[ clientNum ]; - - ent->client->sess.spectatorState = SPECTATOR_NOT; - ClientUserinfoChanged( clientNum, qfalse ); - ClientSpawn( ent, spawn, spawn_origin, spawn_angles ); - } - } -} - -/* -============ -G_CountSpawns - -Counts the number of spawns for each team -============ -*/ -void G_CountSpawns( void ) -{ - int i; - gentity_t *ent; - - level.numAlienSpawns = 0; - level.numHumanSpawns = 0; - for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) - { - if( !ent->inuse || ent->s.eType != ET_BUILDABLE || ent->health <= 0 ) - continue; - - if( ent->s.modelindex == BA_A_SPAWN ) - level.numAlienSpawns++; - - if( ent->s.modelindex == BA_H_SPAWN ) - level.numHumanSpawns++; - } -} - - -/* -============ -G_TimeTilSuddenDeath -============ -*/ -#define SUDDENDEATHWARNING 60000 -int G_TimeTilSuddenDeath( void ) -{ - if( ( !g_suddenDeathTime.integer && level.suddenDeathBeginTime == 0 ) || - ( level.suddenDeathBeginTime < 0 ) ) - return SUDDENDEATHWARNING + 1; // Always some time away - - return ( ( level.suddenDeathBeginTime ) - ( level.time - level.startTime ) ); -} - - -#define PLAYER_COUNT_MOD 5.0f - -/* -============ -G_CalculateBuildPoints - -Recalculate the quantity of building points available to the teams -============ -*/ -void G_CalculateBuildPoints( void ) -{ - int i; - buildable_t buildable; - buildPointZone_t *zone; - - // BP queue updates - while( level.alienBuildPointQueue > 0 && - level.alienNextQueueTime < level.time ) - { - level.alienBuildPointQueue--; - level.alienNextQueueTime += G_NextQueueTime( level.alienBuildPointQueue, - g_alienBuildPoints.integer, - g_alienBuildQueueTime.integer ); - } - - while( level.humanBuildPointQueue > 0 && - level.humanNextQueueTime < level.time ) - { - level.humanBuildPointQueue--; - level.humanNextQueueTime += G_NextQueueTime( level.humanBuildPointQueue, - g_humanBuildPoints.integer, - g_humanBuildQueueTime.integer ); - } - - // Sudden Death checks - if( G_TimeTilSuddenDeath( ) <= 0 && level.suddenDeathWarning < TW_PASSED ) - { - G_LogPrintf( "Beginning Sudden Death\n" ); - trap_SendServerCommand( -1, "cp \"Sudden Death!\"" ); - trap_SendServerCommand( -1, "print \"Beginning Sudden Death.\n\"" ); - level.suddenDeathWarning = TW_PASSED; - G_ClearDeconMarks( ); - - // Clear blueprints, or else structs that cost 0 BP can still be built after SD - for( i = 0; i < level.maxclients; i++ ) - { - if( g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] != BA_NONE ) - g_entities[ i ].client->ps.stats[ STAT_BUILDABLE ] = BA_NONE; - } - } - else if( G_TimeTilSuddenDeath( ) <= SUDDENDEATHWARNING && - level.suddenDeathWarning < TW_IMMINENT ) - { - trap_SendServerCommand( -1, va( "cp \"Sudden Death in %d seconds!\"", - (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); - trap_SendServerCommand( -1, va( "print \"Sudden Death will begin in %d seconds.\n\"", - (int)( G_TimeTilSuddenDeath( ) / 1000 ) ) ); - level.suddenDeathWarning = TW_IMMINENT; - } - - level.humanBuildPoints = g_humanBuildPoints.integer - level.humanBuildPointQueue; - level.alienBuildPoints = g_alienBuildPoints.integer - level.alienBuildPointQueue; - - // Reset buildPointZones - for( i = 0; i < g_humanRepeaterMaxZones.integer; i++ ) - { - buildPointZone_t *zone = &level.buildPointZones[ i ]; - - zone->active = qfalse; - zone->totalBuildPoints = g_humanRepeaterBuildPoints.integer; - } - - // Iterate through entities - for( i = MAX_CLIENTS; i < level.num_entities; i++ ) - { - gentity_t *ent = &g_entities[ i ]; - buildPointZone_t *zone; - buildable_t buildable; - int cost; - - if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD ) - continue; - - // mark a zone as active - if( ent->usesBuildPointZone ) - { - assert( ent->buildPointZone >= 0 && ent->buildPointZone < g_humanRepeaterMaxZones.integer ); - - zone = &level.buildPointZones[ ent->buildPointZone ]; - zone->active = qtrue; - } - - // Subtract the BP from the appropriate pool - buildable = ent->s.modelindex; - cost = BG_Buildable( buildable, ent->cuboidSize )->buildPoints; - - if( ent->buildableTeam == TEAM_ALIENS ) - level.alienBuildPoints -= cost; - if( buildable == BA_H_REPEATER ) - level.humanBuildPoints -= cost; - else if( buildable != BA_H_REACTOR ) - { - gentity_t *power = G_PowerEntityForEntity( ent ); - - if( power ) - { - if( power->s.modelindex == BA_H_REACTOR ) - level.humanBuildPoints -= cost; - else if( power->s.modelindex == BA_H_REPEATER && power->usesBuildPointZone ) - level.buildPointZones[ power->buildPointZone ].totalBuildPoints -= cost; - } - } - } - - // Finally, update repeater zones and their queues - // note that this has to be done after the used BP is calculated - for( i = MAX_CLIENTS; i < level.num_entities; i++ ) - { - gentity_t *ent = &g_entities[ i ]; - - if( ent->s.eType != ET_BUILDABLE || ent->s.eFlags & EF_DEAD || - ent->buildableTeam != TEAM_HUMANS ) - continue; - - buildable = ent->s.modelindex; - - if( buildable != BA_H_REPEATER ) - continue; - - if( ent->usesBuildPointZone && level.buildPointZones[ ent->buildPointZone ].active ) - { - zone = &level.buildPointZones[ ent->buildPointZone ]; - - if( G_TimeTilSuddenDeath( ) > 0 ) - { - // BP queue updates - while( zone->queuedBuildPoints > 0 && - zone->nextQueueTime < level.time ) - { - zone->queuedBuildPoints--; - zone->nextQueueTime += G_NextQueueTime( zone->queuedBuildPoints, - zone->totalBuildPoints, - g_humanRepeaterBuildQueueTime.integer ); - } - } - else - { - zone->totalBuildPoints = zone->queuedBuildPoints = 0; - } - } - } - - if( level.humanBuildPoints < 0 ) - level.humanBuildPoints = 0; - - if( level.alienBuildPoints < 0 ) - level.alienBuildPoints = 0; -} - -/* -============ -G_CalculateStages -============ -*/ -void G_CalculateStages( void ) -{ - float alienPlayerCountMod = level.averageNumAlienClients / PLAYER_COUNT_MOD; - float humanPlayerCountMod = level.averageNumHumanClients / PLAYER_COUNT_MOD; - int alienNextStageThreshold, humanNextStageThreshold; - static int lastAlienStageModCount = 1; - static int lastHumanStageModCount = 1; - - if( alienPlayerCountMod < 0.1f ) - alienPlayerCountMod = 0.1f; - - if( humanPlayerCountMod < 0.1f ) - humanPlayerCountMod = 0.1f; - - if( g_alienCredits.integer >= - (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ) && - g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) - { - trap_Cvar_Set( "g_alienStage", va( "%d", S2 ) ); - level.alienStage2Time = level.time; - lastAlienStageModCount = g_alienStage.modificationCount; - G_LogPrintf("Stage: A 2: Aliens reached Stage 2\n"); - } - - if( g_alienCredits.integer >= - (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ) && - g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) - { - trap_Cvar_Set( "g_alienStage", va( "%d", S3 ) ); - level.alienStage3Time = level.time; - lastAlienStageModCount = g_alienStage.modificationCount; - G_LogPrintf("Stage: A 3: Aliens reached Stage 3\n"); - } - - if( g_humanCredits.integer >= - (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ) && - g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) - { - trap_Cvar_Set( "g_humanStage", va( "%d", S2 ) ); - level.humanStage2Time = level.time; - lastHumanStageModCount = g_humanStage.modificationCount; - G_LogPrintf("Stage: H 2: Humans reached Stage 2\n"); - } - - if( g_humanCredits.integer >= - (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ) && - g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) - { - trap_Cvar_Set( "g_humanStage", va( "%d", S3 ) ); - level.humanStage3Time = level.time; - lastHumanStageModCount = g_humanStage.modificationCount; - G_LogPrintf("Stage: H 3: Humans reached Stage 3\n"); - } - - if( g_alienStage.modificationCount > lastAlienStageModCount ) - { - G_Checktrigger_stages( TEAM_ALIENS, g_alienStage.integer ); - - if( g_alienStage.integer == S2 ) - level.alienStage2Time = level.time; - else if( g_alienStage.integer == S3 ) - level.alienStage3Time = level.time; - - lastAlienStageModCount = g_alienStage.modificationCount; - } - - if( g_humanStage.modificationCount > lastHumanStageModCount ) - { - G_Checktrigger_stages( TEAM_HUMANS, g_humanStage.integer ); - - if( g_humanStage.integer == S2 ) - level.humanStage2Time = level.time; - else if( g_humanStage.integer == S3 ) - level.humanStage3Time = level.time; - - lastHumanStageModCount = g_humanStage.modificationCount; - } - - if( g_alienStage.integer == S1 && g_alienMaxStage.integer > S1 ) - alienNextStageThreshold = (int)( ceil( (float)g_alienStage2Threshold.integer * alienPlayerCountMod ) ); - else if( g_alienStage.integer == S2 && g_alienMaxStage.integer > S2 ) - alienNextStageThreshold = (int)( ceil( (float)g_alienStage3Threshold.integer * alienPlayerCountMod ) ); - else - alienNextStageThreshold = -1; - - if( g_humanStage.integer == S1 && g_humanMaxStage.integer > S1 ) - humanNextStageThreshold = (int)( ceil( (float)g_humanStage2Threshold.integer * humanPlayerCountMod ) ); - else if( g_humanStage.integer == S2 && g_humanMaxStage.integer > S2 ) - humanNextStageThreshold = (int)( ceil( (float)g_humanStage3Threshold.integer * humanPlayerCountMod ) ); - else - humanNextStageThreshold = -1; - - // save a lot of bandwidth by rounding thresholds up to the nearest 100 - if( alienNextStageThreshold > 0 ) - alienNextStageThreshold = ceil( (float)alienNextStageThreshold / 100 ) * 100; - - if( humanNextStageThreshold > 0 ) - humanNextStageThreshold = ceil( (float)humanNextStageThreshold / 100 ) * 100; - - trap_SetConfigstring( CS_ALIEN_STAGES, va( "%d %d %d", - g_alienStage.integer, g_alienCredits.integer, - alienNextStageThreshold ) ); - - trap_SetConfigstring( CS_HUMAN_STAGES, va( "%d %d %d", - g_humanStage.integer, g_humanCredits.integer, - humanNextStageThreshold ) ); -} - -/* -============ -CalculateAvgPlayers - -Calculates the average number of players playing this game -============ -*/ -void G_CalculateAvgPlayers( void ) -{ - //there are no clients or only spectators connected, so - //reset the number of samples in order to avoid the situation - //where the average tends to 0 - if( !level.numAlienClients ) - { - level.numAlienSamples = 0; - trap_Cvar_Set( "g_alienCredits", "0" ); - } - - if( !level.numHumanClients ) - { - level.numHumanSamples = 0; - trap_Cvar_Set( "g_humanCredits", "0" ); - } - - //calculate average number of clients for stats - level.averageNumAlienClients = - ( ( level.averageNumAlienClients * level.numAlienSamples ) - + level.numAlienClients ) / - (float)( level.numAlienSamples + 1 ); - level.numAlienSamples++; - - level.averageNumHumanClients = - ( ( level.averageNumHumanClients * level.numHumanSamples ) - + level.numHumanClients ) / - (float)( level.numHumanSamples + 1 ); - level.numHumanSamples++; -} - -/* -============ -CalculateRanks - -Recalculates the score ranks of all players -This will be called on every client connect, begin, disconnect, death, -and team change. -============ -*/ -void CalculateRanks( void ) -{ - int i; - char P[ MAX_CLIENTS + 1 ] = {""}; - - level.numConnectedClients = 0; - level.numPlayingClients = 0; - memset( level.numVotingClients, 0, sizeof( level.numVotingClients ) ); - level.numAlienClients = 0; - level.numHumanClients = 0; - level.numLiveAlienClients = 0; - level.numLiveHumanClients = 0; - - for( i = 0; i < level.maxclients; i++ ) - { - P[ i ] = '-'; - if ( level.clients[ i ].pers.connected != CON_DISCONNECTED ) - { - level.sortedClients[ level.numConnectedClients ] = i; - level.numConnectedClients++; - P[ i ] = (char)'0' + level.clients[ i ].pers.teamSelection; - - level.numVotingClients[ TEAM_NONE ]++; - - if( level.clients[ i ].pers.connected != CON_CONNECTED ) - continue; - - if( level.clients[ i ].pers.teamSelection != TEAM_NONE ) - { - level.numPlayingClients++; - if( level.clients[ i ].pers.teamSelection == TEAM_ALIENS ) - { - level.numAlienClients++; - if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) - level.numLiveAlienClients++; - } - else if( level.clients[ i ].pers.teamSelection == TEAM_HUMANS ) - { - level.numHumanClients++; - if( level.clients[ i ].sess.spectatorState == SPECTATOR_NOT ) - level.numLiveHumanClients++; - } - } - } - } - level.numNonSpectatorClients = level.numLiveAlienClients + - level.numLiveHumanClients; - level.numVotingClients[ TEAM_ALIENS ] = level.numAlienClients; - level.numVotingClients[ TEAM_HUMANS ] = level.numHumanClients; - P[ i ] = '\0'; - trap_Cvar_Set( "P", P ); - - qsort( level.sortedClients, level.numConnectedClients, - sizeof( level.sortedClients[ 0 ] ), SortRanks ); - - // see if it is time to end the level - CheckExitRules( ); - - // if we are at the intermission, send the new info to everyone - if( level.intermissiontime ) - SendScoreboardMessageToAllClients( ); -} - - -/* -======================================================================== - -MAP CHANGING - -======================================================================== -*/ - -/* -======================== -SendScoreboardMessageToAllClients - -Do this at BeginIntermission time and whenever ranks are recalculated -due to enters/exits/forced team changes -======================== -*/ -void SendScoreboardMessageToAllClients( void ) -{ - int i; - - for( i = 0; i < level.maxclients; i++ ) - { - if( level.clients[ i ].pers.connected == CON_CONNECTED ) - ScoreboardMessage( g_entities + i ); - } -} - -/* -======================== -MoveClientToIntermission - -When the intermission starts, this will be called for all players. -If a new client connects, this will be called after the spawn function. -======================== -*/ -void MoveClientToIntermission( gentity_t *ent ) -{ - // take out of follow mode if needed - if( ent->client->sess.spectatorState == SPECTATOR_FOLLOW ) - G_StopFollowing( ent ); - - // move to the spot - VectorCopy( level.intermission_origin, ent->s.origin ); - VectorCopy( level.intermission_origin, ent->client->ps.origin ); - VectorCopy( level.intermission_angle, ent->client->ps.viewangles ); - ent->client->ps.pm_type = PM_INTERMISSION; - - // clean up powerup info - memset( ent->client->ps.misc, 0, sizeof( ent->client->ps.misc ) ); - - ent->client->ps.eFlags = 0; - ent->s.eFlags = 0; - ent->s.eType = ET_GENERAL; - ent->s.modelindex = 0; - ent->s.loopSound = 0; - ent->s.event = 0; - ent->r.contents = 0; -} - -/* -================== -FindIntermissionPoint - -This is also used for spectator spawns -================== -*/ -void FindIntermissionPoint( void ) -{ - gentity_t *ent, *target; - vec3_t dir; - - // find the intermission spot - ent = G_Find( NULL, FOFS( classname ), "info_player_intermission" ); - - if( !ent ) - { // the map creator forgot to put in an intermission point... - G_SelectSpawnPoint( vec3_origin, level.intermission_origin, level.intermission_angle ); - } - else - { - VectorCopy( ent->s.origin, level.intermission_origin ); - VectorCopy( ent->s.angles, level.intermission_angle ); - // if it has a target, look towards it - if( ent->target ) - { - target = G_PickTarget( ent->target ); - - if( target ) - { - VectorSubtract( target->s.origin, level.intermission_origin, dir ); - vectoangles( dir, level.intermission_angle ); - } - } - } - -} - -/* -================== -BeginIntermission -================== -*/ -void BeginIntermission( void ) -{ - int i; - gentity_t *client; - - if( level.intermissiontime ) - return; // already active - - level.intermissiontime = level.time; - - G_ClearVotes( ); - - G_UpdateTeamConfigStrings( ); - - FindIntermissionPoint( ); - - // move all clients to the intermission point - for( i = 0; i < level.maxclients; i++ ) - { - client = g_entities + i; - - if( !client->inuse ) - continue; - - // respawn if dead - if( client->health <= 0 ) - respawn(client); - - MoveClientToIntermission( client ); - } - - // send the current scoring to all clients - SendScoreboardMessageToAllClients( ); -} - - -/* -============= -ExitLevel - -When the intermission has been exited, the server is either moved -to a new map based on the map rotation or the current map restarted -============= -*/ -void ExitLevel( void ) -{ - int i; - gclient_t *cl; - - if ( G_MapExists( g_nextMap.string ) ) - trap_SendConsoleCommand( EXEC_APPEND, va("map \"%s\"\n", g_nextMap.string ) ); - else if( G_MapRotationActive( ) ) - G_AdvanceMapRotation( 0 ); - else - trap_SendConsoleCommand( EXEC_APPEND, "map_restart\n" ); - - trap_Cvar_Set( "g_nextMap", "" ); - - level.restarted = qtrue; - level.changemap = NULL; - level.intermissiontime = 0; - - // reset all the scores so we don't enter the intermission again - for( i = 0; i < g_maxclients.integer; i++ ) - { - cl = level.clients + i; - if( cl->pers.connected != CON_CONNECTED ) - continue; - - cl->ps.persistant[ PERS_SCORE ] = 0; - } - - // we need to do this here before chaning to CON_CONNECTING - G_WriteSessionData( ); - - // change all client states to connecting, so the early players into the - // next level will know the others aren't done reconnecting - for( i = 0; i < g_maxclients.integer; i++ ) - { - if( level.clients[ i ].pers.connected == CON_CONNECTED ) - level.clients[ i ].pers.connected = CON_CONNECTING; - } - -} - -/* -================= -G_AdminMessage - -Print to all active server admins, and to the logfile, and to the server console -================= -*/ -void G_AdminMessage( gentity_t *ent, const char *msg ) -{ - char string[ 1024 ]; - int i; - - Com_sprintf( string, sizeof( string ), "chat %d %d \"%s\"", - ent ? ent - g_entities : -1, - G_admin_permission( ent, ADMF_ADMINCHAT ) ? SAY_ADMINS : SAY_ADMINS_PUBLIC, - msg ); - - // Send to all appropriate clients - for( i = 0; i < level.maxclients; i++ ) - if( G_admin_permission( &g_entities[ i ], ADMF_ADMINCHAT ) ) - trap_SendServerCommand( i, string ); - - // Send to the logfile and server console - G_LogPrintf( "%s: %d \"%s" S_COLOR_WHITE "\": " S_COLOR_MAGENTA "%s\n", - G_admin_permission( ent, ADMF_ADMINCHAT ) ? "AdminMsg" : "AdminMsgPublic", - ent ? ent - g_entities : -1, ent ? ent->client->pers.netname : "console", - msg ); -} - - -/* -================= -G_LogPrintf - -Print to the logfile with a time stamp if it is open, and to the server console -================= -*/ -void QDECL G_LogPrintf( const char *fmt, ... ) -{ - va_list argptr; - char string[ 1024 ], decolored[ 1024 ]; - int min, tens, sec; - - sec = ( level.time - level.startTime ) / 1000; - - min = sec / 60; - sec -= min * 60; - tens = sec / 10; - sec -= tens * 10; - - Com_sprintf( string, sizeof( string ), "%3i:%i%i ", min, tens, sec ); - - va_start( argptr, fmt ); - Q_vsnprintf( string + 7, sizeof( string ) - 7, fmt, argptr ); - va_end( argptr ); - - if( g_dedicated.integer ) - { - G_UnEscapeString( string, decolored, sizeof( decolored ) ); - G_Printf( "%s", decolored + 7 ); - } - - if( !level.logFile ) - return; - - G_DecolorString( string, decolored, sizeof( decolored ) ); - trap_FS_Write( decolored, strlen( decolored ), level.logFile ); -} - -/* -================= -G_SendGameStat -================= -*/ -void G_SendGameStat( team_t team ) -{ - char map[ MAX_STRING_CHARS ]; - char teamChar; - char data[ BIG_INFO_STRING ]; - char entry[ MAX_STRING_CHARS ]; - int i, dataLength, entryLength; - gclient_t *cl; - - // games with cheats enabled are not very good for balance statistics - if( g_cheats.integer ) - return; - - trap_Cvar_VariableStringBuffer( "mapname", map, sizeof( map ) ); - - switch( team ) - { - case TEAM_ALIENS: teamChar = 'A'; break; - case TEAM_HUMANS: teamChar = 'H'; break; - case TEAM_NONE: teamChar = 'L'; break; - default: return; - } - - Com_sprintf( data, BIG_INFO_STRING, - "%s %s T:%c A:%f H:%f M:%s D:%d SD:%d AS:%d AS2T:%d AS3T:%d HS:%d HS2T:%d HS3T:%d CL:%d", - Q3_VERSION, - g_tag.string, - teamChar, - level.averageNumAlienClients, - level.averageNumHumanClients, - map, - level.time - level.startTime, - G_TimeTilSuddenDeath( ), - g_alienStage.integer, - level.alienStage2Time - level.startTime, - level.alienStage3Time - level.startTime, - g_humanStage.integer, - level.humanStage2Time - level.startTime, - level.humanStage3Time - level.startTime, - level.numConnectedClients ); - - dataLength = strlen( data ); - - for( i = 0; i < level.numConnectedClients; i++ ) - { - int ping; - - cl = &level.clients[ level.sortedClients[ i ] ]; - - if( cl->pers.connected == CON_CONNECTING ) - ping = -1; - else - ping = cl->ps.ping < 999 ? cl->ps.ping : 999; - - switch( cl->ps.stats[ STAT_TEAM ] ) - { - case TEAM_ALIENS: teamChar = 'A'; break; - case TEAM_HUMANS: teamChar = 'H'; break; - case TEAM_NONE: teamChar = 'S'; break; - default: return; - } - - Com_sprintf( entry, MAX_STRING_CHARS, - " \"%s\" %c %d %d %d", - cl->pers.netname, - teamChar, - cl->ps.persistant[ PERS_SCORE ], - ping, - ( level.time - cl->pers.enterTime ) / 60000 ); - - entryLength = strlen( entry ); - - if( dataLength + entryLength >= BIG_INFO_STRING ) - break; - - strcpy( data + dataLength, entry ); - dataLength += entryLength; - } - - trap_SendGameStat( data ); -} - -/* -================ -LogExit - -Append information about this game to the log file -================ -*/ -void LogExit( const char *string ) -{ - int i, numSorted; - gclient_t *cl; - gentity_t *ent; - - G_LogPrintf( "Exit: %s\n", string ); - - level.intermissionQueued = level.time; - - // this will keep the clients from playing any voice sounds - // that will get cut off when the queued intermission starts - trap_SetConfigstring( CS_INTERMISSION, "1" ); - - // don't send more than 32 scores (FIXME?) - numSorted = level.numConnectedClients; - if( numSorted > 32 ) - numSorted = 32; - - for( i = 0; i < numSorted; i++ ) - { - int ping; - - cl = &level.clients[ level.sortedClients[ i ] ]; - - if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE ) - continue; - - if( cl->pers.connected == CON_CONNECTING ) - continue; - - ping = cl->ps.ping < 999 ? cl->ps.ping : 999; - - G_LogPrintf( "score: %i ping: %i client: %i %s\n", - cl->ps.persistant[ PERS_SCORE ], ping, level.sortedClients[ i ], - cl->pers.netname ); - - } - - for( i = 1, ent = g_entities + i ; i < level.num_entities ; i++, ent++ ) - { - if( !ent->inuse ) - continue; - - if( !Q_stricmp( ent->classname, "trigger_win" ) ) - { - if( level.lastWin == ent->stageTeam ) - ent->use( ent, ent, ent ); - } - } - - G_SendGameStat( level.lastWin ); -} - - -/* -================= -CheckIntermissionExit - -The level will stay at the intermission for a minimum of 5 seconds -If all players wish to continue, the level will then exit. -If one or more players have not acknowledged the continue, the game will -wait 10 seconds before going on. -================= -*/ -void CheckIntermissionExit( void ) -{ - int ready, notReady; - int i; - gclient_t *cl; - clientList_t readyMasks; - - //if no clients are connected, just exit - if( level.numConnectedClients == 0 ) - { - ExitLevel( ); - return; - } - - // see which players are ready - ready = 0; - notReady = 0; - Com_Memset( &readyMasks, 0, sizeof( readyMasks ) ); - for( i = 0; i < g_maxclients.integer; i++ ) - { - cl = level.clients + i; - - if( cl->pers.connected != CON_CONNECTED ) - continue; - - if( cl->ps.stats[ STAT_TEAM ] == TEAM_NONE ) - continue; - - if( cl->readyToExit ) - { - ready++; - - Com_ClientListAdd( &readyMasks, i ); - } - else - notReady++; - } - - trap_SetConfigstring( CS_CLIENTS_READY, Com_ClientListString( &readyMasks ) ); - - // never exit in less than five seconds - if( level.time < level.intermissiontime + 5000 ) - return; - - // never let intermission go on for over 1 minute - if( level.time > level.intermissiontime + 60000 ) - { - ExitLevel( ); - return; - } - - // if nobody wants to go, clear timer - if( ready == 0 && notReady > 0 ) - { - level.readyToExit = qfalse; - return; - } - - // if everyone wants to go, go now - if( notReady == 0 ) - { - ExitLevel( ); - return; - } - - // the first person to ready starts the thirty second timeout - if( !level.readyToExit ) - { - level.readyToExit = qtrue; - level.exitTime = level.time; - } - - // if we have waited thirty seconds since at least one player - // wanted to exit, go ahead - if( level.time < level.exitTime + 30000 ) - return; - - ExitLevel( ); -} - -/* -============= -ScoreIsTied -============= -*/ -qboolean ScoreIsTied( void ) -{ - int a, b; - - if( level.numPlayingClients < 2 ) - return qfalse; - - a = level.clients[ level.sortedClients[ 0 ] ].ps.persistant[ PERS_SCORE ]; - b = level.clients[ level.sortedClients[ 1 ] ].ps.persistant[ PERS_SCORE ]; - - return a == b; -} - -/* -================= -CheckExitRules - -There will be a delay between the time the exit is qualified for -and the time everyone is moved to the intermission spot, so you -can see the last frag. -================= -*/ -void CheckExitRules( void ) -{ - // if at the intermission, wait for all non-bots to - // signal ready, then go to next level - if( level.intermissiontime ) - { - CheckIntermissionExit( ); - return; - } - - if( level.intermissionQueued ) - { - if( level.time - level.intermissionQueued >= INTERMISSION_DELAY_TIME ) - { - level.intermissionQueued = 0; - BeginIntermission( ); - } - - return; - } - - if( g_timelimit.integer ) - { - if( level.time - level.startTime >= g_timelimit.integer * 60000 ) - { - level.lastWin = TEAM_NONE; - trap_SendServerCommand( -1, "print \"Timelimit hit\n\"" ); - trap_SetConfigstring( CS_WINNER, "Stalemate" ); - LogExit( "Timelimit hit." ); - return; - } - else if( level.time - level.startTime >= ( g_timelimit.integer - 5 ) * 60000 && - level.timelimitWarning < TW_IMMINENT ) - { - trap_SendServerCommand( -1, "cp \"5 minutes remaining!\"" ); - level.timelimitWarning = TW_IMMINENT; - } - else if( level.time - level.startTime >= ( g_timelimit.integer - 1 ) * 60000 && - level.timelimitWarning < TW_PASSED ) - { - trap_SendServerCommand( -1, "cp \"1 minute remaining!\"" ); - level.timelimitWarning = TW_PASSED; - } - } - - if( level.uncondHumanWin || - ( !level.uncondAlienWin && - ( level.time > level.startTime + 1000 ) && - ( level.numAlienSpawns == 0 ) && - ( level.numLiveAlienClients == 0 ) ) ) - { - //humans win - level.lastWin = TEAM_HUMANS; - trap_SendServerCommand( -1, "print \"Humans win\n\""); - trap_SetConfigstring( CS_WINNER, "Humans Win" ); - LogExit( "Humans win." ); - } - else if( level.uncondAlienWin || - ( ( level.time > level.startTime + 1000 ) && - ( level.numHumanSpawns == 0 ) && - ( level.numLiveHumanClients == 0 ) ) ) - { - //aliens win - level.lastWin = TEAM_ALIENS; - trap_SendServerCommand( -1, "print \"Aliens win\n\""); - trap_SetConfigstring( CS_WINNER, "Aliens Win" ); - LogExit( "Aliens win." ); - } -} - -/* -================== -G_Vote -================== -*/ -void G_Vote( gentity_t *ent, team_t team, qboolean voting ) -{ - if( !level.voteTime[ team ] ) - return; - - if( voting && ent->client->pers.voted & ( 1 << team ) ) - return; - - if( !voting && !( ent->client->pers.voted & ( 1 << team ) ) ) - return; - - ent->client->pers.voted |= 1 << team; - - if( ent->client->pers.vote & ( 1 << team ) ) - { - if( voting ) - level.voteYes[ team ]++; - else - level.voteYes[ team ]--; - - trap_SetConfigstring( CS_VOTE_YES + team, - va( "%d", level.voteYes[ team ] ) ); - } - else - { - if( voting ) - level.voteNo[ team ]++; - else - level.voteNo[ team ]--; - - trap_SetConfigstring( CS_VOTE_NO + team, - va( "%d", level.voteNo[ team ] ) ); - } -} - - -/* -======================================================================== - -FUNCTIONS CALLED EVERY FRAME - -======================================================================== -*/ - - -void G_ExecuteVote( team_t team ) -{ - level.voteExecuteTime[ team ] = 0; - - trap_SendConsoleCommand( EXEC_APPEND, va( "%s\n", - level.voteString[ team ] ) ); - - if( !Q_stricmpn( level.voteString[ team ], "map", 3 ) ) - level.restarted = qtrue; -} - -/* -================== -G_CheckVote -================== -*/ -void G_CheckVote( team_t team ) -{ - float votePassThreshold = (float)level.voteThreshold[ team ] / 100.0f; - qboolean pass = qfalse; - char *msg; - int i; - - if( level.voteExecuteTime[ team ] && - level.voteExecuteTime[ team ] < level.time ) - { - G_ExecuteVote( team ); - } - - if( !level.voteTime[ team ] ) - return; - - if( ( level.time - level.voteTime[ team ] >= VOTE_TIME ) || - ( level.voteYes[ team ] + level.voteNo[ team ] == level.numVotingClients[ team ] ) ) - { - pass = ( level.voteYes[ team ] && - (float)level.voteYes[ team ] / ( (float)level.voteYes[ team ] + (float)level.voteNo[ team ] ) > votePassThreshold ); - } - else - { - if( (float)level.voteYes[ team ] > - (float)level.numVotingClients[ team ] * votePassThreshold ) - { - pass = qtrue; - } - else if( (float)level.voteNo[ team ] <= - (float)level.numVotingClients[ team ] * ( 1.0f - votePassThreshold ) ) - { - return; - } - } - - if( pass ) - level.voteExecuteTime[ team ] = level.time + level.voteDelay[ team ]; - - G_LogPrintf( "EndVote: %s %s %d %d %d\n", - team == TEAM_NONE ? "global" : BG_TeamName( team ), - pass ? "pass" : "fail", - level.voteYes[ team ], level.voteNo[ team ], level.numVotingClients[ team ] ); - - msg = va( "print \"%sote %sed (%d - %d)\n\"", - team == TEAM_NONE ? "V" : "Team v", pass ? "pass" : "fail", - level.voteYes[ team ], level.voteNo[ team ] ); - - if( team == TEAM_NONE ) - trap_SendServerCommand( -1, msg ); - else - G_TeamCommand( team, msg ); - - level.voteTime[ team ] = 0; - level.voteYes[ team ] = 0; - level.voteNo[ team ] = 0; - - for( i = 0; i < level.maxclients; i++ ) - level.clients[ i ].pers.voted &= ~( 1 << team ); - - trap_SetConfigstring( CS_VOTE_TIME + team, "" ); - trap_SetConfigstring( CS_VOTE_STRING + team, "" ); - trap_SetConfigstring( CS_VOTE_YES + team, "0" ); - trap_SetConfigstring( CS_VOTE_NO + team, "0" ); -} - - -/* -================== -CheckCvars -================== -*/ -void CheckCvars( void ) -{ - static int lastPasswordModCount = -1; - static int lastMarkDeconModCount = -1; - static int lastSDTimeModCount = -1; - static int lastNumZones = 0; - - if( g_password.modificationCount != lastPasswordModCount ) - { - lastPasswordModCount = g_password.modificationCount; - - if( *g_password.string && Q_stricmp( g_password.string, "none" ) ) - trap_Cvar_Set( "g_needpass", "1" ); - else - trap_Cvar_Set( "g_needpass", "0" ); - } - - // Unmark any structures for deconstruction when - // the server setting is changed - if( g_markDeconstruct.modificationCount != lastMarkDeconModCount ) - { - lastMarkDeconModCount = g_markDeconstruct.modificationCount; - G_ClearDeconMarks( ); - } - - // If we change g_suddenDeathTime during a map, we need to update - // when sd will begin - if( g_suddenDeathTime.modificationCount != lastSDTimeModCount ) - { - lastSDTimeModCount = g_suddenDeathTime.modificationCount; - level.suddenDeathBeginTime = g_suddenDeathTime.integer * 60000; - } - - // If the number of zones changes, we need a new array - if( g_humanRepeaterMaxZones.integer != lastNumZones ) - { - buildPointZone_t *newZones; - size_t newsize = g_humanRepeaterMaxZones.integer * sizeof( buildPointZone_t ); - size_t oldsize = lastNumZones * sizeof( buildPointZone_t ); - - newZones = BG_Alloc( newsize ); - if( level.buildPointZones ) - { - Com_Memcpy( newZones, level.buildPointZones, MIN( oldsize, newsize ) ); - BG_Free( level.buildPointZones ); - } - - level.buildPointZones = newZones; - lastNumZones = g_humanRepeaterMaxZones.integer; - } - - level.frameMsec = trap_Milliseconds( ); -} - -/* -============= -G_RunThink - -Runs thinking code for this frame if necessary -============= -*/ -void G_RunThink( gentity_t *ent ) -{ - float thinktime; - - thinktime = ent->nextthink; - if( thinktime <= 0 ) - return; - - if( thinktime > level.time ) - return; - - ent->nextthink = 0; - if( !ent->think ) - G_Error( "NULL ent->think" ); - - ent->think( ent ); -} - -/* -============= -G_EvaluateAcceleration - -Calculates the acceleration for an entity -============= -*/ -void G_EvaluateAcceleration( gentity_t *ent, int msec ) -{ - vec3_t deltaVelocity; - vec3_t deltaAccel; - - VectorSubtract( ent->s.pos.trDelta, ent->oldVelocity, deltaVelocity ); - VectorScale( deltaVelocity, 1.0f / (float)msec, ent->acceleration ); - - VectorSubtract( ent->acceleration, ent->oldAccel, deltaAccel ); - VectorScale( deltaAccel, 1.0f / (float)msec, ent->jerk ); - - VectorCopy( ent->s.pos.trDelta, ent->oldVelocity ); - VectorCopy( ent->acceleration, ent->oldAccel ); -} - -/* -================ -G_RunFrame - -Advances the non-player objects in the world -================ -*/ -void G_RunFrame( int levelTime ) -{ - int i; - gentity_t *ent; - int msec; - static int ptime3000 = 0; - - // if we are waiting for the level to restart, do nothing - if( level.restarted ) - return; - - if( level.pausedTime ) - { - msec = levelTime - level.time - level.pausedTime; - level.pausedTime = levelTime - level.time; - - ptime3000 += msec; - while( ptime3000 > 3000 ) - { - ptime3000 -= 3000; - trap_SendServerCommand( -1, "cp \"The game has been paused. Please wait.\"" ); - - if( level.pausedTime >= 110000 && level.pausedTime <= 119000 ) - trap_SendServerCommand( -1, va( "print \"Server: Game will auto-unpause in %d seconds\n\"", - (int) ( (float) ( 120000 - level.pausedTime ) / 1000.0f ) ) ); - } - - // Prevents clients from getting lagged-out messages - for( i = 0; i < level.maxclients; i++ ) - { - if( level.clients[ i ].pers.connected == CON_CONNECTED ) - level.clients[ i ].ps.commandTime = levelTime; - } - - if( level.pausedTime > 120000 ) - { - trap_SendServerCommand( -1, "print \"Server: The game has been unpaused automatically (2 minute max)\n\"" ); - trap_SendServerCommand( -1, "cp \"The game has been unpaused!\"" ); - level.pausedTime = 0; - } - - return; - } - - level.framenum++; - level.previousTime = level.time; - level.time = levelTime; - msec = level.time - level.previousTime; - - // get any cvar changes - G_UpdateCvars( ); - CheckCvars( ); - // now we are done spawning - level.spawning = qfalse; - - // - // go through all allocated objects - // - ent = &g_entities[ 0 ]; - - for( i = 0; i < level.num_entities; i++, ent++ ) - { - if( !ent->inuse ) - continue; - - // clear events that are too old - if( level.time - ent->eventTime > EVENT_VALID_MSEC ) - { - if( ent->s.event ) - { - ent->s.event = 0; // &= EV_EVENT_BITS; - if ( ent->client ) - { - ent->client->ps.externalEvent = 0; - //ent->client->ps.events[0] = 0; - //ent->client->ps.events[1] = 0; - } - } - - if( ent->freeAfterEvent ) - { - // tempEntities or dropped items completely go away after their event - G_FreeEntity( ent ); - continue; - } - else if( ent->unlinkAfterEvent ) - { - // items that will respawn will hide themselves after their pickup event - ent->unlinkAfterEvent = qfalse; - trap_UnlinkEntity( ent ); - } - } - - // temporary entities don't think - if( ent->freeAfterEvent ) - continue; - - // calculate the acceleration of this entity - if( ent->evaluateAcceleration ) - G_EvaluateAcceleration( ent, msec ); - - if( !ent->r.linked && ent->neverFree ) - continue; - - if( ent->s.eType == ET_MISSILE ) - { - G_RunMissile( ent ); - continue; - } - - if( ent->s.eType == ET_BUILDABLE ) - { - G_BuildableThink( ent, msec ); - continue; - } - - if( ent->s.eType == ET_CORPSE || ent->physicsObject ) - { - G_Physics( ent, msec ); - continue; - } - - if( ent->s.eType == ET_MOVER ) - { - G_RunMover( ent ); - continue; - } - - if( i < MAX_CLIENTS ) - { - G_RunClient( ent ); - continue; - } - - G_RunThink( ent ); - } - - // perform final fixups on the players - ent = &g_entities[ 0 ]; - - for( i = 0; i < level.maxclients; i++, ent++ ) - { - if( ent->inuse ) - ClientEndFrame( ent ); - } - - // save position information for all active clients - G_UnlaggedStore( ); - - G_CountSpawns( ); - G_CalculateBuildPoints( ); - G_CalculateStages( ); - - G_SpawnClients( TEAM_ALIENS ); - G_SpawnClients( TEAM_HUMANS ); - G_CalculateAvgPlayers( ); - G_UpdateZaps( msec ); - - // see if it is time to end the level - CheckExitRules( ); - - // update to team status? - CheckTeamStatus( ); - - // cancel vote if timed out - for( i = 0; i < NUM_TEAMS; i++ ) - G_CheckVote( i ); - - level.frameMsec = trap_Milliseconds(); -} - -- cgit