diff -urN --exclude=.svn et.orig/src/game/bg_animation.c etpub/src/game/bg_animation.c --- et.orig/src/game/bg_animation.c 2003-05-03 06:50:52.000000000 -0500 +++ etpub/src/game/bg_animation.c 2005-03-01 09:26:12.000000000 -0600 @@ -1422,6 +1422,7 @@ animScriptItem_t *scriptItem = NULL; animScriptCommand_t *scriptCommand = NULL; int state = ps->aiState; + qboolean setTimer = qfalse; // Allow fallen movetype while dead if( ps->eFlags & EF_DEAD && movetype != ANIM_MT_FALLEN && movetype != ANIM_MT_FLAILING ) @@ -1466,9 +1467,25 @@ Com_Printf( "anim1 (%s): %s", animBodyPartsStr[scriptCommand->bodyPart[1]].string, animModelInfo->animations[scriptCommand->animIndex[1]]->name ); Com_Printf( "\n" ); #endif + // tjw: this is a dirty dirty hack and should be removed + // if using a client mod (update the animation script). + // This is here because I added head hitboxes to + // corpses and we can't have their heads leaving the + // ground because the hitboxes can't do that. + if(movetype == ANIM_MT_FALLEN) { + setTimer = qtrue; + if(ps->torsoTimer < 3400) + return( BG_ExecuteCommand(ps, + animModelInfo, + scriptCommand, + setTimer, + qfalse, + qtrue) != -1 ); + + } // run it - return( BG_ExecuteCommand( ps, animModelInfo, scriptCommand, qfalse, isContinue, qfalse ) != -1 ); + return( BG_ExecuteCommand( ps, animModelInfo, scriptCommand, setTimer, isContinue, qfalse ) != -1 ); } /* diff -urN --exclude=.svn et.orig/src/game/bg_local.h etpub/src/game/bg_local.h --- et.orig/src/game/bg_local.h 2003-04-14 17:32:26.000000000 -0500 +++ etpub/src/game/bg_local.h 2005-02-14 12:52:55.000000000 -0600 @@ -75,3 +75,7 @@ void PM_StepSlideMoveProne( qboolean gravity ); void PM_BeginWeaponChange( int oldweapon, int newweapon, qboolean reload ); + +// tjw +extern vmCvar_t g_weapons; +extern vmCvar_t g_spinCorpse; diff -urN --exclude=.svn et.orig/src/game/bg_misc.c etpub/src/game/bg_misc.c --- et.orig/src/game/bg_misc.c 2003-08-28 15:43:22.000000000 -0500 +++ etpub/src/game/bg_misc.c 2005-03-10 16:24:01.000000000 -0600 @@ -13,12 +13,17 @@ #ifdef CGAMEDLL extern vmCvar_t cg_gameType; #define gametypeCvar cg_gameType +#define MISC_UW_AMMO_RESTORES_HELMET 0 // josh: no client mod #elif GAMEDLL extern vmCvar_t g_developer; extern vmCvar_t g_gametype; #define gametypeCvar g_gametype + extern vmCvar_t g_weapons; +#define MISC_UW_AMMO_RESTORES_HELMET \ + (g_weapons.integer & WPF_AMMO_RESTORES_HELMET) #else extern vmCvar_t ui_gameType; +#define MISC_UW_AMMO_RESTORES_HELMET 0 // josh: no ui stuff here #define gametypeCvar ui_gameType #endif @@ -2908,6 +2913,15 @@ int clip; int weapNumOfClips; + // Michael: add the helmet first, then do weapons + // it looks like the way to check if they have a helmet is by + // seeing if they have already been shot in the head + // to add the helmet, just clear the bit + if(MISC_UW_AMMO_RESTORES_HELMET && (ps->eFlags & EF_HEADSHOT)) { + ps->eFlags &= (EF_HEADSHOT ^ 0xFFFFFFFF); + } + + // Gordon: handle grenades first i = BG_GrenadesForClass( ps->stats[STAT_PLAYER_CLASS], skill ); weapon = BG_GrenadeTypeForTeam( teamNum ); @@ -4027,9 +4041,11 @@ s->eFlags = ps->eFlags; - if ( ps->stats[STAT_HEALTH] <= 0 ) { + if(ps->stats[STAT_HEALTH] <= 0 && + !(ps->eFlags & EF_PLAYDEAD)) { s->eFlags |= EF_DEAD; - } else { + } + else { s->eFlags &= ~EF_DEAD; } @@ -4128,9 +4144,11 @@ } s->eFlags = ps->eFlags; - if ( ps->stats[STAT_HEALTH] <= 0 ) { + if(ps->stats[STAT_HEALTH] <= 0 && + !(ps->eFlags & EF_PLAYDEAD)) { s->eFlags |= EF_DEAD; - } else { + } + else { s->eFlags &= ~EF_DEAD; } diff -urN --exclude=.svn et.orig/src/game/bg_pmove.c etpub/src/game/bg_pmove.c --- et.orig/src/game/bg_pmove.c 2003-08-28 15:43:22.000000000 -0500 +++ etpub/src/game/bg_pmove.c 2005-03-12 22:21:38.000000000 -0600 @@ -15,8 +15,14 @@ #ifdef CGAMEDLL #define PM_GameType cg_gameType.integer +#define PM_UW_SYRINGE 0 // tjw: no client mod +#define PM_UW_PLIERS 0 // tjw: no client mod +#define PM_CORPSE_SPIN 0 // tjw: no client mod #elif GAMEDLL #define PM_GameType g_gametype.integer +#define PM_UW_SYRINGE (g_weapons.integer & WPF_UNDERWATER_SYRINGE) +#define PM_UW_PLIERS (g_weapons.integer & WPF_UNDERWATER_PLIERS) +#define PM_CORPSE_SPIN g_spinCorpse.integer #endif #define PM_IsSinglePlayerGame() (PM_GameType == GT_SINGLE_PLAYER || PM_GameType == GT_COOP) @@ -727,6 +733,136 @@ return qtrue; } + +/* + * PM_CheckPlayDead + * see if this player can lay down and look dead +*/ +static qboolean PM_CheckPlayDead (void) +{ + vec3_t org, flatforward, point; + trace_t trace; + + if(pm->ps->pm_type != PM_PLAYDEAD) return qfalse; + + // PM_PLAYDEAD is a one time only pm_type + pm->ps->pm_type = PM_NORMAL; + + if(!(pm->ps->eFlags & EF_PLAYDEAD)) { + if(pm->ps->pm_flags & PMF_LADDER) { + return qfalse; + } + + if(pm->ps->persistant[PERS_HWEAPON_USE] || + pm->ps->eFlags & EF_MOUNTEDTANK) { + + return qfalse; + } + + if(pm->waterlevel > 1) { + return qfalse; + } + + // see if we have the space to go prone + // we know our main body isn't in a solid, check for our legs + flatforward[0] = pml.forward[0]; + flatforward[1] = pml.forward[1]; + flatforward[2] = 0; + + org[0] = pm->ps->origin[0] + flatforward[0] * -32; + org[1] = pm->ps->origin[1] + flatforward[1] * -32; + org[2] = pm->ps->origin[2] + 24.f; // 24 units to play with + + // diff between playerlegsMins and playerlegsMaxs z + 24 units to play with + VectorSet( point, org[0], org[1], org[2] - ( 24.f - 2.4f ) - 24.f ); + + pm->trace (&trace, + org, + playerlegsProneMins, + playerlegsProneMaxs, + point, + pm->ps->clientNum, + pm->tracemask); + + if( trace.startsolid && trace.entityNum >= MAX_CLIENTS ) { + // starting in a solid, no prone at all + return qfalse; + } + else if( trace.fraction == 1.f ) { + // no ground to play dead on, so don't + return qfalse; + } + VectorCopy( trace.endpos, org ); + VectorSet( point, org[0], org[1], org[2] + ( 24.f - 2.4f ) ); + + pm->trace (&trace, + org, + playerlegsProneMins, + playerlegsProneMaxs, + point, + pm->ps->clientNum, + pm->tracemask); + + if (!trace.allsolid || trace.entityNum < MAX_CLIENTS) { + pm->ps->eFlags |= EF_PLAYDEAD; + pm->ps->eFlags |= EF_DEAD; + pm->ps->pm_type = PM_DEAD; + + // client uses stats[STAT_HEALTH] as the indicator for + // revive sprites and player position + pm->ps->stats[STAT_HEALTH] = 0; + + // make the hitbox like a dead guy + pm->maxs[2] = pm->ps->maxs[2] = pm->ps->crouchMaxZ; + return qtrue; + } + } + else { + // see if we have the space to stop playing dead + pm->mins[0] = pm->ps->mins[0]; + pm->mins[1] = pm->ps->mins[1]; + + pm->maxs[0] = pm->ps->maxs[0]; + pm->maxs[1] = pm->ps->maxs[1]; + + pm->mins[2] = pm->ps->mins[2]; + pm->maxs[2] = pm->ps->crouchMaxZ; + + pm->trace( &trace, + pm->ps->origin, + pm->mins, + pm->maxs, + pm->ps->origin, + pm->ps->clientNum, + pm->tracemask ); + + if(trace.allsolid) { + // this would have been turned up to ent->health + // before running a PM_PLAYDEAD. Set it back down + // if we can't stand up. + pm->ps->stats[STAT_HEALTH] = 0; + return qfalse; + } + + // crouch for a bit + pm->ps->pm_flags |= PMF_DUCKED; + + // turn the hitbox back up + pm->maxs[2] = pm->ps->maxs[2] = pm->ps->standViewHeight; + + // stop playdead + pm->ps->eFlags &= ~EF_PLAYDEAD; + pm->ps->eFlags &= ~EF_DEAD; + + // don't jump for a bit + pm->pmext->jumpTime = pm->cmd.serverTime - 650; + pm->ps->jumpTime = pm->cmd.serverTime - 650; + + return qtrue; + } + return qfalse; +} + /* ============== PM_CheckProne @@ -772,11 +908,13 @@ // return qfalse; //} - if( ((pm->ps->pm_flags & PMF_DUCKED && pm->cmd.doubleTap == DT_FORWARD) || - (pm->cmd.wbuttons & WBUTTON_PRONE)) && pm->cmd.serverTime - -pm->pmext->proneTime > 750 ) { + if(((pm->ps->pm_flags & PMF_DUCKED && + pm->cmd.doubleTap == DT_FORWARD) || + (pm->cmd.wbuttons & WBUTTON_PRONE)) && + pm->cmd.serverTime - -pm->pmext->proneTime > 750) { + vec3_t org, flatforward, point; trace_t trace; - // see if we have the space to go prone // we know our main body isn't in a solid, check for our legs flatforward[0] = pml.forward[0]; @@ -812,6 +950,7 @@ pm->ps->eFlags |= EF_PRONE; pm->pmext->proneTime = pm->cmd.serverTime; // timestamp 'go prone' pm->pmext->proneGroundTime = pm->cmd.serverTime; + pm->maxs[2] = pm->ps->crouchMaxZ; } } } @@ -822,7 +961,11 @@ pm->ps->pm_type == PM_DEAD || pm->ps->eFlags & EF_MOUNTEDTANK || pm->cmd.serverTime - pm->pmext->proneGroundTime > 450 || - ((pm->cmd.doubleTap == DT_BACK || pm->cmd.upmove > 10 || pm->cmd.wbuttons & WBUTTON_PRONE) && pm->cmd.serverTime - pm->pmext->proneTime > 750) ) { + ((pm->cmd.doubleTap == DT_BACK || + pm->cmd.upmove > 10 || + pm->cmd.wbuttons & WBUTTON_PRONE) + && pm->cmd.serverTime - pm->pmext->proneTime > 750)) { + trace_t trace; // see if we have the space to stop prone @@ -833,7 +976,6 @@ pm->maxs[1] = pm->ps->maxs[1]; pm->mins[2] = pm->ps->mins[2]; - pm->maxs[2] = pm->ps->crouchMaxZ; pm->trace( &trace, pm->ps->origin, pm->mins, pm->maxs, pm->ps->origin, pm->ps->clientNum, pm->tracemask ); if( !trace.allsolid ) { @@ -887,9 +1029,12 @@ //if( frac > 1.f ) // frac = 1.f; - //pm->maxs[2] = pm->ps->maxs[2] - (frac * (pm->ps->standViewHeight - PRONE_VIEWHEIGHT)); - //pm->ps->viewheight = DEFAULT_VIEWHEIGHT - (frac * (DEFAULT_VIEWHEIGHT - PRONE_VIEWHEIGHT)); // default - prone to get a positive which is subtracted from default - pm->maxs[2] = pm->ps->maxs[2] - pm->ps->standViewHeight - PRONE_VIEWHEIGHT; + + //pm->maxs[2] = pm->ps->maxs[2] - pm->ps->standViewHeight - PRONE_VIEWHEIGHT; + // tjw: it appears that 12 is the magic number + // for the minimum maxs[2] that prevents + // player from getting stuck into the world. + pm->maxs[2] = 12; pm->ps->viewheight = PRONE_VIEWHEIGHT; return( qtrue ); @@ -1423,6 +1568,7 @@ VectorNormalize (pm->ps->velocity); VectorScale (pm->ps->velocity, forward, pm->ps->velocity); } + } @@ -1955,14 +2101,13 @@ if (pm->ps->eFlags & EF_DEAD) { - - //if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) - if ( pm->ps->pm_flags & PMF_FLAILING ) { + if( pm->ps->pm_flags & PMF_FLAILING ) { animResult = BG_AnimScriptAnimation( pm->ps, pm->character->animModelInfo, ANIM_MT_FLAILING, qtrue ); if( !pm->ps->pm_time ) pm->ps->pm_flags &= ~PMF_FLAILING; // the eagle has landed - } else if ( !pm->ps->pm_time && !(pm->ps->pm_flags & PMF_LIMBO) ) { // DHM - Nerve :: before going to limbo, play a wounded/fallen animation + } + else if(!pm->ps->pm_time && !(pm->ps->pm_flags & PMF_LIMBO) ) { // DHM - Nerve :: before going to limbo, play a wounded/fallen animation if ( pm->ps->groundEntityNum == ENTITYNUM_NONE ) { // takeoff! pm->ps->pm_flags |= PMF_FLAILING; @@ -2925,6 +3070,7 @@ */ void PM_CoolWeapons( void ) { int wp; + int maxHeat; // rain - for #172 for(wp=0;wpps->weapon) { if( pm->ps->persistant[PERS_HWEAPON_USE] || pm->ps->eFlags & EF_MOUNTEDTANK ) { - pm->ps->curWeapHeat = ( ( (float)pm->ps->weapHeat[WP_DUMMY_MG42] / MAX_MG42_HEAT) ) * 255.0f; + // rain - floor to prevent 8-bit wrap + pm->ps->curWeapHeat = floor( ( ( (float)pm->ps->weapHeat[WP_DUMMY_MG42] / MAX_MG42_HEAT) ) * 255.0f ); } else { - pm->ps->curWeapHeat = ( ( (float)pm->ps->weapHeat[pm->ps->weapon] / (float)GetAmmoTableData(pm->ps->weapon)->maxHeat) ) * 255.0f; + // rain - #172 - don't divide by 0 + maxHeat = GetAmmoTableData(pm->ps->weapon)->maxHeat; + // rain - floor to prevent 8-bit wrap + if (maxHeat != 0) + pm->ps->curWeapHeat = floor( ( ( (float)pm->ps->weapHeat[pm->ps->weapon] / (float)maxHeat) ) * 255.0f); + else + pm->ps->curWeapHeat = 0; } // if(pm->ps->weapHeat[pm->ps->weapon]) @@ -3128,7 +3281,7 @@ // check for dead player if ( pm->ps->stats[STAT_HEALTH] <= 0 ) { - if(!pm->ps->pm_flags & PMF_LIMBO) { + if(!(pm->ps->pm_flags & PMF_LIMBO)) { PM_CoolWeapons(); } @@ -3149,7 +3302,8 @@ if( pm->ps->weapHeat[WP_DUMMY_MG42] < 0 ) pm->ps->weapHeat[WP_DUMMY_MG42] = 0; - pm->ps->curWeapHeat = ( ( (float)pm->ps->weapHeat[WP_DUMMY_MG42] / MAX_MG42_HEAT) ) * 255.0f; + // rain - floor() to prevent 8-bit wrap + pm->ps->curWeapHeat = floor( ( ( (float)pm->ps->weapHeat[WP_DUMMY_MG42] / MAX_MG42_HEAT) ) * 255.0f ); } if( pm->ps->weaponTime > 0 ) { @@ -3211,7 +3365,11 @@ if( pm->ps->weapHeat[WP_DUMMY_MG42] < 0 ) pm->ps->weapHeat[WP_DUMMY_MG42] = 0; - pm->ps->curWeapHeat = ( ( (float)pm->ps->weapHeat[WP_DUMMY_MG42] / MAX_MG42_HEAT) ) * 255.0f; + // rain - floor() to prevent 8-bit wrap + //pm->ps->curWeapHeat = floor( ( (float)pm->ps->weapHeat[WP_DUMMY_MG42] / MAX_MG42_HEAT) ) * 255.0f; + // tjw: reverse rain's fix since it always sets curWeapHeat to 0. + // is 8-bit wrap even an issue here? + pm->ps->curWeapHeat = ( (float)pm->ps->weapHeat[WP_DUMMY_MG42] / MAX_MG42_HEAT) * 255.0f; } if( pm->ps->weaponTime > 0 ) { @@ -3418,9 +3576,26 @@ if ( pm->ps->weapon == WP_LUGER || pm->ps->weapon == WP_COLT || pm->ps->weapon == WP_SILENCER || pm->ps->weapon == WP_SILENCED_COLT || pm->ps->weapon == WP_KAR98 || pm->ps->weapon == WP_K43 || pm->ps->weapon == WP_CARBINE || pm->ps->weapon == WP_GARAND || pm->ps->weapon == WP_GARAND_SCOPE || pm->ps->weapon == WP_K43_SCOPE || BG_IsAkimboWeapon( pm->ps->weapon ) ) { + // rain - bug #11 - this is wrong, the frame where BUTTON_ATTACK is up + // will eventually be outside of the pmove loop + // rain - XXX - reverted to this behavior: it seems that my other prediction + // fixes have made this work properly, and the other method is a frame + // or two behind. (#275) + // rain - moved releasedFire into pmext instead of ps + // tjw - left releasedFire in ps for now + if ( pm->ps->releasedFire ) { - if ( (pm->cmd.buttons & BUTTON_ATTACK) && pm->ps->weaponTime <= 150 ) { - pm->ps->weaponTime = 0; + if ( (pm->cmd.buttons & BUTTON_ATTACK) ) { + // rain - akimbo weapons only have a 200ms delay, so + // use a shorter time for quickfire (#255) + + if (BG_IsAkimboWeapon(pm->ps->weapon)) { + if (pm->ps->weaponTime <= 50) + pm->ps->weaponTime = 0; + } else { + if (pm->ps->weaponTime <= 150) + pm->ps->weaponTime = 0; + } } } else if (!(pm->cmd.buttons & BUTTON_ATTACK)) { pm->ps->releasedFire = qtrue; @@ -3678,10 +3853,18 @@ pm->ps->weapon != WP_DYNAMITE && pm->ps->weapon != WP_LANDMINE && pm->ps->weapon != WP_TRIPMINE && - pm->ps->weapon != WP_SMOKE_BOMB ) { - PM_AddEvent(EV_NOFIRE_UNDERWATER); // event for underwater 'click' for nofire + pm->ps->weapon != WP_SMOKE_BOMB && + !(pm->ps->weapon == WP_MEDIC_SYRINGE + && PM_UW_SYRINGE) && + !(pm->ps->weapon == WP_PLIERS + && PM_UW_PLIERS) + ) { + + // event for underwater 'click' for nofire + PM_AddEvent(EV_NOFIRE_UNDERWATER); pm->ps->weaponTime = 500; - pm->ps->weaponDelay = 0; // avoid insta-fire after water exit on delayed weapon attacks + // avoid insta-fire after water exit on delayed weapon attacks + pm->ps->weaponDelay = 0; return; } } @@ -4121,10 +4304,10 @@ addTime = GetAmmoTableData(pm->ps->weapon)->nextShotTime; if( !pm->ps->ammoclip[BG_FindClipForWeapon(pm->ps->weapon)] ) { - if( akimboFire ) + if( !akimboFire ) addTime = 2 * GetAmmoTableData(pm->ps->weapon)->nextShotTime; } else if( !pm->ps->ammoclip[BG_FindClipForWeapon(BG_AkimboSidearm(pm->ps->weapon))] ) { - if( !akimboFire ) + if( akimboFire ) addTime = 2 * GetAmmoTableData(pm->ps->weapon)->nextShotTime; } @@ -4474,7 +4657,14 @@ temp = cmd->angles[1] + ps->delta_angles[1]; if( ps->stats[STAT_DEAD_YAW] == 999 ) ps->stats[STAT_DEAD_YAW] = SHORT2ANGLE(temp); - return; // no view changes at all + // tjw: don't let corpses bury their heads in the sand + ps->viewangles[2] = 0; + ps->viewangles[0] = 0; + if(PM_CORPSE_SPIN) { + temp = cmd->angles[1] + ps->delta_angles[1]; + ps->viewangles[1] = SHORT2ANGLE(temp); + } + return; } VectorCopy( ps->viewangles, oldViewAngles ); @@ -4494,7 +4684,7 @@ } ps->viewangles[i] = SHORT2ANGLE(temp); } - + if( BG_PlayerMounted(ps->eFlags) ) { float yaw, oldYaw; float degsSec = MG42_YAWSPEED; @@ -5090,7 +5280,6 @@ if( pm->skill[SK_BATTLE_SENSE] >= 2 ) rechargebase *= 1.6f; } - pm->pmext->sprintTime += rechargebase*pml.frametime; // JPW NERVE adjusted for framerate independence if (pm->pmext->sprintTime > 5000) pm->pmext->sprintTime += rechargebase*pml.frametime; // JPW NERVE adjusted for framerate independence @@ -5150,17 +5339,23 @@ pm->ps->eFlags &= ~(EF_FIRING | EF_ZOOMING); if( pm->cmd.wbuttons & WBUTTON_ZOOM && pm->ps->stats[STAT_HEALTH] >= 0 && !( pm->ps->weaponDelay ) ) { - if(pm->ps->stats[STAT_KEYS] & (1<ps->weapon)) { // don't allow binocs if using the sniper scope - if(!BG_PlayerMounted(pm->ps->eFlags)) { // or if mounted on a weapon - pm->ps->eFlags |= EF_ZOOMING; - } - } - - // don't allow binocs if in the middle of throwing grenade - if( (pm->ps->weapon == WP_GRENADE_LAUNCHER || pm->ps->weapon == WP_GRENADE_PINEAPPLE || pm->ps->weapon == WP_DYNAMITE) && pm->ps->grenadeTimeLeft > 0) { - pm->ps->eFlags &= ~EF_ZOOMING; - } + if(pm->ps->stats[STAT_KEYS] & (1<ps->weapon) && // don't allow binocs if using the sniper scope + !BG_PlayerMounted(pm->ps->eFlags) && // or if mounted on a weapon + // rain - don't allow binocs w/ mounted mob. MG42 or mortar + // either. + pm->ps->weapon != WP_MOBILE_MG42_SET && + pm->ps->weapon != WP_MORTAR_SET) { + + pm->ps->eFlags |= EF_ZOOMING; + } + } + // don't allow binocs if in the middle of throwing grenade + if( (pm->ps->weapon == WP_GRENADE_LAUNCHER || + pm->ps->weapon == WP_GRENADE_PINEAPPLE || + pm->ps->weapon == WP_DYNAMITE) && + pm->ps->grenadeTimeLeft > 0) { + pm->ps->eFlags &= ~EF_ZOOMING; } } @@ -5315,14 +5510,14 @@ #endif } + // set watertype, and waterlevel PM_SetWaterLevel(); pml.previous_waterlevel = pmove->waterlevel; + PM_CheckPlayDead(); + // set mins, maxs, and viewheight - //if( !PM_CheckProne() ) { -// PM_CheckDuck (); - //} if( !PM_CheckProne() ) { PM_CheckDuck(); } @@ -5484,8 +5679,13 @@ } } else { - if ( msec > 66 ) { - msec = 66; + // rain - this was 66 (15fps), but I've changed it to + // 50 (20fps, max rate of mg42) to alleviate some of the + // framerate dependency with the mg42. + // in reality, this should be split according to sv_fps, + // and pmove() shouldn't handle weapon firing + if ( msec > 50 ) { + msec = 50; } } pmove->cmd.serverTime = pmove->ps->commandTime + msec; @@ -5496,6 +5696,12 @@ } } + // rain - sanity check weapon heat + if (pmove->ps->curWeapHeat > 255) + pmove->ps->curWeapHeat = 255; + else if (pmove->ps->curWeapHeat < 0) + pmove->ps->curWeapHeat = 0; + //PM_CheckStuck(); if ( (pm->ps->stats[STAT_HEALTH] <= 0 || pm->ps->pm_type == PM_DEAD ) && pml.groundTrace.surfaceFlags & SURF_MONSTERSLICK ) diff -urN --exclude=.svn et.orig/src/game/bg_public.h etpub/src/game/bg_public.h --- et.orig/src/game/bg_public.h 2003-06-23 14:13:46.000000000 -0500 +++ etpub/src/game/bg_public.h 2005-03-11 10:19:25.000000000 -0600 @@ -417,10 +417,11 @@ typedef enum { PM_NORMAL, // can accelerate and turn PM_NOCLIP, // noclip movement - PM_SPECTATOR, // still run into walls + PM_SPECTATOR, // still run into walls PM_DEAD, // no acceleration or turning, but free falling PM_FREEZE, // stuck in place with no control - PM_INTERMISSION // no movement or status bar + PM_INTERMISSION, // no movement or status bar + PM_PLAYDEAD // no movement or status bar } pmtype_t; typedef enum { @@ -651,6 +652,7 @@ #define EF_BOUNCE_HALF 0x08000000 // for missiles #define EF_MOVER_STOP 0x10000000 // will push otherwise // (SA) moved down to make space for one more client flag #define EF_MOVER_BLOCKED 0x20000000 // mover was blocked dont lerp on the client // xkan, moved down to make space for client flag +#define EF_PLAYDEAD 0x40000000 // player looks dead #define BG_PlayerMounted( eFlags ) (( eFlags & EF_MG42_ACTIVE ) || ( eFlags & EF_MOUNTEDTANK ) || ( eFlags & EF_AAGUN_ACTIVE )) @@ -813,6 +815,15 @@ // NOTE: this cannot be larger than 64 for AI/player weapons! } weapon_t; +#define WPF_NO_L0_FOPS_BINOCS 1 +#define WPF_UNDERWATER_SYRINGE 2 +#define WPF_UNDERWATER_PLIERS 4 +#define WPF_TM_AIRSTRIKE_RESTORE_FULL 8 +#define WPF_TM_AIRSTRIKE_RESTORE_HALF 16 +#define WPF_AMMO_RESTORES_HELMET 32 +#define WPF_DROP_BINOCS 64 +#define WPF_L4HW_KEEPS_PISTOL 128 + // JPW NERVE moved from cg_weapons (now used in g_active) for drop command, actual array in bg_misc.c extern int weapBanksMultiPlayer[MAX_WEAP_BANKS_MP][MAX_WEAPS_IN_BANK_MP]; // jpw diff -urN --exclude=.svn et.orig/src/game/g_active.c etpub/src/game/g_active.c --- et.orig/src/game/g_active.c 2003-08-26 16:20:40.000000000 -0500 +++ etpub/src/game/g_active.c 2005-03-07 11:31:54.000000000 -0600 @@ -17,7 +17,8 @@ vec3_t angles; client = player->client; - if ( client->ps.pm_type == PM_DEAD ) { + if ( client->ps.pm_type == PM_DEAD && + !(client->ps.eFlags & EF_PLAYDEAD)) { return; } @@ -374,7 +375,9 @@ ent->client->touchingTOI = NULL; // dead clients don't activate triggers! - if ( ent->client->ps.stats[STAT_HEALTH] <= 0 ) { + // tjw: unless they're only playing. + if ( ent->client->ps.stats[STAT_HEALTH] <= 0 && + !(ent->client->ps.eFlags & EF_PLAYDEAD)) { return; } @@ -563,7 +566,9 @@ g_spectatorInactivity.integer); } else if ( !client->pers.localClient ) { - if ( level.time > client->inactivityTime && client->inactivityWarning) { + if(level.time > client->inactivityTime && + client->inactivityWarning && + !G_shrubbot_permission(&g_entities[client-level.clients], SBF_IMMUNITY)) { client->inactivityWarning = qfalse; client->inactivityTime = level.time + 60 * 1000; trap_DropClient(client - level.clients, "Dropped due to inactivity", 0 ); @@ -599,7 +604,8 @@ client->timeResidual -= 1000; // regenerate - if( client->sess.playerType == PC_MEDIC ) { + // tjw: dead players can't regenerate + if( client->sess.playerType == PC_MEDIC && !(client->ps.eFlags & EF_DEAD) ) { if( ent->health < client->ps.stats[STAT_MAX_HEALTH]) { ent->health += 3; if ( ent->health > client->ps.stats[STAT_MAX_HEALTH] * 1.1){ @@ -618,6 +624,10 @@ } } } + + // notify client that they are playing dead + if(client->ps.eFlags & EF_PLAYDEAD && ent->health > 0) + CP("cp \"Playing Dead\" 1"); } /* @@ -655,9 +665,13 @@ gclient_t *client; int damage; vec3_t dir; + gentity_t *victim; + int kb_time = 0; client = ent->client; + victim = &level.gentities[ent->s.groundEntityNum]; + if ( oldEventSequence < client->ps.eventSequence - MAX_EVENTS ) { oldEventSequence = client->ps.eventSequence - MAX_EVENTS; } @@ -673,7 +687,7 @@ //case EV_FALL_DMG_30: case EV_FALL_DMG_50: //case EV_FALL_DMG_75: - + if ( ent->s.eType != ET_PLAYER ) { break; // not in the player model } @@ -684,34 +698,51 @@ else if (event == EV_FALL_DMG_50) { damage = 50; - ent->client->ps.pm_time = 1000; - ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - VectorClear (ent->client->ps.velocity); + kb_time = 1000; } else if (event == EV_FALL_DMG_25) { damage = 25; - ent->client->ps.pm_time = 250; - ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - VectorClear (ent->client->ps.velocity); + kb_time = 500; } else if (event == EV_FALL_DMG_15) { damage = 15; - ent->client->ps.pm_time = 1000; - ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - VectorClear (ent->client->ps.velocity); + kb_time = 250; } else if (event == EV_FALL_DMG_10) { damage = 10; - ent->client->ps.pm_time = 1000; - ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; - VectorClear (ent->client->ps.velocity); + kb_time = 250; } else damage = 5; // never used + + VectorClear (ent->client->ps.velocity); + VectorSet (dir, 0, 0, 1); + + if(kb_time) { + ent->client->ps.pm_time = kb_time; + ent->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + + if(victim->client && + victim->s.eType == ET_PLAYER && + g_goomba.integer) { + + if(kb_time) { + victim->client->ps.pm_time = kb_time; + victim->client->ps.pm_flags |= PMF_TIME_KNOCKBACK; + } + victim->pain_debounce_time = level.time + 200; // no normal pain sound + G_Damage (victim, ent, ent, NULL, NULL, (damage*g_goomba.integer*2), 0, MOD_CRUSH); + G_AddEvent(victim, EV_GENERAL_SOUND, + G_SoundIndex("sound/world/debris1.wav")); + // faller has a soft landing + damage *= 0.2f; + } + ent->pain_debounce_time = level.time + 200; // no normal pain sound G_Damage (ent, NULL, NULL, NULL, NULL, damage, 0, MOD_FALLING); break; @@ -967,6 +998,11 @@ SpectatorThink( ent, ucmd ); return; } + // bani's flamethrower exploit fix + if( client->flametime && level.time > client->flametime ) { + client->flametime = 0; + ent->r.svFlags &= ~SVF_BROADCAST; + } if((client->ps.eFlags & EF_VIEWING_CAMERA) || level.match_pause != PAUSE_NONE #ifdef SAVEGAME_SUPPORT @@ -993,11 +1029,19 @@ VectorClear(client->ps.velocity); client->ps.pm_type = PM_FREEZE; } - } else if ( client->noclip ) { + } + else if ( client->noclip ) { client->ps.pm_type = PM_NOCLIP; - } else if ( client->ps.stats[STAT_HEALTH] <= 0 ) { + } + else if(client->ps.pm_type == PM_PLAYDEAD) { + // no need to change it since it will + // be adjusted by PM_CheckPlayDead regardless + } + else if(client->ps.stats[STAT_HEALTH] <= 0 || + client->ps.eFlags & EF_PLAYDEAD) { client->ps.pm_type = PM_DEAD; - } else { + } + else { client->ps.pm_type = PM_NORMAL; } @@ -1231,7 +1275,8 @@ } // check for respawning - if( client->ps.stats[STAT_HEALTH] <= 0 ) { + if( client->ps.stats[STAT_HEALTH] <= 0 && + !(client->ps.eFlags & EF_PLAYDEAD)) { // DHM - Nerve WolfFindMedic( ent ); @@ -1353,6 +1398,13 @@ gclient_t *cl; qboolean do_respawn = qfalse; // JPW NERVE + /* + G_Printf("(dwRedReinfOffset %d + timeCurrent %d - startTime %d) lastReinforceTime %d\n", + level.dwRedReinfOffset, + level.timeCurrent, + level.startTime, + ent->client->pers.lastReinforceTime); + */ // Players can respawn quickly in warmup if(g_gamestate.integer != GS_PLAYING && ent->client->respawnTime <= level.timeCurrent && ent->client->sess.sessionTeam != TEAM_SPECTATOR) { @@ -1367,6 +1419,11 @@ do_respawn = (testtime < ent->client->pers.lastReinforceTime); ent->client->pers.lastReinforceTime = testtime; } + /* + G_Printf("testtime %d lastReinforceTime %d\n\n", + testtime, + ent->client->pers.lastReinforceTime); + */ if( g_gametype.integer != GT_WOLF_LMS ) { if ( ( g_maxlives.integer > 0 || g_alliedmaxlives.integer > 0 || g_axismaxlives.integer > 0 ) @@ -1698,8 +1755,10 @@ ent->s.eFlags &= ~EF_CONNECTION; } - ent->client->ps.stats[STAT_HEALTH] = ent->health; // FIXME: get rid of ent->health... - // Gordon: WHY? other ents use it. + // don't tell the clients about this player's health + // when using playdead. It's a secret. + if(!(ent->s.eFlags & EF_PLAYDEAD)) + ent->client->ps.stats[STAT_HEALTH] = ent->health; G_SetClientSound (ent); @@ -1724,7 +1783,11 @@ ent->r.contents = CONTENTS_CORPSE; } - if ( ent->health > 0 && ent->r.contents == CONTENTS_CORPSE && !(ent->s.eFlags & EF_MOUNTEDTANK)) { + if ( ent->health > 0 && + ent->r.contents == CONTENTS_CORPSE && + !(ent->s.eFlags & EF_MOUNTEDTANK) && + !(ent->s.eFlags & EF_PLAYDEAD) + ) { WolfReviveBbox( ent ); } diff -urN --exclude=.svn et.orig/src/game/g_antilag.c etpub/src/game/g_antilag.c --- et.orig/src/game/g_antilag.c 2003-09-02 15:50:26.000000000 -0500 +++ etpub/src/game/g_antilag.c 2005-03-10 18:45:47.000000000 -0600 @@ -71,6 +71,8 @@ VectorCopy( ent->client->clientMarkers[j].mins, ent->r.mins ); VectorCopy( ent->client->clientMarkers[j].maxs, ent->r.maxs ); } + if(ent->client->fakeMaxZ) + ent->r.maxs[2] = ent->client->fakeMaxZ; trap_LinkEntity( ent ); } @@ -84,7 +86,9 @@ VectorCopy( ent->client->backupMarker.origin, ent->r.currentOrigin ); VectorCopy( ent->client->backupMarker.mins, ent->r.mins ); VectorCopy( ent->client->backupMarker.maxs, ent->r.maxs ); - ent->client->backupMarker.servertime = 0; + ent->client->backupMarker.servertime = 0; + if(ent->client->fakeMaxZ) + ent->r.maxs[2] = ent->client->fakeMaxZ; trap_LinkEntity( ent ); } @@ -151,9 +155,9 @@ (list->client->sess.sessionTeam == TEAM_AXIS || list->client->sess.sessionTeam == TEAM_ALLIES) && (list != ent) && list->r.linked && - (list->health > 0) && !(list->client->ps.pm_flags & PMF_LIMBO) && - (list->client->ps.pm_type == PM_NORMAL) + ((list->client->ps.pm_type == PM_NORMAL) || + (list->client->ps.pm_type == PM_DEAD)) ) { list->client->tempHead = G_BuildHead( list ); list->client->tempLeg = G_BuildLeg( list ); diff -urN --exclude=.svn et.orig/src/game/g_client.c etpub/src/game/g_client.c --- et.orig/src/game/g_client.c 2003-08-28 15:43:22.000000000 -0500 +++ etpub/src/game/g_client.c 2005-03-12 11:26:50.000000000 -0600 @@ -297,11 +297,13 @@ */ void BodySink2( gentity_t *ent ) { ent->physicsObject = qfalse; - ent->nextthink = level.time + BODY_TIME(BODY_TEAM(ent))+1500; - ent->think = BodyUnlink; - ent->s.pos.trType = TR_LINEAR; - ent->s.pos.trTime = level.time; - VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + // tjw: BODY_TIME is how long they last all together, not just sink anim + //ent->nextthink = level.time + BODY_TIME(BODY_TEAM(ent))+1500; + ent->nextthink = level.time + 4000; + ent->think = BodyUnlink; + ent->s.pos.trType = TR_LINEAR; + ent->s.pos.trTime = level.time; + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); VectorSet( ent->s.pos.trDelta, 0, 0, -8 ); } @@ -418,10 +420,11 @@ body->r.contents = CONTENTS_CORPSE; body->r.ownerNum = ent->r.ownerNum; - BODY_TEAM(body) = ent->client->sess.sessionTeam; - BODY_CLASS(body) = ent->client->sess.playerType; - BODY_CHARACTER(body) = ent->client->pers.characterIndex; - BODY_VALUE(body) = 0; + BODY_TEAM(body) = ent->client->sess.sessionTeam; + BODY_CLASS(body) = ent->client->sess.playerType; + BODY_CHARACTER(body) = ent->client->pers.characterIndex; + BODY_VALUE(body) = 0; + BODY_WEAPON(body) = ent->client->sess.playerWeapon; body->s.time2 = 0; @@ -478,6 +481,99 @@ VectorCopy( ent->s.angles, ent->client->ps.viewangles); } + +void G_DropLimboHealth(gentity_t *ent) +{ + vec3_t launchvel, launchspot; + int packs = 0; + int i; + int cwt; + + if(g_dropHealth.integer == 0) return; + if(!ent->client) return; + if(ent->client->sess.playerType != PC_MEDIC) + return; + + packs = g_dropHealth.integer; + if(g_dropHealth.integer == -1) { + packs = 5; // max packs + cwt = ent->client->ps.classWeaponTime; + ent->client->ps.classWeaponTime += + (level.time + - ent->client->deathTime + + 10000); + } + for(i=0; iclient->ps.classWeaponTime >= level.time) { + + break; + } + launchvel[0] = crandom(); + launchvel[1] = crandom(); + launchvel[2] = 0; + VectorScale(launchvel, 100, launchvel); + launchvel[2] = (100 + (g_tossDistance.integer * 10)); + VectorCopy(ent->r.currentOrigin, launchspot); + + Weapon_Medic_Ext(ent, + launchspot, + launchspot, + launchvel); + } + ent->client->ps.classWeaponTime = cwt; +} + +void G_DropLimboAmmo(gentity_t *ent) +{ + vec3_t launchvel, launchspot; + int packs = 0; + int i; + int cwt; + + if(g_dropAmmo.integer == 0) return; + if(!ent->client) return; + if(ent->client->sess.playerType != PC_FIELDOPS) + return; + + packs = g_dropAmmo.integer; + if(g_dropAmmo.integer == -1) { + packs = 5; // max packs + cwt = ent->client->ps.classWeaponTime; + // adjust the charge timer so that the + // fops only drops as much ammo as he + // could have before he died. + // don't ask me where the 10000 comes + // from, it just makes this more + // accurate. + ent->client->ps.classWeaponTime += + (level.time + - ent->client->deathTime + + 10000); + } + for(i=0; iclient->ps.classWeaponTime >= level.time) { + + break; + } + launchvel[0] = crandom(); + launchvel[1] = crandom(); + launchvel[2] = 0; + + VectorScale(launchvel, 100, launchvel); + launchvel[2] = (100 + (g_tossDistance.integer * 10)); + VectorCopy(ent->r.currentOrigin, launchspot); + + Weapon_MagicAmmo_Ext(ent, + launchspot, + launchspot, + launchvel); + } + ent->client->ps.classWeaponTime = cwt; +} + + /* JPW NERVE ================ limbo @@ -513,6 +609,8 @@ if( makeCorpse ) { + G_DropLimboHealth(ent); + G_DropLimboAmmo(ent); CopyToBodyQue (ent); // make a nice looking corpse } else { trap_UnlinkEntity (ent); @@ -648,7 +746,7 @@ G_DPrintf( "Respawning %s, %i lives left\n", ent->client->pers.netname, ent->client->ps.persistant[PERS_RESPAWNS_LEFT]); - ClientSpawn(ent, qfalse); + ClientSpawn(ent, qfalse, qfalse, qtrue); // DHM - Nerve :: Add back if we decide to have a spawn effect // add a teleportation effect @@ -788,6 +886,441 @@ void BotSetPOW(int entityNum, qboolean isPOW); + + +/* + G_AddClassSpecificTools //tjw + This stuff was stripped out of SetWolfSpawnWeapons() so it can + be used for class stealing. It needed cleaning up badly anyway. + It still does. + */ +void G_AddClassSpecificTools(gclient_t *client) +{ + int pc = client->sess.playerType; + qboolean add_binocs = qfalse; + + if(client->sess.skill[SK_BATTLE_SENSE] >= 1) + add_binocs = qtrue; + + switch(client->sess.playerType) { + case PC_ENGINEER: + AddWeaponToPlayer( client, WP_DYNAMITE, 0, 1, qfalse ); + AddWeaponToPlayer( client, WP_PLIERS, 0, 1, qfalse ); + AddWeaponToPlayer( client, + WP_LANDMINE, + GetAmmoTableData(WP_LANDMINE)->defaultStartingAmmo, + GetAmmoTableData(WP_LANDMINE)->defaultStartingClip, + qfalse ); + break; + case PC_COVERTOPS: + add_binocs = qtrue; + AddWeaponToPlayer(client, + WP_SMOKE_BOMB, + GetAmmoTableData(WP_SMOKE_BOMB)->defaultStartingAmmo, + GetAmmoTableData(WP_SMOKE_BOMB)->defaultStartingClip, + qfalse); + // See if we already have a satchel charge placed + // this needs to be here for class stealing + if( G_FindSatchel( &g_entities[client->ps.clientNum] ) ) { + AddWeaponToPlayer( client, WP_SATCHEL, 0, 0, qfalse ); + AddWeaponToPlayer( client, WP_SATCHEL_DET, 0, 1, qfalse ); + } else { + AddWeaponToPlayer( client, WP_SATCHEL, 0, 1, qfalse ); + AddWeaponToPlayer( client, WP_SATCHEL_DET, 0, 0, qfalse ); + } + break; + case PC_FIELDOPS: + add_binocs = qtrue; + AddWeaponToPlayer(client, WP_AMMO, 0, 1, qfalse); + AddWeaponToPlayer( client, + WP_SMOKE_MARKER, + GetAmmoTableData(WP_SMOKE_MARKER)->defaultStartingAmmo, + GetAmmoTableData(WP_SMOKE_MARKER)->defaultStartingClip, + qfalse); + break; + case PC_MEDIC: + AddWeaponToPlayer(client, + WP_MEDIC_SYRINGE, + GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingClip, + qfalse); + if( client->sess.skill[SK_FIRST_AID] >= 4 ) + AddWeaponToPlayer(client, + WP_MEDIC_ADRENALINE, + GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingClip, + qfalse); + + AddWeaponToPlayer(client, + WP_MEDKIT, + GetAmmoTableData(WP_MEDKIT)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDKIT)->defaultStartingClip, + qfalse); + break; + case PC_SOLDIER: + break; + } + if(add_binocs) { + if(AddWeaponToPlayer(client, WP_BINOCULARS, 1, 0, qfalse)) { + client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); + } + } + //// Josh: adren in different classes when level 4 + if( (client->sess.playerType != PC_MEDIC + && (g_skills.integer & SKILLS_ADREN)) + && client->sess.skill[SK_FIRST_AID] >= 4 ) + AddWeaponToPlayer(client, + WP_MEDIC_ADRENALINE, + GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingClip, + qfalse); + +} + +qboolean _SetSoldierSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2, w3 = 0; + weapon_t ws = 0; + + w = client->sess.latchPlayerWeapon; + if(client->sess.sessionTeam == TEAM_AXIS) { + switch(w) { + case WP_MP40: + case WP_PANZERFAUST: + case WP_MORTAR: + case WP_FLAMETHROWER: + case WP_MOBILE_MG42: + break; + default: + w = WP_MP40; + } + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 4, qfalse); + w2 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_LUGER; + } + if( client->sess.skill[SK_HEAVY_WEAPONS] >= 4) { + w3 = WP_MP40; + if(!(g_weapons.integer & WPF_L4HW_KEEPS_PISTOL)) + w2 = 0; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + switch(w) { + case WP_THOMPSON: + case WP_PANZERFAUST: + case WP_MORTAR: + case WP_FLAMETHROWER: + case WP_MOBILE_MG42: + break; + default: + w = WP_THOMPSON; + } + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 4, qfalse); + w2 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_COLT; + } + if( client->sess.skill[SK_HEAVY_WEAPONS] >= 4) { + w3 = WP_THOMPSON; + if(!(g_weapons.integer & WPF_L4HW_KEEPS_PISTOL)) + w2 = 0; + } + } + else { + return qfalse; + } + + AddWeaponToPlayer(client, + w, + GetAmmoTableData(w)->defaultStartingAmmo, + GetAmmoTableData(w)->defaultStartingClip, + qtrue); + + if(w == WP_MOBILE_MG42) { + ws = WP_MOBILE_MG42_SET; + } + else if(w == WP_MORTAR) { + ws = WP_MORTAR_SET; + } + + if(ws) { + AddWeaponToPlayer(client, + ws, + GetAmmoTableData(ws)->defaultStartingAmmo, + GetAmmoTableData(ws)->defaultStartingClip, + qfalse); + } + if(w2 == WP_AKIMBO_LUGER || w2 == WP_AKIMBO_COLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + if(w3) { + AddWeaponToPlayer(client, + w3, + 2*GetAmmoTableData(w3)->defaultStartingAmmo, + GetAmmoTableData(w3)->defaultStartingClip, + qfalse); + } + return qtrue; +} + +qboolean _SetMedicSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2; + + if(client->sess.sessionTeam == TEAM_AXIS) { + w = WP_MP40; + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 1, qfalse); + w2 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_LUGER; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + w = WP_THOMPSON; + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 1, qfalse); + w2 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_COLT; + } + } + else { + return qfalse; + } + + AddWeaponToPlayer(client, + w, + 0, + GetAmmoTableData(w)->defaultStartingClip, + qtrue); + + if(w2 == WP_AKIMBO_LUGER || w2 == WP_AKIMBO_COLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + return qtrue; +} + +qboolean _SetEngineerSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2; + + w = client->sess.latchPlayerWeapon; + if(client->sess.sessionTeam == TEAM_AXIS) { + switch(w) { + case WP_KAR98: + case WP_MP40: + break; + default: + w = WP_MP40; + } + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 4, qfalse); + w2 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_LUGER; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + switch(w) { + case WP_CARBINE: + case WP_THOMPSON: + break; + default: + w = WP_THOMPSON; + } + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 4, qfalse); + w2 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_COLT; + } + } + else { + return qfalse; + } + + AddWeaponToPlayer(client, + w, + GetAmmoTableData(w)->defaultStartingAmmo, + GetAmmoTableData(w)->defaultStartingClip, + qtrue); + + if(w == WP_KAR98) { + AddWeaponToPlayer(client, + WP_GPG40, + GetAmmoTableData(WP_GPG40)->defaultStartingAmmo, + GetAmmoTableData(WP_GPG40)->defaultStartingClip, + qfalse); + } + else if(w == WP_CARBINE) { + AddWeaponToPlayer(client, + WP_M7, + GetAmmoTableData(WP_M7)->defaultStartingAmmo, + GetAmmoTableData(WP_M7)->defaultStartingClip, + qfalse); + } + if(w2 == WP_AKIMBO_LUGER || w2 == WP_AKIMBO_COLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + return qtrue; +} + +qboolean _SetFieldOpSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2; + + if(client->sess.sessionTeam == TEAM_AXIS) { + w = WP_MP40; + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 1, qfalse); + w2 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_LUGER; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + w = WP_THOMPSON; + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 1, qfalse); + w2 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_COLT; + } + } + else { + return qfalse; + } + + AddWeaponToPlayer(client, + w, + GetAmmoTableData(w)->defaultStartingAmmo, + GetAmmoTableData(w)->defaultStartingClip, + qtrue); + + if(w2 == WP_AKIMBO_LUGER || w2 == WP_AKIMBO_COLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + return qtrue; + +} + +qboolean _SetCovertSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2, w3; + weapon_t ws = 0; + + w = client->sess.latchPlayerWeapon; + if(client->sess.sessionTeam== TEAM_AXIS) { + switch(w) { + case WP_K43: + case WP_FG42: + case WP_STEN: + break; + default: + w = WP_STEN; + } + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 2, qfalse); + w2 = WP_SILENCER; + w3 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_SILENCEDLUGER; + w3 = 0; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + switch(w) { + case WP_GARAND: + case WP_FG42: + case WP_STEN: + break; + default: + w = WP_STEN; + } + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 2, qfalse); + w2 = WP_SILENCED_COLT; + w3 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_SILENCEDCOLT; + w3 = 0; + } + } + else { + return qfalse; + } + + AddWeaponToPlayer(client, + w, + GetAmmoTableData(w)->defaultStartingAmmo, + GetAmmoTableData(w)->defaultStartingClip, + qtrue); + + if(w == WP_K43) { + ws = WP_K43_SCOPE; + } + else if(w == WP_GARAND) { + ws = WP_GARAND_SCOPE; + } + else if(w == WP_FG42) { + ws = WP_FG42SCOPE; + } + + if(ws) { + AddWeaponToPlayer(client, + ws, + GetAmmoTableData(ws)->defaultStartingAmmo, + GetAmmoTableData(ws)->defaultStartingClip, + qfalse); + } + if(w2 == WP_AKIMBO_SILENCEDLUGER || w2 == WP_AKIMBO_SILENCEDCOLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + if(w3) { + AddWeaponToPlayer(client, + w3, + GetAmmoTableData(w3)->defaultStartingAmmo, + GetAmmoTableData(w3)->defaultStartingClip, + qfalse); + } + client->pmext.silencedSideArm = 1; + return qtrue; +} + /* =========== SetWolfSpawnWeapons @@ -795,11 +1328,18 @@ */ void SetWolfSpawnWeapons( gclient_t *client ) { - int pc = client->sess.playerType; - qboolean isBot = (g_entities[client->ps.clientNum].r.svFlags & SVF_BOT) ? qtrue : qfalse; - qboolean isPOW = (g_entities[client->ps.clientNum].r.svFlags & SVF_POW) ? qtrue : qfalse; + int pc; + qboolean isBot = qfalse; + qboolean isPOW = qfalse; + + pc = client->sess.playerType; + + if(g_entities[client->ps.clientNum].r.svFlags & SVF_BOT) + isBot = qtrue; + if(g_entities[client->ps.clientNum].r.svFlags & SVF_POW) + isPOW = qtrue; - if ( client->sess.sessionTeam == TEAM_SPECTATOR ) + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) return; // Reset special weapon time @@ -808,13 +1348,15 @@ // Communicate it to cgame client->ps.stats[STAT_PLAYER_CLASS] = pc; - // Abuse teamNum to store player class as well (can't see stats for all clients in cgame) + // Abuse teamNum to store player class as well (can't see stats + // for all clients in cgame) client->ps.teamNum = pc; // JPW NERVE -- zero out all ammo counts memset(client->ps.ammo, 0, MAX_WEAPONS * sizeof(int)); - // All players start with a knife (not OR-ing so that it clears previous weapons) + // All players start with a knife (not OR-ing so that it clears + // previous weapons) client->ps.weapons[0] = 0; client->ps.weapons[1] = 0; @@ -832,279 +1374,37 @@ client->ps.weaponstate = WEAPON_READY; - // Engineer gets dynamite - if ( pc == PC_ENGINEER ) { - AddWeaponToPlayer( client, WP_DYNAMITE, 0, 1, qfalse ); - AddWeaponToPlayer( client, WP_PLIERS, 0, 1, qfalse ); - - if( g_knifeonly.integer != 1 ) { - if( client->sess.skill[SK_BATTLE_SENSE] >= 1 ) { - if( AddWeaponToPlayer( client, WP_BINOCULARS, 1, 0, qfalse ) ) { - client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); - } - } - - if (client->sess.sessionTeam == TEAM_AXIS) { - switch( client->sess.playerWeapon ) { - case WP_KAR98: - if( AddWeaponToPlayer( client, WP_KAR98, GetAmmoTableData(WP_KAR98)->defaultStartingAmmo, GetAmmoTableData(WP_KAR98)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_GPG40, GetAmmoTableData(WP_GPG40)->defaultStartingAmmo, GetAmmoTableData(WP_GPG40)->defaultStartingClip, qfalse ); - } - break; - default: - AddWeaponToPlayer( client, WP_MP40, GetAmmoTableData(WP_MP40)->defaultStartingAmmo, GetAmmoTableData(WP_MP40)->defaultStartingClip, qtrue ); - break; - } - AddWeaponToPlayer( client, WP_LANDMINE, GetAmmoTableData(WP_LANDMINE)->defaultStartingAmmo, GetAmmoTableData(WP_LANDMINE)->defaultStartingClip, qfalse ); - AddWeaponToPlayer( client, WP_GRENADE_LAUNCHER, 0, 4, qfalse ); - } else { - switch( client->sess.playerWeapon ) { - case WP_CARBINE: - if( AddWeaponToPlayer( client, WP_CARBINE, GetAmmoTableData(WP_CARBINE)->defaultStartingAmmo, GetAmmoTableData(WP_CARBINE)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_M7, GetAmmoTableData(WP_M7)->defaultStartingAmmo, GetAmmoTableData(WP_M7)->defaultStartingClip, qfalse ); - } - break; - default: - AddWeaponToPlayer( client, WP_THOMPSON, GetAmmoTableData(WP_THOMPSON)->defaultStartingAmmo, GetAmmoTableData(WP_THOMPSON)->defaultStartingClip, qtrue ); - break; - } - AddWeaponToPlayer( client, WP_LANDMINE, GetAmmoTableData(WP_LANDMINE)->defaultStartingAmmo, GetAmmoTableData(WP_LANDMINE)->defaultStartingClip, qfalse ); - AddWeaponToPlayer( client, WP_GRENADE_PINEAPPLE, 0, 4, qfalse ); - } - } + // the only weapon other than knife is syringe apperantly no need + // for mile long conditionals + if( g_knifeonly.integer && pc == PC_MEDIC) { + AddWeaponToPlayer(client, + WP_MEDIC_SYRINGE, + GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingClip, + qfalse); + return; } - if ( g_knifeonly.integer != 1 ) { - // Field ops gets binoculars, ammo pack, artillery, and a grenade - if ( pc == PC_FIELDOPS ) { - AddWeaponToPlayer( client, WP_AMMO, 0, 1, qfalse ); - - if( AddWeaponToPlayer( client, WP_BINOCULARS, 1, 0, qfalse ) ) { - client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); - } - - AddWeaponToPlayer( client, WP_SMOKE_MARKER, GetAmmoTableData(WP_SMOKE_MARKER)->defaultStartingAmmo, GetAmmoTableData(WP_SMOKE_MARKER)->defaultStartingClip, qfalse ); - - if( client->sess.sessionTeam == TEAM_AXIS ) { - AddWeaponToPlayer( client, WP_MP40, GetAmmoTableData(WP_MP40)->defaultStartingAmmo, GetAmmoTableData(WP_MP40)->defaultStartingClip, qtrue ); - AddWeaponToPlayer( client, WP_GRENADE_LAUNCHER, 0, 1, qfalse ); - } else { - AddWeaponToPlayer( client, WP_THOMPSON, GetAmmoTableData(WP_THOMPSON)->defaultStartingAmmo, GetAmmoTableData(WP_THOMPSON)->defaultStartingClip, qtrue ); - AddWeaponToPlayer( client, WP_GRENADE_PINEAPPLE, 0, 1, qfalse ); - } - } else if( pc == PC_MEDIC ) { - if( client->sess.skill[SK_BATTLE_SENSE] >= 1 ) { - if( AddWeaponToPlayer( client, WP_BINOCULARS, 1, 0, qfalse ) ) { - client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); - } - } - - AddWeaponToPlayer( client, WP_MEDIC_SYRINGE, GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingAmmo, GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingClip, qfalse ); - if( client->sess.skill[SK_FIRST_AID] >= 4 ) - AddWeaponToPlayer( client, WP_MEDIC_ADRENALINE, GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingAmmo, GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingClip, qfalse ); - - AddWeaponToPlayer( client, WP_MEDKIT, GetAmmoTableData(WP_MEDKIT)->defaultStartingAmmo, GetAmmoTableData(WP_MEDKIT)->defaultStartingClip, qfalse ); - - if (client->sess.sessionTeam == TEAM_AXIS) { - AddWeaponToPlayer( client, WP_MP40, 0, GetAmmoTableData(WP_MP40)->defaultStartingClip, qtrue ); - AddWeaponToPlayer( client, WP_GRENADE_LAUNCHER, 0, 1, qfalse ); - } else { - AddWeaponToPlayer( client, WP_THOMPSON, 0, GetAmmoTableData(WP_THOMPSON)->defaultStartingClip, qtrue ); - AddWeaponToPlayer( client, WP_GRENADE_PINEAPPLE, 0, 1, qfalse ); - } - } else if ( pc == PC_SOLDIER ) { - if( client->sess.skill[SK_BATTLE_SENSE] >= 1 ) { - if( AddWeaponToPlayer( client, WP_BINOCULARS, 1, 0, qfalse ) ) { - client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); - } - } - - switch( client->sess.sessionTeam ) { - case TEAM_AXIS: - switch( client->sess.playerWeapon ) { - default: - case WP_MP40: - AddWeaponToPlayer( client, WP_MP40, 2*(GetAmmoTableData(WP_MP40)->defaultStartingAmmo), GetAmmoTableData(WP_MP40)->defaultStartingClip, qtrue ); - break; - case WP_PANZERFAUST: - AddWeaponToPlayer( client, WP_PANZERFAUST, GetAmmoTableData(WP_PANZERFAUST)->defaultStartingAmmo, GetAmmoTableData(WP_PANZERFAUST)->defaultStartingClip, qtrue ); - break; - case WP_FLAMETHROWER: - AddWeaponToPlayer( client, WP_FLAMETHROWER, GetAmmoTableData(WP_FLAMETHROWER)->defaultStartingAmmo, GetAmmoTableData(WP_FLAMETHROWER)->defaultStartingClip, qtrue ); - break; - case WP_MOBILE_MG42: - if( AddWeaponToPlayer( client, WP_MOBILE_MG42, GetAmmoTableData(WP_MOBILE_MG42)->defaultStartingAmmo, GetAmmoTableData(WP_MOBILE_MG42)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_MOBILE_MG42_SET, GetAmmoTableData(WP_MOBILE_MG42_SET)->defaultStartingAmmo, GetAmmoTableData(WP_MOBILE_MG42_SET)->defaultStartingClip, qfalse ); - } - break; - case WP_MORTAR: - if( AddWeaponToPlayer( client, WP_MORTAR, GetAmmoTableData(WP_MORTAR)->defaultStartingAmmo, GetAmmoTableData(WP_MORTAR)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_MORTAR_SET, GetAmmoTableData(WP_MORTAR_SET)->defaultStartingAmmo, GetAmmoTableData(WP_MORTAR_SET)->defaultStartingClip, qfalse ); - } - break; - } - break; - case TEAM_ALLIES: - switch( client->sess.playerWeapon ) { - default: - case WP_THOMPSON: - AddWeaponToPlayer( client, WP_THOMPSON, 2*(GetAmmoTableData(WP_THOMPSON)->defaultStartingAmmo), GetAmmoTableData(WP_THOMPSON)->defaultStartingClip, qtrue ); - break; - case WP_PANZERFAUST: - AddWeaponToPlayer( client, WP_PANZERFAUST, GetAmmoTableData(WP_PANZERFAUST)->defaultStartingAmmo, GetAmmoTableData(WP_PANZERFAUST)->defaultStartingClip, qtrue ); - break; - case WP_FLAMETHROWER: - AddWeaponToPlayer( client, WP_FLAMETHROWER, GetAmmoTableData(WP_FLAMETHROWER)->defaultStartingAmmo, GetAmmoTableData(WP_FLAMETHROWER)->defaultStartingClip, qtrue ); - break; - case WP_MOBILE_MG42: - if( AddWeaponToPlayer( client, WP_MOBILE_MG42, GetAmmoTableData(WP_MOBILE_MG42)->defaultStartingAmmo, GetAmmoTableData(WP_MOBILE_MG42)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_MOBILE_MG42_SET, GetAmmoTableData(WP_MOBILE_MG42_SET)->defaultStartingAmmo, GetAmmoTableData(WP_MOBILE_MG42_SET)->defaultStartingClip, qfalse ); - } - break; - case WP_MORTAR: - if( AddWeaponToPlayer( client, WP_MORTAR, GetAmmoTableData(WP_MORTAR)->defaultStartingAmmo, GetAmmoTableData(WP_MORTAR)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_MORTAR_SET, GetAmmoTableData(WP_MORTAR_SET)->defaultStartingAmmo, GetAmmoTableData(WP_MORTAR_SET)->defaultStartingClip, qfalse ); - } - break; - } - break; - } - } else if( pc == PC_COVERTOPS ) { - switch( client->sess.playerWeapon ) { - case WP_K43: - case WP_GARAND: - if( client->sess.sessionTeam == TEAM_AXIS ) { - if( AddWeaponToPlayer( client, WP_K43, GetAmmoTableData(WP_K43)->defaultStartingAmmo, GetAmmoTableData(WP_K43)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_K43_SCOPE, GetAmmoTableData(WP_K43_SCOPE)->defaultStartingAmmo, GetAmmoTableData(WP_K43_SCOPE)->defaultStartingClip, qfalse ); - } - break; - } else { - if( AddWeaponToPlayer( client, WP_GARAND, GetAmmoTableData(WP_GARAND)->defaultStartingAmmo, GetAmmoTableData(WP_GARAND)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_GARAND_SCOPE, GetAmmoTableData(WP_GARAND_SCOPE)->defaultStartingAmmo, GetAmmoTableData(WP_GARAND_SCOPE)->defaultStartingClip, qfalse ); - } - break; - } - case WP_FG42: - if( AddWeaponToPlayer( client, WP_FG42, GetAmmoTableData(WP_FG42)->defaultStartingAmmo, GetAmmoTableData(WP_FG42)->defaultStartingClip, qtrue ) ) { - AddWeaponToPlayer( client, WP_FG42SCOPE, GetAmmoTableData(WP_FG42SCOPE)->defaultStartingAmmo, GetAmmoTableData(WP_FG42SCOPE)->defaultStartingClip, qfalse ); - } - break; - default: - AddWeaponToPlayer( client, WP_STEN, 2*(GetAmmoTableData(WP_STEN)->defaultStartingAmmo), GetAmmoTableData(WP_STEN)->defaultStartingClip, qtrue ); - break; - } - - if( AddWeaponToPlayer( client, WP_BINOCULARS, 1, 0, qfalse ) ) { - client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); - } - - AddWeaponToPlayer( client, WP_SMOKE_BOMB, GetAmmoTableData(WP_SMOKE_BOMB)->defaultStartingAmmo, GetAmmoTableData(WP_SMOKE_BOMB)->defaultStartingClip, qfalse ); - - // See if we already have a satchel charge placed - NOTE: maybe we want to change this so the thing voids on death - if( G_FindSatchel( &g_entities[client->ps.clientNum] ) ) { - AddWeaponToPlayer( client, WP_SATCHEL, 0, 0, qfalse ); // Big Bang \o/ - AddWeaponToPlayer( client, WP_SATCHEL_DET, 0, 1, qfalse ); // Big Red Button for tha Big Bang - } else { - AddWeaponToPlayer( client, WP_SATCHEL, 0, 1, qfalse ); // Big Bang \o/ - AddWeaponToPlayer( client, WP_SATCHEL_DET, 0, 0, qfalse ); // Big Red Button for tha Big Bang - } - } - - switch( client->sess.sessionTeam ) { - case TEAM_AXIS: - switch( pc ) { - case PC_SOLDIER: - if( client->sess.skill[SK_HEAVY_WEAPONS] >= 4 && client->sess.playerWeapon2 == WP_MP40 ) { - AddWeaponToPlayer( client, WP_MP40, 2*(GetAmmoTableData(WP_MP40)->defaultStartingAmmo), GetAmmoTableData(WP_MP40)->defaultStartingClip, qtrue ); - } else if( client->sess.skill[SK_LIGHT_WEAPONS] >= 4 && client->sess.playerWeapon2 == WP_AKIMBO_LUGER ) { - client->ps.ammoclip[BG_FindClipForWeapon(BG_AkimboSidearm(WP_AKIMBO_LUGER))] = GetAmmoTableData(WP_AKIMBO_LUGER)->defaultStartingClip; - AddWeaponToPlayer( client, WP_AKIMBO_LUGER, GetAmmoTableData(WP_AKIMBO_LUGER)->defaultStartingAmmo, GetAmmoTableData(WP_AKIMBO_LUGER)->defaultStartingClip, qfalse ); - } else { - AddWeaponToPlayer( client, WP_LUGER, GetAmmoTableData(WP_LUGER)->defaultStartingAmmo, GetAmmoTableData(WP_LUGER)->defaultStartingClip, qfalse ); - } - break; - - case PC_COVERTOPS: - if( client->sess.skill[SK_LIGHT_WEAPONS] >= 4 && ( client->sess.playerWeapon2 == WP_AKIMBO_SILENCEDLUGER || client->sess.playerWeapon2 == WP_AKIMBO_LUGER ) ) { - client->ps.ammoclip[BG_FindClipForWeapon(BG_AkimboSidearm(WP_AKIMBO_SILENCEDLUGER))] = GetAmmoTableData(WP_AKIMBO_SILENCEDLUGER)->defaultStartingClip; - AddWeaponToPlayer( client, WP_AKIMBO_SILENCEDLUGER, GetAmmoTableData(WP_AKIMBO_SILENCEDLUGER)->defaultStartingAmmo, GetAmmoTableData(WP_AKIMBO_SILENCEDLUGER)->defaultStartingClip, qfalse ); - } else { - AddWeaponToPlayer( client, WP_LUGER, GetAmmoTableData(WP_LUGER)->defaultStartingAmmo, GetAmmoTableData(WP_LUGER)->defaultStartingClip, qfalse ); - AddWeaponToPlayer( client, WP_SILENCER, GetAmmoTableData(WP_SILENCER)->defaultStartingAmmo, GetAmmoTableData(WP_SILENCER)->defaultStartingClip, qfalse ); - client->pmext.silencedSideArm = 1; - } - break; - - default: - if( client->sess.skill[SK_LIGHT_WEAPONS] >= 4 && client->sess.playerWeapon2 == WP_AKIMBO_LUGER ) { - client->ps.ammoclip[BG_FindClipForWeapon(BG_AkimboSidearm(WP_AKIMBO_LUGER))] = GetAmmoTableData(WP_AKIMBO_LUGER)->defaultStartingClip; - AddWeaponToPlayer( client, WP_AKIMBO_LUGER, GetAmmoTableData(WP_AKIMBO_LUGER)->defaultStartingAmmo, GetAmmoTableData(WP_AKIMBO_LUGER)->defaultStartingClip, qfalse ); - } else { - AddWeaponToPlayer( client, WP_LUGER, GetAmmoTableData(WP_LUGER)->defaultStartingAmmo, GetAmmoTableData(WP_LUGER)->defaultStartingClip, qfalse ); - } - break; - } - break; - default: - switch( pc ) { - case PC_SOLDIER: - if( client->sess.skill[SK_HEAVY_WEAPONS] >= 4 && client->sess.playerWeapon2 == WP_THOMPSON ) { - AddWeaponToPlayer( client, WP_THOMPSON, 2*(GetAmmoTableData(WP_THOMPSON)->defaultStartingAmmo), GetAmmoTableData(WP_THOMPSON)->defaultStartingClip, qtrue ); - } else if( client->sess.skill[SK_LIGHT_WEAPONS] >= 4 && client->sess.playerWeapon2 == WP_AKIMBO_COLT ) { - client->ps.ammoclip[BG_FindClipForWeapon(BG_AkimboSidearm(WP_AKIMBO_COLT))] = GetAmmoTableData(WP_AKIMBO_COLT)->defaultStartingClip; - AddWeaponToPlayer( client, WP_AKIMBO_COLT, GetAmmoTableData(WP_AKIMBO_COLT)->defaultStartingAmmo, GetAmmoTableData(WP_AKIMBO_COLT)->defaultStartingClip, qfalse ); - } else { - AddWeaponToPlayer( client, WP_COLT, GetAmmoTableData(WP_COLT)->defaultStartingAmmo, GetAmmoTableData(WP_COLT)->defaultStartingClip, qfalse ); - } - break; - - case PC_COVERTOPS: - if( client->sess.skill[SK_LIGHT_WEAPONS] >= 4 && ( client->sess.playerWeapon2 == WP_AKIMBO_SILENCEDCOLT || client->sess.playerWeapon2 == WP_AKIMBO_COLT ) ) { - client->ps.ammoclip[BG_FindClipForWeapon(BG_AkimboSidearm(WP_AKIMBO_SILENCEDCOLT))] = GetAmmoTableData(WP_AKIMBO_SILENCEDCOLT)->defaultStartingClip; - AddWeaponToPlayer( client, WP_AKIMBO_SILENCEDCOLT, GetAmmoTableData(WP_AKIMBO_SILENCEDCOLT)->defaultStartingAmmo, GetAmmoTableData(WP_AKIMBO_SILENCEDCOLT)->defaultStartingClip, qfalse ); - } else { - AddWeaponToPlayer( client, WP_COLT, GetAmmoTableData(WP_COLT)->defaultStartingAmmo, GetAmmoTableData(WP_COLT)->defaultStartingClip, qfalse ); - AddWeaponToPlayer( client, WP_SILENCED_COLT, GetAmmoTableData(WP_SILENCED_COLT)->defaultStartingAmmo, GetAmmoTableData(WP_SILENCED_COLT)->defaultStartingClip, qfalse ); - client->pmext.silencedSideArm = 1; - } - break; - - default: - if( client->sess.skill[SK_LIGHT_WEAPONS] >= 4 && client->sess.playerWeapon2 == WP_AKIMBO_COLT ) { - client->ps.ammoclip[BG_FindClipForWeapon(BG_AkimboSidearm(WP_AKIMBO_COLT))] = GetAmmoTableData(WP_AKIMBO_COLT)->defaultStartingClip; - AddWeaponToPlayer( client, WP_AKIMBO_COLT, GetAmmoTableData(WP_AKIMBO_COLT)->defaultStartingAmmo, GetAmmoTableData(WP_AKIMBO_COLT)->defaultStartingClip, qfalse ); - } else { - AddWeaponToPlayer( client, WP_COLT, GetAmmoTableData(WP_COLT)->defaultStartingAmmo, GetAmmoTableData(WP_COLT)->defaultStartingClip, qfalse ); - } - break; - } - } - - if( pc == PC_SOLDIER ) { - if( client->sess.sessionTeam == TEAM_AXIS ) { - AddWeaponToPlayer( client, WP_GRENADE_LAUNCHER, 0, 4, qfalse ); - } else { - AddWeaponToPlayer( client, WP_GRENADE_PINEAPPLE, 0, 4, qfalse ); - } - } - if( pc == PC_COVERTOPS ) { - if( client->sess.sessionTeam == TEAM_AXIS ) { - AddWeaponToPlayer( client, WP_GRENADE_LAUNCHER, 0, 2, qfalse ); - } else { - AddWeaponToPlayer( client, WP_GRENADE_PINEAPPLE, 0, 2, qfalse ); - } - } - } else { - // Knifeonly block - if( pc == PC_MEDIC ) { - AddWeaponToPlayer( client, WP_MEDIC_SYRINGE, 0, 20, qfalse ); - if( client->sess.skill[SK_FIRST_AID] >= 4 ) - AddWeaponToPlayer( client, WP_MEDIC_ADRENALINE, 0, 10, qfalse ); - - } - // End Knifeonly stuff -- Ensure that medics get their basic stuff + switch(pc) { + case PC_SOLDIER: + _SetSoldierSpawnWeapons(client); + break; + case PC_MEDIC: + _SetMedicSpawnWeapons(client); + break; + case PC_ENGINEER: + _SetEngineerSpawnWeapons(client); + break; + case PC_FIELDOPS: + _SetFieldOpSpawnWeapons(client); + break; + case PC_COVERTOPS: + _SetCovertSpawnWeapons(client); + break; } + + G_AddClassSpecificTools(client); } int G_CountTeamMedics( team_t team, qboolean alivecheck ) { @@ -1325,6 +1625,11 @@ } } + // look for cg_hitsounds + s = Info_ValueForKey(userinfo, "cg_hitsounds"); + if(*s) client->pers.hitsounds = atoi(s); + else client->pers.hitsounds = 1; + // set name Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); s = Info_ValueForKey (userinfo, "name"); @@ -1415,8 +1720,15 @@ return; } - G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); - G_DPrintf( "ClientUserinfoChanged: %i :: %s\n", clientNum, s ); + if (g_logOptions.integer & LOGOPTS_GUID) // Log the GUIDs? + { + char *guid = Info_ValueForKey ( userinfo, "cl_guid" ); + G_LogPrintf( "ClientUserinfoChangedGUID: %i %s %s\n", clientNum, guid, s ); + G_DPrintf( "ClientUserinfoChangedGUID: %i :: %s %s\n", clientNum, guid, s ); + } else { + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); + G_DPrintf( "ClientUserinfoChanged: %i :: %s\n", clientNum, s ); + } } @@ -1444,11 +1756,12 @@ char *value; gclient_t *client; char userinfo[MAX_INFO_STRING]; + char userinfo2[MAX_INFO_STRING]; gentity_t *ent; -#ifdef USEXPSTORAGE - ipXPStorage_t* xpBackup; - int i; -#endif // USEXPSTORAGE + int i; + int clientNum2; + char guid[33]; + ent = &g_entities[ clientNum ]; @@ -1463,6 +1776,11 @@ return "You are banned from this server."; } + // check for shrubbot ban + if(G_shrubbot_ban_check(userinfo)) + return "You are banned from this server."; + + // Xian - check for max lives enforcement ban if( g_gametype.integer != GT_WOLF_LMS ) { if( g_enforcemaxlives.integer && (g_maxlives.integer > 0 || g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0) ) { @@ -1502,6 +1820,28 @@ ClientDisconnect( ent-g_entities ); } + // tjw: if the client has crashed, force disconnect of previous + // session so that the XP can be reclaimed + if((g_XPSave.integer & XPSF_ENABLE) && firstTime) { + value = Info_ValueForKey (userinfo, "cl_guid"); + Q_strncpyz(guid, value, sizeof(guid)); + for (i = 0; i < level.numConnectedClients; i++) { + clientNum2 = level.sortedClients[i]; + if(clientNum == clientNum2) continue; + trap_GetUserinfo(clientNum2, + userinfo2, + sizeof(userinfo2)); + value = Info_ValueForKey (userinfo2, "cl_guid"); + if(!Q_stricmp(guid, value)) { + G_LogPrintf( "Forcing disconnect of " + "duplicate guid %s for client %i\n", + guid, + clientNum2); + ClientDisconnect(clientNum2); + } + } + } + // they can connect ent->client = level.clients + clientNum; client = ent->client; @@ -1513,11 +1853,10 @@ client->pers.connected = CON_CONNECTING; client->pers.connectTime = level.time; // DHM - Nerve - if( firstTime ) - client->pers.initialSpawn = qtrue; // DHM - Nerve - // read or initialize the session data if( firstTime ) { + client->pers.lastTeamChangeTime = 0; + client->pers.initialSpawn = qtrue; // DHM - Nerve G_InitSessionData( client, userinfo ); client->pers.enterTime = level.time; client->ps.persistant[PERS_SCORE] = 0; @@ -1525,15 +1864,39 @@ G_ReadSessionData( client ); } -#ifdef USEXPSTORAGE - value = Info_ValueForKey (userinfo, "ip"); - if( xpBackup = G_FindXPBackup( value ) ) { - for( i = 0; i < SK_NUM_SKILLS; i++ ) { - client->sess.skillpoints[ i ] = xpBackup->skills[ i ]; + + if(g_XPSave.integer & XPSF_ENABLE) { + + char *guid; + char userinfo[MAX_INFO_STRING]; + XPStorage_t* storage; + int i; + + client->XPSave_lives = -999; + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + guid = Info_ValueForKey ( userinfo, "cl_guid" ); + storage = G_FindXPBackup(guid); + if(storage) { + // make the client aware that they have no lives before + // ClientBegin to avoid confusion + if(storage->lives != -999 && + storage->lives < 0) { + + client->ps.persistant[PERS_RESPAWNS_LEFT] = storage->lives; + + } + client->XPSave_lives = storage->lives; + for( i = 0; i < SK_NUM_SKILLS; i++ ) { + client->sess.skillpoints[i] = storage->skills[i]; + } + G_CalcRank( client ); + ent->client->ps.stats[STAT_XP] = 0; + for( i = 0; i < SK_NUM_SKILLS; i++ ) { + ent->client->ps.stats[STAT_XP] += ent->client->sess.skillpoints[i]; + } + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); } - G_CalcRank( client ); } -#endif // USEXPSTORAGE if( g_gametype.integer == GT_WOLF_CAMPAIGN ) { if( g_campaigns[level.currentCampaign].current == 0 || level.newCampaign ) { @@ -1625,10 +1988,25 @@ // int G_ComputeMaxLives(gclient_t *cl, int maxRespawns) { - float scaled = (float)(maxRespawns - 1) * (1.0f - ((float)(level.time - level.startTime) / (g_timelimit.value * 60000.0f))); - int val = (int)scaled; + float scaled; + int val; + + // tjw: do not give out lives in dual obj maps once time runs out. + if((float)(level.time - level.startTime) >= (g_timelimit.value * 60000.0f)) + return -1; + + scaled = (float)(maxRespawns - 1) * (1.0f - ((float)(level.time - level.startTime) / (g_timelimit.value * 60000.0f))); + val = (int)scaled; val += ((scaled - (float)val) < 0.5f) ? 0 : 1; + + if((g_XPSave.integer & XPSF_ENABLE) && + cl->XPSave_lives != -999 && + cl->XPSave_lives < val) { + + val = cl->XPSave_lives; + } + return(val); } @@ -1647,11 +2025,19 @@ gclient_t *client; int flags; int spawn_count, lives_left; // DHM - Nerve + int health; + qboolean maxlives; ent = g_entities + clientNum; client = level.clients + clientNum; + health = ent->health; + + maxlives = (g_maxlives.integer || + g_alliedmaxlives.integer || + g_axismaxlives.integer); + if ( ent->r.linked ) { trap_UnlinkEntity( ent ); } @@ -1676,14 +2062,26 @@ memset( &client->ps, 0, sizeof( client->ps ) ); client->ps.eFlags = flags; client->ps.persistant[PERS_SPAWN_COUNT] = spawn_count; - client->ps.persistant[PERS_RESPAWNS_LEFT] = lives_left; + if(!maxlives) + client->ps.persistant[PERS_RESPAWNS_LEFT] = -1; + else + client->ps.persistant[PERS_RESPAWNS_LEFT] = lives_left; client->pers.complaintClient = -1; client->pers.complaintEndTime = -1; - // locate ent at a spawn point - ClientSpawn( ent, qfalse ); + // tjw: even if g_teamChangeKills is 0, kill them if they try to + // change teams twice in one round to prevent team change + // spamming. + if(client->sess.sessionTeam == TEAM_AXIS) { + if(level.time - client->pers.lastTeamChangeTime < g_redlimbotime.integer) + health = 0; + } + if(client->sess.sessionTeam == TEAM_ALLIES) { + if(level.time - client->pers.lastTeamChangeTime < g_bluelimbotime.integer) + health = 0; + } // Xian -- Changed below for team independant maxlives if( g_gametype.integer != GT_WOLF_LMS ) { @@ -1721,27 +2119,37 @@ } } } - } + } + // locate ent at a spawn point + if(!g_teamChangeKills.integer && health > 0) { - // DHM - Nerve :: Start players in limbo mode if they change teams during the match - if(client->sess.sessionTeam != TEAM_SPECTATOR && (level.time - level.startTime > FRAMETIME * GAME_INIT_FRAMES) ) { -/* if( (client->sess.sessionTeam != TEAM_SPECTATOR && (level.time - client->pers.connectTime) > 60000) || - ( g_gamestate.integer == GS_PLAYING && ( client->sess.sessionTeam == TEAM_AXIS || client->sess.sessionTeam == TEAM_ALLIES ) && - g_gametype.integer == GT_WOLF_LMS && ( level.numTeamClients[0] > 0 || level.numTeamClients[1] > 0 ) ) ) {*/ - ent->health = 0; - ent->r.contents = CONTENTS_CORPSE; + ClientSpawn(ent, qfalse, qtrue, qfalse); + } + else { + ClientSpawn(ent, qfalse, qtrue, qtrue); + } - client->ps.pm_type = PM_DEAD; - client->ps.stats[STAT_HEALTH] = 0; - if( g_gametype.integer != GT_WOLF_LMS ) { - if( g_maxlives.integer > 0 ) { + // DHM - Nerve :: Start players in limbo mode if they change teams + // during the match + // tjw: don't kill the player unless they're dead already + if(client->sess.sessionTeam != TEAM_SPECTATOR && + level.time - level.startTime > FRAMETIME * GAME_INIT_FRAMES) { + + // tjw: otherwise players lose 2 lives. + // tjw: wtf does gametype have to do with it? + if(g_gametype.integer != GT_WOLF_LMS && maxlives) client->ps.persistant[PERS_RESPAWNS_LEFT]++; - } - } + + if(health <= 0 || g_teamChangeKills.integer) { + ent->health = 0; + ent->r.contents = CONTENTS_CORPSE; + client->ps.pm_type = PM_DEAD; + client->ps.stats[STAT_HEALTH] = 0; - limbo(ent, qfalse); + limbo(ent, qfalse); + } } if(client->sess.sessionTeam != TEAM_SPECTATOR) { @@ -1845,7 +2253,11 @@ Initializes all non-persistant parts of playerState ============ */ -void ClientSpawn( gentity_t *ent, qboolean revived ) +void ClientSpawn( + gentity_t *ent, + qboolean revived, + qboolean teamChange, + qboolean restoreHealth) { int index; vec3_t spawn_origin, spawn_angles; @@ -1934,10 +2346,12 @@ { qboolean set = client->maxlivescalced; + int XPSave_lives = client->XPSave_lives; memset( client, 0, sizeof(*client) ); client->maxlivescalced = set; + client->XPSave_lives = XPSave_lives; } client->pers = saved; @@ -1964,6 +2378,7 @@ // clear entity values client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + client->ps.eFlags = flags; // MrE: use capsules for AI and player //client->ps.eFlags |= EF_CAPSULE; @@ -2000,7 +2415,8 @@ client->ps.standViewHeight = DEFAULT_VIEWHEIGHT; client->ps.deadViewHeight = DEAD_VIEWHEIGHT; - client->ps.crouchMaxZ = client->ps.maxs[2] - (client->ps.standViewHeight - client->ps.crouchViewHeight); + //client->ps.crouchMaxZ = client->ps.maxs[2] - (client->ps.standViewHeight - client->ps.crouchViewHeight); + client->ps.crouchMaxZ = CROUCH_VIEWHEIGHT; client->ps.runSpeedScale = 0.8; client->ps.sprintSpeedScale = 1.1; @@ -2036,7 +2452,7 @@ client->sess.playerType = client->sess.latchPlayerType; - if( G_IsWeaponDisabled( ent, client->sess.latchPlayerWeapon ) ) { + if( G_IsWeaponDisabled( ent, client->sess.latchPlayerWeapon, qtrue ) ) { bg_playerclass_t* classInfo = BG_PlayerClassForPlayerState( &ent->client->ps ); client->sess.latchPlayerWeapon = classInfo->classWeapons[0]; update = qtrue; @@ -2047,7 +2463,7 @@ update = qtrue; } - if( G_IsWeaponDisabled( ent, client->sess.playerWeapon ) ) { + if( G_IsWeaponDisabled( ent, client->sess.playerWeapon, qtrue ) ) { bg_playerclass_t* classInfo = BG_PlayerClassForPlayerState( &ent->client->ps ); client->sess.playerWeapon = classInfo->classWeapons[0]; update = qtrue; @@ -2055,7 +2471,7 @@ client->sess.playerWeapon2 = client->sess.latchPlayerWeapon2; - if( update ) { + if( update || teamChange ) { ClientUserinfoChanged( index ); } } @@ -2065,8 +2481,10 @@ // Xian - Moved the invul. stuff out of SetWolfSpawnWeapons and put it here for clarity if ( g_fastres.integer == 1 && revived ) client->ps.powerups[PW_INVULNERABLE] = level.time + 1000; - else + else if(revived) client->ps.powerups[PW_INVULNERABLE] = level.time + 3000; + else + client->ps.powerups[PW_INVULNERABLE] = level.time + (g_spawnInvul.integer * 1000); } // End Xian @@ -2089,11 +2507,16 @@ // JPW NERVE ***NOTE*** the following line is order-dependent and must *FOLLOW* SetWolfSpawnWeapons() in multiplayer // AddMedicTeamBonus() now adds medic team bonus and stores in ps.stats[STAT_MAX_HEALTH]. - if( client->sess.skill[SK_BATTLE_SENSE] >= 3 ) - // We get some extra max health, but don't spawn with that much - ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] - 15; - else - ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; + if(restoreHealth) { + if( client->sess.skill[SK_BATTLE_SENSE] >= 3 ) + // We get some extra max health, but don't spawn with that much + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH] - 15; + else + ent->health = client->ps.stats[STAT_HEALTH] = client->ps.stats[STAT_MAX_HEALTH]; + } + else { + client->ps.stats[STAT_HEALTH] = ent->health; + } G_SetOrigin( ent, spawn_origin ); VectorCopy( spawn_origin, client->ps.origin ); @@ -2209,9 +2632,8 @@ return; } -#ifdef USEXPSTORAGE - G_AddXPBackup( ent ); -#endif // USEXPSTORAGE + + if(g_XPSave.integer & XPSF_ENABLE) G_AddXPBackup( ent ); G_RemoveClientFromFireteams( clientNum, qtrue, qfalse ); G_RemoveFromAllIgnoreLists( clientNum ); diff -urN --exclude=.svn et.orig/src/game/g_client.c.bak etpub/src/game/g_client.c.bak --- et.orig/src/game/g_client.c.bak 1969-12-31 18:00:00.000000000 -0600 +++ etpub/src/game/g_client.c.bak 2005-03-11 20:33:37.000000000 -0600 @@ -0,0 +1,2820 @@ +#include "g_local.h" +#include "../../etmain/ui/menudef.h" + +// g_client.c -- client functions that don't happen every frame + +// Ridah, new bounding box +//static vec3_t playerMins = {-15, -15, -24}; +//static vec3_t playerMaxs = {15, 15, 32}; +vec3_t playerMins = {-18, -18, -24}; +vec3_t playerMaxs = {18, 18, 48}; +// done. + +/*QUAKED info_player_deathmatch (1 0 1) (-18 -18 -24) (18 18 48) +potential spawning position for deathmatch games. +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. +If the start position is targeting an entity, the players camera will start out facing that ent (like an info_notnull) +*/ +void SP_info_player_deathmatch( gentity_t *ent ) { + int i; + vec3_t dir; + + G_SpawnInt( "nobots", "0", &i); + if ( i ) { + ent->flags |= FL_NO_BOTS; + } + G_SpawnInt( "nohumans", "0", &i ); + if ( i ) { + ent->flags |= FL_NO_HUMANS; + } + + ent->enemy = G_PickTarget( ent->target ); + if(ent->enemy) + { + VectorSubtract( ent->enemy->s.origin, ent->s.origin, dir ); + vectoangles( dir, ent->s.angles ); + } + +} + +//----(SA) added +/*QUAKED info_player_checkpoint (1 0 0) (-16 -16 -24) (16 16 32) a b c d +these are start points /after/ the level start +the letter (a b c d) designates the checkpoint that needs to be complete in order to use this start position +*/ +void SP_info_player_checkpoint(gentity_t *ent) { + ent->classname = "info_player_checkpoint"; + SP_info_player_deathmatch( ent ); +} + +//----(SA) end + + +/*QUAKED info_player_start (1 0 0) (-18 -18 -24) (18 18 48) +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) AXIS ALLIED +The intermission will be viewed from this point. Target an info_notnull for the view direction. +*/ +void SP_info_player_intermission( gentity_t *ent ) { + +} + +extern void BotSpeedBonus(int clientNum); + + +/* +======================================================================= + + SelectSpawnPoint + +======================================================================= +*/ + +/* +================ +SpotWouldTelefrag + +================ +*/ +qboolean SpotWouldTelefrag( gentity_t *spot ) { + int i, num; + int touch[MAX_GENTITIES]; + gentity_t *hit; + vec3_t mins, maxs; + + VectorAdd( spot->r.currentOrigin, playerMins, mins ); + VectorAdd( spot->r.currentOrigin, playerMaxs, maxs ); + num = trap_EntitiesInBox( mins, maxs, touch, MAX_GENTITIES ); + + for (i=0 ; iclient && hit->client->ps.stats[STAT_HEALTH] > 0 ) { + return qtrue; + } + + } + + return qfalse; +} + +/* +================ +SelectNearestDeathmatchSpawnPoint + +Find the spot that we DON'T want to use +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectNearestDeathmatchSpawnPoint( vec3_t from ) { + gentity_t *spot; + vec3_t delta; + float dist, nearestDist; + gentity_t *nearestSpot; + + nearestDist = 999999; + nearestSpot = NULL; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + + VectorSubtract( spot->r.currentOrigin, from, delta ); + dist = VectorLength( delta ); + if ( dist < nearestDist ) { + nearestDist = dist; + nearestSpot = spot; + } + } + + return nearestSpot; +} + + +/* +================ +SelectRandomDeathmatchSpawnPoint + +go to a random point that doesn't telefrag +================ +*/ +#define MAX_SPAWN_POINTS 128 +gentity_t *SelectRandomDeathmatchSpawnPoint( void ) { + gentity_t *spot; + int count; + int selection; + gentity_t *spots[MAX_SPAWN_POINTS]; + + count = 0; + spot = NULL; + + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( SpotWouldTelefrag( spot ) ) { + continue; + } + spots[ count ] = spot; + count++; + } + + if ( !count ) { // no spots that won't telefrag + return G_Find( NULL, FOFS(classname), "info_player_deathmatch"); + } + + selection = rand() % count; + return spots[ selection ]; +} + + +/* +=========== +SelectSpawnPoint + +Chooses a player start, deathmatch start, etc +============ +*/ +gentity_t *SelectSpawnPoint ( vec3_t avoidPoint, vec3_t origin, vec3_t angles ) { + gentity_t *spot; + gentity_t *nearestSpot; + + nearestSpot = SelectNearestDeathmatchSpawnPoint( avoidPoint ); + + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // roll again if it would be real close to point of death + spot = SelectRandomDeathmatchSpawnPoint ( ); + if ( spot == nearestSpot ) { + // last try + spot = SelectRandomDeathmatchSpawnPoint ( ); + } + } + + // find a single player start spot + if (!spot) { + G_Error( "Couldn't find a spawn point" ); + } + + VectorCopy (spot->r.currentOrigin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +} + +/* +=========== +SelectInitialSpawnPoint + +Try to find a spawn point marked 'initial', otherwise +use normal spawn selection. +============ +*/ +/*gentity_t *SelectInitialSpawnPoint( vec3_t origin, vec3_t angles ) { + gentity_t *spot; + + spot = NULL; + while ((spot = G_Find (spot, FOFS(classname), "info_player_deathmatch")) != NULL) { + if ( spot->spawnflags & 1 ) { + break; + } + } + + if ( !spot || SpotWouldTelefrag( spot ) ) { + return SelectSpawnPoint( vec3_origin, origin, angles ); + } + + VectorCopy (spot->r.currentOrigin, origin); + origin[2] += 9; + VectorCopy (spot->s.angles, angles); + + return spot; +}*/ + +/* +=========== +SelectSpectatorSpawnPoint + +============ +*/ +gentity_t *SelectSpectatorSpawnPoint( vec3_t origin, vec3_t angles ) { + FindIntermissionPoint(); + + VectorCopy( level.intermission_origin, origin ); + VectorCopy( level.intermission_angle, angles ); + + return NULL; +} + +/* +======================================================================= + +BODYQUE + +======================================================================= +*/ + +/* +=============== +InitBodyQue +=============== +*/ +void InitBodyQue (void) { + int i; + gentity_t *ent; + + level.bodyQueIndex = 0; + for (i=0; iclassname = "bodyque"; + ent->neverFree = qtrue; + level.bodyQue[i] = ent; + } +} + +/* +============= +BodyUnlink + +Called by BodySink +============= +*/ +void BodyUnlink( gentity_t *ent ) { + trap_UnlinkEntity( ent ); + ent->physicsObject = qfalse; +} + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink2( gentity_t *ent ) { + ent->physicsObject = qfalse; + // tjw: BODY_TIME is how long they last all together, not just sink anim + //ent->nextthink = level.time + BODY_TIME(BODY_TEAM(ent))+1500; + ent->nextthink = level.time + 4000; + ent->think = BodyUnlink; + ent->s.pos.trType = TR_LINEAR; + ent->s.pos.trTime = level.time; + VectorCopy( ent->r.currentOrigin, ent->s.pos.trBase ); + VectorSet( ent->s.pos.trDelta, 0, 0, -8 ); +} + +/* +============= +BodySink + +After sitting around for five seconds, fall into the ground and dissapear +============= +*/ +void BodySink( gentity_t *ent ) { + if( ent->activator ) { + // see if parent is still disguised + if( ent->activator->client->ps.powerups[PW_OPS_DISGUISED] ) { + ent->nextthink = level.time + 100; + return; + } else { + ent->activator = NULL; + } + } + + BodySink2( ent ); +} + + +/* +============= +CopyToBodyQue + +A player is respawning, so make an entity that looks +just like the existing corpse to leave behind. +============= +*/ +void CopyToBodyQue( gentity_t *ent ) { + gentity_t *body; + int contents, i; + + trap_UnlinkEntity (ent); + + // if client is in a nodrop area, don't leave the body + contents = trap_PointContents( ent->client->ps.origin, -1 ); + if ( contents & CONTENTS_NODROP ) { + return; + } + + // grab a body que and cycle to the next one + body = level.bodyQue[ level.bodyQueIndex ]; + level.bodyQueIndex = (level.bodyQueIndex + 1) % BODY_QUEUE_SIZE; + + // Gordon: um, what on earth was this here for? +// trap_UnlinkEntity (body); + + body->s = ent->s; + body->s.eFlags = EF_DEAD; // clear EF_TALK, etc + + if( ent->client->ps.eFlags & EF_HEADSHOT ) { + body->s.eFlags |= EF_HEADSHOT; // make sure the dead body draws no head (if killed that way) + } + + body->s.eType = ET_CORPSE; + body->classname = "corpse"; + body->s.powerups = 0; // clear powerups + body->s.loopSound = 0; // clear lava burning + body->s.number = body - g_entities; + body->timestamp = level.time; + body->physicsObject = qtrue; + body->physicsBounce = 0; // don't bounce + if ( body->s.groundEntityNum == ENTITYNUM_NONE ) { + body->s.pos.trType = TR_GRAVITY; + body->s.pos.trTime = level.time; + VectorCopy( ent->client->ps.velocity, body->s.pos.trDelta ); + } else { + body->s.pos.trType = TR_STATIONARY; + } + body->s.event = 0; + + // DHM - Clear out event system + for( i=0; is.events[i] = 0; + body->s.eventSequence = 0; + + // DHM - Nerve + // change the animation to the last-frame only, so the sequence + // doesn't repeat anew for the body + switch ( body->s.legsAnim & ~ANIM_TOGGLEBIT ) + { + case BOTH_DEATH1: + case BOTH_DEAD1: + default: + 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: + body->s.torsoAnim = body->s.legsAnim = BOTH_DEAD3; + break; + } + + body->r.svFlags = ent->r.svFlags & ~SVF_BOT; + VectorCopy (ent->r.mins, body->r.mins); + VectorCopy (ent->r.maxs, body->r.maxs); + VectorCopy (ent->r.absmin, body->r.absmin); + VectorCopy (ent->r.absmax, body->r.absmax); + + // ydnar: bodies have lower bounding box + body->r.maxs[ 2 ] = 0; + + body->clipmask = CONTENTS_SOLID | CONTENTS_PLAYERCLIP; + // DHM - Nerve :: allow bullets to pass through bbox + // Gordon: need something to allow the hint for covert ops + body->r.contents = CONTENTS_CORPSE; + body->r.ownerNum = ent->r.ownerNum; + + BODY_TEAM(body) = ent->client->sess.sessionTeam; + BODY_CLASS(body) = ent->client->sess.playerType; + BODY_CHARACTER(body) = ent->client->pers.characterIndex; + BODY_VALUE(body) = 0; + BODY_WEAPON(body) = ent->client->sess.playerWeapon; + + body->s.time2 = 0; + + body->activator = NULL; + + body->nextthink = level.time + BODY_TIME(ent->client->sess.sessionTeam); + + body->think = BodySink; + + body->die = body_die; + + // don't take more damage if already gibbed + if ( ent->health <= GIB_HEALTH ) { + body->takedamage = qfalse; + } else { + body->takedamage = qtrue; + } + + + VectorCopy ( body->s.pos.trBase, body->r.currentOrigin ); + trap_LinkEntity (body); +} + +//====================================================================== + + +/* +================== +SetClientViewAngle + +================== +*/ +void 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); +} + +void SetClientViewAnglePitch( gentity_t *ent, vec_t angle ) { + int cmdAngle; + + cmdAngle = ANGLE2SHORT(angle); + ent->client->ps.delta_angles[PITCH] = cmdAngle - ent->client->pers.cmd.angles[PITCH]; + + ent->s.angles[ PITCH ] = 0; + VectorCopy( ent->s.angles, ent->client->ps.viewangles); +} + + +void G_DropLimboHealth(gentity_t *ent) +{ + vec3_t launchvel, launchspot; + int packs = 0; + int i; + int cwt; + + if(g_dropHealth.integer == 0) return; + if(!ent->client) return; + if(ent->client->sess.playerType != PC_MEDIC) + return; + + packs = g_dropHealth.integer; + if(g_dropHealth.integer == -1) { + packs = 5; // max packs + cwt = ent->client->ps.classWeaponTime; + ent->client->ps.classWeaponTime += + (level.time + - ent->client->deathTime + + 10000); + } + for(i=0; iclient->ps.classWeaponTime >= level.time) { + + break; + } + launchvel[0] = crandom(); + launchvel[1] = crandom(); + launchvel[2] = 0; + VectorScale(launchvel, 100, launchvel); + launchvel[2] = (100 + (g_tossDistance.integer * 10)); + VectorCopy(ent->r.currentOrigin, launchspot); + + Weapon_Medic_Ext(ent, + launchspot, + launchspot, + launchvel); + } + ent->client->ps.classWeaponTime = cwt; +} + +void G_DropLimboAmmo(gentity_t *ent) +{ + vec3_t launchvel, launchspot; + int packs = 0; + int i; + int cwt; + + if(g_dropAmmo.integer == 0) return; + if(!ent->client) return; + if(ent->client->sess.playerType != PC_FIELDOPS) + return; + + packs = g_dropAmmo.integer; + if(g_dropAmmo.integer == -1) { + packs = 5; // max packs + cwt = ent->client->ps.classWeaponTime; + // adjust the charge timer so that the + // fops only drops as much ammo as he + // could have before he died. + // don't ask me where the 10000 comes + // from, it just makes this more + // accurate. + ent->client->ps.classWeaponTime += + (level.time + - ent->client->deathTime + + 10000); + } + for(i=0; iclient->ps.classWeaponTime >= level.time) { + + break; + } + launchvel[0] = crandom(); + launchvel[1] = crandom(); + launchvel[2] = 0; + + VectorScale(launchvel, 100, launchvel); + launchvel[2] = (100 + (g_tossDistance.integer * 10)); + VectorCopy(ent->r.currentOrigin, launchspot); + + Weapon_MagicAmmo_Ext(ent, + launchspot, + launchspot, + launchvel); + } + ent->client->ps.classWeaponTime = cwt; +} + + +/* JPW NERVE +================ +limbo +================ +*/ +void limbo( gentity_t *ent, qboolean makeCorpse ) +{ + int i,contents; + //int startclient = ent->client->sess.spectatorClient; + int startclient = ent->client->ps.clientNum; + + if(ent->r.svFlags & SVF_POW) { + return; + } + + if (!(ent->client->ps.pm_flags & PMF_LIMBO)) { + + if( ent->client->ps.persistant[PERS_RESPAWNS_LEFT] == 0 ) { + if( g_maxlivesRespawnPenalty.integer ) { + ent->client->ps.persistant[PERS_RESPAWNS_PENALTY] = g_maxlivesRespawnPenalty.integer; + } else { + ent->client->ps.persistant[PERS_RESPAWNS_PENALTY] = -1; + } + } + + // DHM - Nerve :: First save off persistant info we'll need for respawn + for( i = 0; i < MAX_PERSISTANT; i++) { + ent->client->saved_persistant[i] = ent->client->ps.persistant[i]; + } + + ent->client->ps.pm_flags |= PMF_LIMBO; + ent->client->ps.pm_flags |= PMF_FOLLOW; + + + if( makeCorpse ) { + G_DropLimboHealth(ent); + G_DropLimboAmmo(ent); + CopyToBodyQue (ent); // make a nice looking corpse + } else { + trap_UnlinkEntity (ent); + } + + // DHM - Nerve :: reset these values + ent->client->ps.viewlocked = 0; + ent->client->ps.viewlocked_entNum = 0; + + ent->r.maxs[2] = 0; + ent->r.currentOrigin[2] += 8; + contents = trap_PointContents( ent->r.currentOrigin, -1 ); // drop stuff + ent->s.weapon = ent->client->limboDropWeapon; // stored in player_die() + if ( makeCorpse && !( contents & CONTENTS_NODROP ) ) { + TossClientItems( ent ); + } + + ent->client->sess.spectatorClient = startclient; + Cmd_FollowCycle_f(ent,1); // get fresh spectatorClient + + if (ent->client->sess.spectatorClient == startclient) { + // No one to follow, so just stay put + ent->client->sess.spectatorState = SPECTATOR_FREE; + } + else + ent->client->sess.spectatorState = SPECTATOR_FOLLOW; + +// ClientUserinfoChanged( ent->client - level.clients ); // NERVE - SMF - don't do this + if (ent->client->sess.sessionTeam == TEAM_AXIS) { + ent->client->deployQueueNumber = level.redNumWaiting; + level.redNumWaiting++; + } + else if (ent->client->sess.sessionTeam == TEAM_ALLIES) { + ent->client->deployQueueNumber = level.blueNumWaiting; + level.blueNumWaiting++; + } + + for(i=0; ips.pm_flags & PMF_LIMBO) || + (cl->sess.sessionTeam == TEAM_SPECTATOR && cl->sess.spectatorState == SPECTATOR_FOLLOW)) && + cl->sess.spectatorClient == ent - g_entities) {//ent->s.number ) { + Cmd_FollowCycle_f( &g_entities[level.sortedClients[i]], 1 ); + } + } + } +} + +/* JPW NERVE +================ +reinforce +================ +// -- called when time expires for a team deployment cycle and there is at least one guy ready to go +*/ +void reinforce(gentity_t *ent) { + int p, team;// numDeployable=0, finished=0; // TTimo unused + char *classname; + gclient_t *rclient; + char userinfo[MAX_INFO_STRING], *respawnStr; + + if (ent->r.svFlags & SVF_BOT) { + trap_GetUserinfo( ent->s.number, userinfo, sizeof(userinfo) ); + respawnStr = Info_ValueForKey( userinfo, "respawn" ); + if (!Q_stricmp( respawnStr, "no" ) || !Q_stricmp( respawnStr, "off" )) { + return; // no respawns + } + } + + if (!(ent->client->ps.pm_flags & PMF_LIMBO)) { + G_Printf("player already deployed, skipping\n"); + return; + } + + if(ent->client->pers.mvCount > 0) { + G_smvRemoveInvalidClients(ent, TEAM_AXIS); + G_smvRemoveInvalidClients(ent, TEAM_ALLIES); + } + + // get team to deploy from passed entity + team = ent->client->sess.sessionTeam; + + // find number active team spawnpoints + if (team == TEAM_AXIS) + classname = "team_CTF_redspawn"; + else if (team == TEAM_ALLIES) + classname = "team_CTF_bluespawn"; + else + assert(0); + + // DHM - Nerve :: restore persistant data now that we're out of Limbo + rclient = ent->client; + for (p=0; pps.persistant[p] = rclient->saved_persistant[p]; + // dhm + + respawn(ent); +} +// jpw + + +/* +================ +respawn +================ +*/ +void respawn( gentity_t *ent ) { + +#ifdef SAVEGAME_SUPPORT + if( g_gametype.integer == GT_SINGLE_PLAYER ) { + if (g_reloading.integer || saveGamePending) { + return; + } + } +#endif // SAVEGAME_SUPPORT + + ent->client->ps.pm_flags &= ~PMF_LIMBO; // JPW NERVE turns off limbo + + // DHM - Nerve :: Decrease the number of respawns left + if( g_gametype.integer != GT_WOLF_LMS ) { + if( ent->client->ps.persistant[PERS_RESPAWNS_LEFT] > 0 && g_gamestate.integer == GS_PLAYING ) { + if( g_maxlives.integer > 0 ) { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT]--; + } else { + if( g_alliedmaxlives.integer > 0 && ent->client->sess.sessionTeam == TEAM_ALLIES ) { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT]--; + } + if( g_axismaxlives.integer > 0 && ent->client->sess.sessionTeam == TEAM_AXIS ) { + ent->client->ps.persistant[PERS_RESPAWNS_LEFT]--; + } + } + } + } + + G_DPrintf( "Respawning %s, %i lives left\n", ent->client->pers.netname, ent->client->ps.persistant[PERS_RESPAWNS_LEFT]); + + ClientSpawn(ent, qfalse, qfalse, qtrue); + + // DHM - Nerve :: Add back if we decide to have a spawn effect + // add a teleportation effect + //tent = G_TempEntity( ent->client->ps.origin, EV_PLAYER_TELEPORT_IN ); + //tent->s.clientNum = ent->s.clientNum; +} + +// NERVE - SMF - merge from team arena +/* +================ +TeamCount + +Returns number of players on a team +================ +*/ +team_t TeamCount(int ignoreClientNum, int team) +{ + int i, ref, count = 0; + + for(i=0; i counts[TEAM_AXIS]) return(TEAM_AXIS); + if(counts[TEAM_AXIS] > counts[TEAM_ALLIES]) return(TEAM_ALLIES); + + // equal team count, so join the team with the lowest score + return(((level.teamScores[TEAM_ALLIES] > level.teamScores[TEAM_AXIS]) ? TEAM_AXIS : TEAM_ALLIES)); +} + +/* +=========== +AddExtraSpawnAmmo +=========== +*/ +static void AddExtraSpawnAmmo( gclient_t *client, weapon_t weaponNum) +{ + switch( weaponNum ) { + //case WP_KNIFE: + case WP_LUGER: + case WP_COLT: + case WP_STEN: + case WP_SILENCER: + case WP_CARBINE: + case WP_KAR98: + case WP_SILENCED_COLT: + if( client->sess.skill[SK_LIGHT_WEAPONS] >= 1 ) + client->ps.ammo[BG_FindAmmoForWeapon(weaponNum)] += GetAmmoTableData(weaponNum)->maxclip; + break; + case WP_MP40: + case WP_THOMPSON: + if( (client->sess.skill[SK_FIRST_AID] >= 1 && client->sess.playerType == PC_MEDIC) || client->sess.skill[SK_LIGHT_WEAPONS] >= 1 ) { + client->ps.ammo[BG_FindAmmoForWeapon(weaponNum)] += GetAmmoTableData(weaponNum)->maxclip; + } + break; + case WP_M7: + case WP_GPG40: + if( client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 1 ) + client->ps.ammo[BG_FindAmmoForWeapon(weaponNum)] += 4; + break; + case WP_GRENADE_PINEAPPLE: + case WP_GRENADE_LAUNCHER: + if( client->sess.playerType == PC_ENGINEER ) { + if( client->sess.skill[SK_EXPLOSIVES_AND_CONSTRUCTION] >= 1 ) { + client->ps.ammoclip[BG_FindAmmoForWeapon(weaponNum)] += 4; + } + } + if( client->sess.playerType == PC_MEDIC ) { + if( client->sess.skill[SK_FIRST_AID] >= 1 ) { + client->ps.ammoclip[BG_FindAmmoForWeapon(weaponNum)] += 1; + } + } + break; + /*case WP_MOBILE_MG42: + case WP_PANZERFAUST: + case WP_FLAMETHROWER: + if( client->sess.skill[SK_HEAVY_WEAPONS] >= 1 ) + client->ps.ammo[BG_FindAmmoForWeapon(weaponNum)] += GetAmmoTableData(weaponNum)->maxclip; + break; + case WP_MORTAR: + case WP_MORTAR_SET: + if( client->sess.skill[SK_HEAVY_WEAPONS] >= 1 ) + client->ps.ammo[BG_FindAmmoForWeapon(weaponNum)] += 2; + break;*/ + case WP_MEDIC_SYRINGE: + case WP_MEDIC_ADRENALINE: + if( client->sess.skill[SK_FIRST_AID] >= 2 ) + client->ps.ammoclip[BG_FindAmmoForWeapon(weaponNum)] += 2; + break; + case WP_GARAND: + case WP_K43: + case WP_FG42: + if( client->sess.skill[SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS] >= 1 || client->sess.skill[SK_LIGHT_WEAPONS] >= 1 ) + client->ps.ammo[BG_FindAmmoForWeapon(weaponNum)] += GetAmmoTableData(weaponNum)->maxclip; + break; + case WP_GARAND_SCOPE: + case WP_K43_SCOPE: + case WP_FG42SCOPE: + if( client->sess.skill[SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS] >= 1 ) + client->ps.ammo[BG_FindAmmoForWeapon(weaponNum)] += GetAmmoTableData(weaponNum)->maxclip; + break; + default: + break; + } +} + +qboolean AddWeaponToPlayer( gclient_t *client, weapon_t weapon, int ammo, int ammoclip, qboolean setcurrent ) { + COM_BitSet( client->ps.weapons, weapon ); + client->ps.ammoclip[BG_FindClipForWeapon(weapon)] = ammoclip; + client->ps.ammo[BG_FindAmmoForWeapon(weapon)] = ammo; + if( setcurrent ) + client->ps.weapon = weapon; + + // skill handling + AddExtraSpawnAmmo( client, weapon ); + + return qtrue; +} + +void BotSetPOW(int entityNum, qboolean isPOW); + + + +/* + G_AddClassSpecificTools //tjw + This stuff was stripped out of SetWolfSpawnWeapons() so it can + be used for class stealing. It needed cleaning up badly anyway. + It still does. + */ +void G_AddClassSpecificTools(gclient_t *client) +{ + int pc = client->sess.playerType; + qboolean add_binocs = qfalse; + + if(client->sess.skill[SK_BATTLE_SENSE] >= 1) + add_binocs = qtrue; + + switch(client->sess.playerType) { + case PC_ENGINEER: + AddWeaponToPlayer( client, WP_DYNAMITE, 0, 1, qfalse ); + AddWeaponToPlayer( client, WP_PLIERS, 0, 1, qfalse ); + AddWeaponToPlayer( client, + WP_LANDMINE, + GetAmmoTableData(WP_LANDMINE)->defaultStartingAmmo, + GetAmmoTableData(WP_LANDMINE)->defaultStartingClip, + qfalse ); + break; + case PC_COVERTOPS: + add_binocs = qtrue; + AddWeaponToPlayer(client, + WP_SMOKE_BOMB, + GetAmmoTableData(WP_SMOKE_BOMB)->defaultStartingAmmo, + GetAmmoTableData(WP_SMOKE_BOMB)->defaultStartingClip, + qfalse); + // See if we already have a satchel charge placed + // this needs to be here for class stealing + if( G_FindSatchel( &g_entities[client->ps.clientNum] ) ) { + AddWeaponToPlayer( client, WP_SATCHEL, 0, 0, qfalse ); + AddWeaponToPlayer( client, WP_SATCHEL_DET, 0, 1, qfalse ); + } else { + AddWeaponToPlayer( client, WP_SATCHEL, 0, 1, qfalse ); + AddWeaponToPlayer( client, WP_SATCHEL_DET, 0, 0, qfalse ); + } + break; + case PC_FIELDOPS: + add_binocs = qtrue; + AddWeaponToPlayer(client, WP_AMMO, 0, 1, qfalse); + AddWeaponToPlayer( client, + WP_SMOKE_MARKER, + GetAmmoTableData(WP_SMOKE_MARKER)->defaultStartingAmmo, + GetAmmoTableData(WP_SMOKE_MARKER)->defaultStartingClip, + qfalse); + break; + case PC_MEDIC: + AddWeaponToPlayer(client, + WP_MEDIC_SYRINGE, + GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingClip, + qfalse); + if( client->sess.skill[SK_FIRST_AID] >= 4 ) + AddWeaponToPlayer(client, + WP_MEDIC_ADRENALINE, + GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingClip, + qfalse); + + AddWeaponToPlayer(client, + WP_MEDKIT, + GetAmmoTableData(WP_MEDKIT)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDKIT)->defaultStartingClip, + qfalse); + break; + case PC_SOLDIER: + break; + } + if(add_binocs) { + if(AddWeaponToPlayer(client, WP_BINOCULARS, 1, 0, qfalse)) { + client->ps.stats[STAT_KEYS] |= ( 1 << INV_BINOCS ); + } + } + //// Josh: adren in different classes when level 4 + if( (g_skills.integer & SKILLS_ADREN) + && client->sess.skill[SK_FIRST_AID] >= 4 ) + AddWeaponToPlayer(client, + WP_MEDIC_ADRENALINE, + GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDIC_ADRENALINE)->defaultStartingClip, + qfalse); + +} + +qboolean _SetSoldierSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2, w3 = 0; + weapon_t ws = 0; + + w = client->sess.latchPlayerWeapon; + if(client->sess.sessionTeam == TEAM_AXIS) { + if(!w) w = WP_MP40; + switch(w) { + case WP_MP40: + case WP_PANZERFAUST: + case WP_MORTAR: + case WP_FLAMETHROWER: + case WP_MOBILE_MG42: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 4, qfalse); + w2 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_LUGER; + } + if( client->sess.skill[SK_HEAVY_WEAPONS] >= 4) { + w3 = WP_MP40; + if(!(g_weapons.integer & WPF_L4HW_KEEPS_PISTOL)) + w2 = 0; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + if(!w) w = WP_THOMPSON; + switch(w) { + case WP_THOMPSON: + case WP_PANZERFAUST: + case WP_MORTAR: + case WP_FLAMETHROWER: + case WP_MOBILE_MG42: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 4, qfalse); + w2 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_COLT; + } + if( client->sess.skill[SK_HEAVY_WEAPONS] >= 4) { + w3 = WP_THOMPSON; + if(!(g_weapons.integer & WPF_L4HW_KEEPS_PISTOL)) + w2 = 0; + } + } + else { + return qfalse; + } + + if(!AddWeaponToPlayer(client, + w, + GetAmmoTableData(w)->defaultStartingAmmo, + GetAmmoTableData(w)->defaultStartingClip, + qtrue)) { + + return qfalse; + } + + if(w == WP_MOBILE_MG42) { + ws = WP_MOBILE_MG42_SET; + } + else if(w == WP_MORTAR) { + ws = WP_MORTAR_SET; + } + + if(ws) { + AddWeaponToPlayer(client, + ws, + GetAmmoTableData(ws)->defaultStartingAmmo, + GetAmmoTableData(ws)->defaultStartingClip, + qfalse); + } + if(w2 == WP_AKIMBO_LUGER || w2 == WP_AKIMBO_COLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + if(w3) { + AddWeaponToPlayer(client, + w3, + 2*GetAmmoTableData(w3)->defaultStartingAmmo, + GetAmmoTableData(w3)->defaultStartingClip, + qfalse); + } + return qtrue; +} + +qboolean _SetMedicSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2; + + w = client->sess.latchPlayerWeapon; + if(client->sess.sessionTeam == TEAM_AXIS) { + if(!w) w = WP_MP40; + switch(w) { + case WP_MP40: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 1, qfalse); + w2 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_LUGER; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + if(!w) w = WP_THOMPSON; + switch(w) { + case WP_THOMPSON: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 1, qfalse); + w2 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_COLT; + } + } + else { + return qfalse; + } + + if(!AddWeaponToPlayer(client, + w, + 0, + GetAmmoTableData(w)->defaultStartingClip, + qtrue)) { + + return qfalse; + } + if(w2 == WP_AKIMBO_LUGER || w2 == WP_AKIMBO_COLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + return qtrue; +} + +qboolean _SetEngineerSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2; + + w = client->sess.latchPlayerWeapon; + if(client->sess.sessionTeam == TEAM_AXIS) { + if(!w) w = WP_MP40; + switch(w) { + case WP_KAR98: + case WP_MP40: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 4, qfalse); + w2 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_LUGER; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + if(!w) w = WP_THOMPSON; + switch(w) { + case WP_CARBINE: + case WP_THOMPSON: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 4, qfalse); + w2 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_COLT; + } + } + else { + return qfalse; + } + + if(!AddWeaponToPlayer(client, + w, + GetAmmoTableData(w)->defaultStartingAmmo, + GetAmmoTableData(w)->defaultStartingClip, + qtrue)) { + + return qfalse; + } + + if(w == WP_KAR98) { + AddWeaponToPlayer(client, + WP_GPG40, + GetAmmoTableData(WP_GPG40)->defaultStartingAmmo, + GetAmmoTableData(WP_GPG40)->defaultStartingClip, + qfalse); + } + else if(w == WP_CARBINE) { + AddWeaponToPlayer(client, + WP_M7, + GetAmmoTableData(WP_M7)->defaultStartingAmmo, + GetAmmoTableData(WP_M7)->defaultStartingClip, + qfalse); + } + if(w2 == WP_AKIMBO_LUGER || w2 == WP_AKIMBO_COLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + return qtrue; +} + +qboolean _SetFieldOpSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2; + + w = client->sess.latchPlayerWeapon; + if(client->sess.sessionTeam == TEAM_AXIS) { + if(!w) w = WP_MP40; + switch(w) { + case WP_MP40: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 1, qfalse); + w2 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_LUGER; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + if(!w) w = WP_THOMPSON; + switch(w) { + case WP_THOMPSON: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 1, qfalse); + w2 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_COLT; + } + } + else { + return qfalse; + } + + if(!AddWeaponToPlayer(client, + w, + GetAmmoTableData(w)->defaultStartingAmmo, + GetAmmoTableData(w)->defaultStartingClip, + qtrue)) { + + return qfalse; + } + if(w2 == WP_AKIMBO_LUGER || w2 == WP_AKIMBO_COLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + return qtrue; + +} + +qboolean _SetCovertSpawnWeapons(gclient_t *client) +{ + weapon_t w; + weapon_t w2, w3; + weapon_t ws = 0; + + w = client->sess.latchPlayerWeapon; + if(client->sess.sessionTeam== TEAM_AXIS) { + if(!w) w = WP_STEN; + switch(w) { + case WP_K43: + case WP_FG42: + case WP_STEN: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_LAUNCHER, 0, 2, qfalse); + w2 = WP_SILENCER; + w3 = WP_LUGER; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_SILENCEDLUGER; + w3 = 0; + } + } + else if(client->sess.sessionTeam == TEAM_ALLIES) { + if(!w) w = WP_STEN; + switch(w) { + case WP_GARAND: + case WP_FG42: + case WP_STEN: + break; + default: + return qfalse; + } + AddWeaponToPlayer(client, WP_GRENADE_PINEAPPLE, 0, 2, qfalse); + w2 = WP_SILENCED_COLT; + w3 = WP_COLT; + if(client->sess.skill[SK_LIGHT_WEAPONS] >= 4) { + w2 = WP_AKIMBO_SILENCEDCOLT; + w3 = 0; + } + } + else { + return qfalse; + } + + if(!AddWeaponToPlayer(client, + w, + GetAmmoTableData(w)->defaultStartingAmmo, + GetAmmoTableData(w)->defaultStartingClip, + qtrue)) { + + return qfalse; + } + + if(w == WP_K43) { + ws = WP_K43_SCOPE; + } + else if(w == WP_GARAND) { + ws = WP_GARAND_SCOPE; + } + else if(w == WP_FG42) { + ws = WP_FG42SCOPE; + } + + if(ws) { + AddWeaponToPlayer(client, + ws, + GetAmmoTableData(ws)->defaultStartingAmmo, + GetAmmoTableData(ws)->defaultStartingClip, + qfalse); + } + if(w2 == WP_AKIMBO_SILENCEDLUGER || w2 == WP_AKIMBO_SILENCEDCOLT) { + client->ps.ammoclip[ + BG_FindClipForWeapon(BG_AkimboSidearm(w2))] = + GetAmmoTableData(w2)->defaultStartingClip; + } + AddWeaponToPlayer(client, + w2, + GetAmmoTableData(w2)->defaultStartingAmmo, + GetAmmoTableData(w2)->defaultStartingClip, + qfalse); + if(w3) { + AddWeaponToPlayer(client, + w3, + GetAmmoTableData(w3)->defaultStartingAmmo, + GetAmmoTableData(w3)->defaultStartingClip, + qfalse); + } + client->pmext.silencedSideArm = 1; + return qtrue; +} + +/* +=========== +SetWolfSpawnWeapons +=========== +*/ +void SetWolfSpawnWeapons( gclient_t *client ) +{ + int pc; + qboolean isBot = qfalse; + qboolean isPOW = qfalse; + + pc = client->sess.playerType; + + if(g_entities[client->ps.clientNum].r.svFlags & SVF_BOT) + isBot = qtrue; + if(g_entities[client->ps.clientNum].r.svFlags & SVF_POW) + isPOW = qtrue; + + if ( client->sess.sessionTeam == TEAM_SPECTATOR ) + return; + + // Reset special weapon time + client->ps.classWeaponTime = -999999; + + // Communicate it to cgame + client->ps.stats[STAT_PLAYER_CLASS] = pc; + + // Abuse teamNum to store player class as well (can't see stats + // for all clients in cgame) + client->ps.teamNum = pc; + + // JPW NERVE -- zero out all ammo counts + memset(client->ps.ammo, 0, MAX_WEAPONS * sizeof(int)); + + // All players start with a knife (not OR-ing so that it clears + // previous weapons) + client->ps.weapons[0] = 0; + client->ps.weapons[1] = 0; + + // Gordon: set up pow status + if( isBot ) { + if( isPOW ) { + BotSetPOW( client->ps.clientNum, qtrue ); + return; + } else { + BotSetPOW( client->ps.clientNum, qfalse ); + } + } + + AddWeaponToPlayer( client, WP_KNIFE, 1, 0, qtrue ); + + client->ps.weaponstate = WEAPON_READY; + + + // the only weapon other than knife is syringe apperantly no need + // for mile long conditionals + if( g_knifeonly.integer && pc == PC_MEDIC) { + AddWeaponToPlayer(client, + WP_MEDIC_SYRINGE, + GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingAmmo, + GetAmmoTableData(WP_MEDIC_SYRINGE)->defaultStartingClip, + qfalse); + return; + } + + switch(pc) { + case PC_SOLDIER: + _SetSoldierSpawnWeapons(client); + break; + case PC_MEDIC: + _SetMedicSpawnWeapons(client); + break; + case PC_ENGINEER: + _SetEngineerSpawnWeapons(client); + break; + case PC_FIELDOPS: + _SetFieldOpSpawnWeapons(client); + break; + case PC_COVERTOPS: + _SetCovertSpawnWeapons(client); + break; + } + + G_AddClassSpecificTools(client); +} + +int G_CountTeamMedics( team_t team, qboolean alivecheck ) { + int numMedics = 0; + int i, j; + + for( i = 0; i < level.numConnectedClients; i++ ) { + j = level.sortedClients[i]; + + if( level.clients[j].sess.sessionTeam != team ) { + continue; + } + + if( level.clients[j].sess.playerType != PC_MEDIC ) { + continue; + } + + if( alivecheck ) { + if( g_entities[j].health <= 0 ) { + continue; + } + + if( level.clients[j].ps.pm_type == PM_DEAD || level.clients[j].ps.pm_flags & PMF_LIMBO ) { + continue; + } + } + + numMedics++; + } + + return numMedics; +} + +// +// AddMedicTeamBonus +// +void AddMedicTeamBonus( gclient_t *client ) { + int numMedics = G_CountTeamMedics( client->sess.sessionTeam, qfalse ); + + // compute health mod + client->pers.maxHealth = 100 + 10 * numMedics; + + if( client->pers.maxHealth > 125 ) { + client->pers.maxHealth = 125; + } + + if( client->sess.skill[SK_BATTLE_SENSE] >= 3 ) { + client->pers.maxHealth += 15; + } + + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; +} + +/* +=========== +ClientCheckName +============ +*/ +static void ClientCleanName( const char *in, char *out, int outSize ) +{ + int len, colorlessLen; + char ch; + char *p; + int spaces; + + //save room for trailing null byte + outSize--; + + len = 0; + colorlessLen = 0; + p = out; + *p = 0; + spaces = 0; + + while( 1 ) { + ch = *in++; + if( !ch ) { + break; + } + + // don't allow leading spaces + if( !*p && ch == ' ' ) { + continue; + } + + // check colors + if( ch == Q_COLOR_ESCAPE ) { + // solo trailing carat is not a color prefix + if( !*in ) { + break; + } + + // don't allow black in a name, period +/* if( ColorIndex(*in) == 0 ) { + in++; + continue; + } +*/ + // make sure room in dest for both chars + if( len > outSize - 2 ) { + break; + } + + *out++ = ch; + *out++ = *in++; + len += 2; + continue; + } + + // don't allow too many consecutive spaces + if( ch == ' ' ) { + spaces++; + if( spaces > 3 ) { + continue; + } + } + else { + spaces = 0; + } + + if( len > outSize - 1 ) { + break; + } + + *out++ = ch; + colorlessLen++; + len++; + } + *out = 0; + + // don't allow empty names + if( *p == 0 || colorlessLen == 0 ) { + Q_strncpyz( p, "UnnamedPlayer", outSize ); + } +} + +void G_StartPlayerAppropriateSound(gentity_t *ent, char *soundType) { +} + +/* +=========== +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. +============ +*/ +void ClientUserinfoChanged( int clientNum ) { + gentity_t *ent; + char *s; + char oldname[MAX_STRING_CHARS]; + char userinfo[MAX_INFO_STRING]; + gclient_t *client; + int i; + char skillStr[16] = ""; + char medalStr[16] = ""; + int characterIndex; + + + ent = g_entities + clientNum; + client = ent->client; + + client->ps.clientNum = clientNum; + + client->medals = 0; + for( i = 0; i < SK_NUM_SKILLS; i++ ) { + client->medals += client->sess.medals[ i ]; + } + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // check for malformed or illegal info strings + if ( !Info_Validate(userinfo) ) { + Q_strncpyz( userinfo, "\\name\\badinfo", sizeof(userinfo) ); + } + +#ifndef DEBUG_STATS + if( g_developer.integer || *g_log.string || g_dedicated.integer ) +#endif + { + G_Printf("Userinfo: %s\n", userinfo); + } + + // check for local client + s = Info_ValueForKey( userinfo, "ip" ); + if ( s && !strcmp( s, "localhost" ) ) { + client->pers.localClient = qtrue; + level.fLocalHost = qtrue; + client->sess.referee = RL_REFEREE; + } + + // OSP - extra client info settings + // FIXME: move other userinfo flag settings in here + if(ent->r.svFlags & SVF_BOT) { + client->pers.autoActivate = PICKUP_TOUCH; + client->pers.bAutoReloadAux = qtrue; + client->pmext.bAutoReload = qtrue; + client->pers.predictItemPickup = qfalse; + } else { + s = Info_ValueForKey(userinfo, "cg_uinfo"); + sscanf(s, "%i %i %i", + &client->pers.clientFlags, + &client->pers.clientTimeNudge, + &client->pers.clientMaxPackets); + + client->pers.autoActivate = (client->pers.clientFlags & CGF_AUTOACTIVATE) ? PICKUP_TOUCH : PICKUP_ACTIVATE; + client->pers.predictItemPickup = ((client->pers.clientFlags & CGF_PREDICTITEMS) != 0); + + if(client->pers.clientFlags & CGF_AUTORELOAD) { + client->pers.bAutoReloadAux = qtrue; + client->pmext.bAutoReload = qtrue; + } else { + client->pers.bAutoReloadAux = qfalse; + client->pmext.bAutoReload = qfalse; + } + } + + // look for cg_hitsounds + s = Info_ValueForKey(userinfo, "cg_hitsounds"); + if(*s) client->pers.hitsounds = atoi(s); + else client->pers.hitsounds = 1; + + // set name + Q_strncpyz( oldname, client->pers.netname, sizeof( oldname ) ); + s = Info_ValueForKey (userinfo, "name"); + ClientCleanName( s, client->pers.netname, sizeof(client->pers.netname) ); + + if ( client->pers.connected == CON_CONNECTED ) { + if ( strcmp( oldname, client->pers.netname ) ) { + trap_SendServerCommand( -1, va("print \"[lof]%s" S_COLOR_WHITE " [lon]renamed to[lof] %s\n\"", oldname, + client->pers.netname) ); + } + } + + for( i = 0; i < SK_NUM_SKILLS; i++ ) { + Q_strcat( skillStr, sizeof(skillStr), va("%i",client->sess.skill[i]) ); + Q_strcat( medalStr, sizeof(medalStr), va("%i",client->sess.medals[i]) ); + // FIXME: Gordon: wont this break if medals > 9 arnout? JK: Medal count is tied to skill count :() Gordon: er, it's based on >> skill per map, so for a huuuuuuge campaign it could break... + } + + client->ps.stats[STAT_MAX_HEALTH] = client->pers.maxHealth; + + // check for custom character + s = Info_ValueForKey( userinfo, "ch" ); + if( *s ) { + characterIndex = atoi(s); + } else { + characterIndex = -1; + } + + // To communicate it to cgame + client->ps.stats[ STAT_PLAYER_CLASS ] = client->sess.playerType; + // Gordon: Not needed any more as it's in clientinfo? + + // send over a subset of the userinfo keys so other clients can + // print scoreboards, display models, and play custom sounds + if ( ent->r.svFlags & SVF_BOT ) { + // n: netname + // t: sessionTeam + // c1: color + // hc: maxHealth + // skill: skill + // c: playerType (class?) + // r: rank + // f: fireteam + // bot: botSlotNumber + // nwp: noWeapon + // m: medals + // ch: character + + s = va( "n\\%s\\t\\%i\\skill\\%s\\c\\%i\\r\\%i\\m\\%s\\s\\%s%s\\dn\\%s\\dr\\%i\\w\\%i\\lw\\%i\\sw\\%i\\mu\\%i", + client->pers.netname, + client->sess.sessionTeam, + Info_ValueForKey( userinfo, "skill" ), + client->sess.playerType, + client->sess.rank, + medalStr, + skillStr, + characterIndex >= 0 ? va( "\\ch\\%i", characterIndex ) : "", + client->disguiseNetname, + client->disguiseRank, + client->sess.playerWeapon, + client->sess.latchPlayerWeapon, + client->sess.latchPlayerWeapon2, + client->sess.muted ? 1 : 0 + ); + } else { + s = va( "n\\%s\\t\\%i\\c\\%i\\r\\%i\\m\\%s\\s\\%s\\dn\\%s\\dr\\%i\\w\\%i\\lw\\%i\\sw\\%i\\mu\\%i\\ref\\%i", + client->pers.netname, + client->sess.sessionTeam, + client->sess.playerType, + client->sess.rank, + medalStr, + skillStr, + client->disguiseNetname, + client->disguiseRank, + client->sess.playerWeapon, + client->sess.latchPlayerWeapon, + client->sess.latchPlayerWeapon2, + client->sess.muted ? 1 : 0, + client->sess.referee + ); + } + + trap_GetConfigstring( CS_PLAYERS + clientNum, oldname, sizeof( oldname ) ); + + trap_SetConfigstring( CS_PLAYERS + clientNum, s ); + + if( !Q_stricmp( oldname, s ) ) { + return; + } + + if (g_logOptions.integer & LOGOPTS_GUID) // Log the GUIDs? + { + char *guid = Info_ValueForKey ( userinfo, "cl_guid" ); + G_LogPrintf( "ClientUserinfoChangedGUID: %i %s %s\n", clientNum, guid, s ); + G_DPrintf( "ClientUserinfoChangedGUID: %i :: %s %s\n", clientNum, guid, s ); + } else { + G_LogPrintf( "ClientUserinfoChanged: %i %s\n", clientNum, s ); + G_DPrintf( "ClientUserinfoChanged: %i :: %s\n", clientNum, s ); + } +} + + +/* +=========== +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, qboolean isBot ) { + char *value; + gclient_t *client; + char userinfo[MAX_INFO_STRING]; + char userinfo2[MAX_INFO_STRING]; + gentity_t *ent; + int i; + int clientNum2; + char guid[33]; + + + ent = &g_entities[ clientNum ]; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + + // IP filtering + // https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=500 + // recommanding PB based IP / GUID banning, the builtin system is pretty limited + // check to see if they are on the banned IP list + value = Info_ValueForKey (userinfo, "ip"); + if ( G_FilterIPBanPacket( value ) ) { + return "You are banned from this server."; + } + + // check for shrubbot ban + if(G_shrubbot_ban_check(userinfo)) + return "You are banned from this server."; + + + // Xian - check for max lives enforcement ban + if( g_gametype.integer != GT_WOLF_LMS ) { + if( g_enforcemaxlives.integer && (g_maxlives.integer > 0 || g_axismaxlives.integer > 0 || g_alliedmaxlives.integer > 0) ) { + if( trap_Cvar_VariableIntegerValue( "sv_punkbuster" ) ) { + value = Info_ValueForKey ( userinfo, "cl_guid" ); + if ( G_FilterMaxLivesPacket ( value ) ) { + return "Max Lives Enforcement Temp Ban. You will be able to reconnect when the next round starts. This ban is enforced to ensure you don't reconnect to get additional lives."; + } + } else { + value = Info_ValueForKey ( userinfo, "ip" ); // this isn't really needed, oh well. + if ( G_FilterMaxLivesIPPacket ( value ) ) { + return "Max Lives Enforcement Temp Ban. You will be able to reconnect when the next round starts. This ban is enforced to ensure you don't reconnect to get additional lives."; + } + } + } + } + // End Xian + + // we don't check password for bots and local client + // NOTE: local client <-> "ip" "localhost" + // this means this client is not running in our current process + if ( !isBot && !( ent->r.svFlags & SVF_BOT ) && (strcmp(Info_ValueForKey ( userinfo, "ip" ), "localhost") != 0)) { + // 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) { + if( !sv_privatepassword.string[ 0 ] || strcmp( sv_privatepassword.string, value ) ) { + return "Invalid password"; + } + } + } + + // Gordon: porting q3f flag bug fix + // If a player reconnects quickly after a disconnect, the client disconnect may never be called, thus flag can get lost in the ether + if( ent->inuse ) { + G_LogPrintf( "Forcing disconnect on active client: %i\n", ent-g_entities ); + // so lets just fix up anything that should happen on a disconnect + ClientDisconnect( ent-g_entities ); + } + + // tjw: if the client has crashed, force disconnect of previous + // session so that the XP can be reclaimed + if((g_XPSave.integer & XPSF_ENABLE) && firstTime) { + value = Info_ValueForKey (userinfo, "cl_guid"); + Q_strncpyz(guid, value, sizeof(guid)); + for (i = 0; i < level.numConnectedClients; i++) { + clientNum2 = level.sortedClients[i]; + if(clientNum == clientNum2) continue; + trap_GetUserinfo(clientNum2, + userinfo2, + sizeof(userinfo2)); + value = Info_ValueForKey (userinfo2, "cl_guid"); + if(!Q_stricmp(guid, value)) { + G_LogPrintf( "Forcing disconnect of " + "duplicate guid %s for client %i\n", + guid, + clientNum2); + ClientDisconnect(clientNum2); + } + } + } + + // they can connect + ent->client = level.clients + clientNum; + client = ent->client; + + + + memset( client, 0, sizeof(*client) ); + + client->pers.connected = CON_CONNECTING; + client->pers.connectTime = level.time; // DHM - Nerve + + // read or initialize the session data + if( firstTime ) { + client->pers.lastTeamChangeTime = 0; + client->pers.initialSpawn = qtrue; // DHM - Nerve + G_InitSessionData( client, userinfo ); + client->pers.enterTime = level.time; + client->ps.persistant[PERS_SCORE] = 0; + } else { + G_ReadSessionData( client ); + } + + + if(g_XPSave.integer & XPSF_ENABLE) { + + char *guid; + char userinfo[MAX_INFO_STRING]; + XPStorage_t* storage; + int i; + + client->XPSave_lives = -999; + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + guid = Info_ValueForKey ( userinfo, "cl_guid" ); + storage = G_FindXPBackup(guid); + if(storage) { + // make the client aware that they have no lives before + // ClientBegin to avoid confusion + if(storage->lives != -999 && + storage->lives < 0) { + + client->ps.persistant[PERS_RESPAWNS_LEFT] = storage->lives; + + } + client->XPSave_lives = storage->lives; + for( i = 0; i < SK_NUM_SKILLS; i++ ) { + client->sess.skillpoints[i] = storage->skills[i]; + } + G_CalcRank( client ); + ent->client->ps.stats[STAT_XP] = 0; + for( i = 0; i < SK_NUM_SKILLS; i++ ) { + ent->client->ps.stats[STAT_XP] += ent->client->sess.skillpoints[i]; + } + BG_PlayerStateToEntityState( &ent->client->ps, &ent->s, qtrue ); + } + } + + if( g_gametype.integer == GT_WOLF_CAMPAIGN ) { + if( g_campaigns[level.currentCampaign].current == 0 || level.newCampaign ) { + client->pers.enterTime = level.time; + } + } else { + client->pers.enterTime = level.