diff -urN et.orig/src/game/bg_local.h et/src/game/bg_local.h --- et.orig/src/game/bg_local.h 2003-04-14 17:32:26.000000000 -0500 +++ et/src/game/bg_local.h 2005-02-03 16:50:53.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 et.orig/src/game/bg_misc.c et/src/game/bg_misc.c --- et.orig/src/game/bg_misc.c 2003-08-28 15:43:22.000000000 -0500 +++ et/src/game/bg_misc.c 2005-02-01 13:37:07.000000000 -0600 @@ -4027,9 +4027,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 +4130,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 et.orig/src/game/bg_pmove.c et/src/game/bg_pmove.c --- et.orig/src/game/bg_pmove.c 2003-08-28 15:43:22.000000000 -0500 +++ et/src/game/bg_pmove.c 2005-02-10 09:34:41.000000000 -0600 @@ -15,8 +15,12 @@ #ifdef CGAMEDLL #define PM_GameType cg_gameType.integer +#define PM_UNDERWATER_SYRINGE 0 // tjw: no client mod +#define PM_CORPSE_SPIN 0 // tjw: no client mod #elif GAMEDLL #define PM_GameType g_gametype.integer +#define PM_UNDERWATER_SYRINGE (g_weapons.integer & WPF_UNDERWATER_SYRINGE) +#define PM_CORPSE_SPIN g_spinCorpse.integer #endif #define PM_IsSinglePlayerGame() (PM_GameType == GT_SINGLE_PLAYER || PM_GameType == GT_COOP) @@ -727,6 +731,137 @@ 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] = 1; + 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->maxs[2] = pm->ps->maxs[2]; + + 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 +907,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]; @@ -822,7 +959,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 @@ -1423,6 +1564,7 @@ VectorNormalize (pm->ps->velocity); VectorScale (pm->ps->velocity, forward, pm->ps->velocity); } + } @@ -1955,14 +2097,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 +3066,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 +3277,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 +3298,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 +3361,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 ) { @@ -3418,9 +3569,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 +3846,15 @@ 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_UNDERWATER_SYRINGE) + ) { + + // 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 +4294,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 +4647,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 +4674,7 @@ } ps->viewangles[i] = SHORT2ANGLE(temp); } - + if( BG_PlayerMounted(ps->eFlags) ) { float yaw, oldYaw; float degsSec = MG42_YAWSPEED; @@ -5090,7 +5270,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 +5329,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 +5500,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 +5669,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 +5686,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 et.orig/src/game/bg_public.h et/src/game/bg_public.h --- et.orig/src/game/bg_public.h 2003-06-23 14:13:46.000000000 -0500 +++ et/src/game/bg_public.h 2005-02-01 12:56:54.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,12 @@ // 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 + // 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 et.orig/src/game/g_active.c et/src/game/g_active.c --- et.orig/src/game/g_active.c 2003-08-26 16:20:40.000000000 -0500 +++ et/src/game/g_active.c 2005-02-06 13:41:49.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; } @@ -563,7 +564,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 +602,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 +622,10 @@ } } } + + // notify client that they are playing dead + if(client->ps.eFlags & EF_PLAYDEAD && ent->health > 0) + CP("cp \"Playing Dead\" 1"); } /* @@ -655,9 +663,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 +685,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 +696,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 +996,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 +1027,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 +1273,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 +1396,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 +1417,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 +1753,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 +1781,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 et.orig/src/game/g_client.c et/src/game/g_client.c --- et.orig/src/game/g_client.c 2003-08-28 15:43:22.000000000 -0500 +++ et/src/game/g_client.c 2005-02-12 13:02:35.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; @@ -648,7 +651,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); // DHM - Nerve :: Add back if we decide to have a spawn effect // add a teleportation effect @@ -788,6 +791,88 @@ 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 ); + } + } + +} + /* =========== SetWolfSpawnWeapons @@ -832,146 +917,125 @@ 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; + } + // 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_GRENADE_LAUNCHER, 0, 4, qfalse ); - if (client->sess.sessionTeam == TEAM_AXIS) { + } 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_GRENADE_PINEAPPLE, 0, 4, qfalse ); + } + } + // Field ops gets binoculars, ammo pack, artillery, and a grenade + else if ( pc == PC_FIELDOPS ) { + 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 ); + } + } + // Medic + else if( pc == PC_MEDIC ) { + 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 ); + } + } + // Soldier + else if ( pc == PC_SOLDIER ) { + switch( client->sess.sessionTeam ) { + case 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 ); + 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; - default: - AddWeaponToPlayer( client, WP_MP40, GetAmmoTableData(WP_MP40)->defaultStartingAmmo, GetAmmoTableData(WP_MP40)->defaultStartingClip, qtrue ); + 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; } - AddWeaponToPlayer( client, WP_LANDMINE, GetAmmoTableData(WP_LANDMINE)->defaultStartingAmmo, GetAmmoTableData(WP_LANDMINE)->defaultStartingClip, qfalse ); - AddWeaponToPlayer( client, WP_GRENADE_LAUNCHER, 0, 4, qfalse ); - - } else { + AddWeaponToPlayer( client, WP_GRENADE_LAUNCHER, 0, 4, qfalse ); + break; + case TEAM_ALLIES: 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 ); + case WP_THOMPSON: + AddWeaponToPlayer( client, WP_THOMPSON, 2*(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 ); - } - } - } - - 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; + 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 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; + 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; - } - } else if( pc == PC_COVERTOPS ) { - switch( client->sess.playerWeapon ) { + } + AddWeaponToPlayer( client, WP_GRENADE_PINEAPPLE, 0, 4, qfalse ); + break; + } + } + // Covert + else if( pc == PC_COVERTOPS ) { + switch( client->sess.playerWeapon ) { case WP_K43: case WP_GARAND: if( client->sess.sessionTeam == TEAM_AXIS ) { @@ -979,7 +1043,8 @@ AddWeaponToPlayer( client, WP_K43_SCOPE, GetAmmoTableData(WP_K43_SCOPE)->defaultStartingAmmo, GetAmmoTableData(WP_K43_SCOPE)->defaultStartingClip, qfalse ); } break; - } else { + } + 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 ); } @@ -993,118 +1058,86 @@ 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 - } + } + if( client->sess.sessionTeam == TEAM_AXIS ) { + AddWeaponToPlayer( client, WP_GRENADE_LAUNCHER, 0, 2, qfalse ); + } else { + AddWeaponToPlayer( client, WP_GRENADE_PINEAPPLE, 0, 2, qfalse ); } - 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; + 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; - 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_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; - 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_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; - 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; - } - } + 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; - 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 ); + 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; } - } - } 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 } + G_AddClassSpecificTools(client); + } int G_CountTeamMedics( team_t team, qboolean alivecheck ) { @@ -1325,6 +1358,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"); @@ -1445,10 +1483,7 @@ gclient_t *client; char userinfo[MAX_INFO_STRING]; gentity_t *ent; -#ifdef USEXPSTORAGE - ipXPStorage_t* xpBackup; - int i; -#endif // USEXPSTORAGE + ent = &g_entities[ clientNum ]; @@ -1463,6 +1498,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) ) { @@ -1513,11 +1553,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,16 +1564,6 @@ 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 ]; - } - G_CalcRank( client ); - } -#endif // USEXPSTORAGE - if( g_gametype.integer == GT_WOLF_CAMPAIGN ) { if( g_campaigns[level.currentCampaign].current == 0 || level.newCampaign ) { client->pers.enterTime = level.time; @@ -1625,8 +1654,14 @@ // 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: 0 lives if the timelimit has expired. + if((level.time - level.startTime) >= (g_timelimit.integer * 60000)) return -2; + + 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; return(val); @@ -1647,11 +1682,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 ); } @@ -1682,8 +1725,17 @@ 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 +1773,62 @@ } } } - } + } + + if(g_XPSave.integer && (client->maxlivescalced || !maxlives)) { + char *guid; + char userinfo[MAX_INFO_STRING]; + XPStorage_t* storage; + int i; + + trap_GetUserinfo( clientNum, userinfo, sizeof( userinfo ) ); + guid = Info_ValueForKey ( userinfo, "cl_guid" ); + storage = G_FindXPBackup(guid); + if(storage) { + if(storage->lives != -999 && + (storage->lives - 2) < client->ps.persistant[PERS_RESPAWNS_LEFT]) { + client->ps.persistant[PERS_RESPAWNS_LEFT] = storage->lives - 2; + } + for( i = 0; i < SK_NUM_SKILLS; i++ ) { + client->sess.skillpoints[i] = storage->skills[i]; + } + G_CalcRank( client ); + } + } + // tjw: stop unlimited lives bug + if(maxlives && client->ps.persistant[PERS_RESPAWNS_LEFT] < -1) + client->sess.sessionTeam = TEAM_SPECTATOR; - // 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; + // locate ent at a spawn point + if(!g_teamChangeKills.integer && health > 0) { + + ClientSpawn(ent, qfalse, qtrue); + } + else { + ClientSpawn(ent, qfalse, qfalse); + } - 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 +1932,7 @@ Initializes all non-persistant parts of playerState ============ */ -void ClientSpawn( gentity_t *ent, qboolean revived ) +void ClientSpawn( gentity_t *ent, qboolean revived, qboolean teamChange ) { int index; vec3_t spawn_origin, spawn_angles; @@ -1964,6 +2051,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; @@ -2055,7 +2143,7 @@ client->sess.playerWeapon2 = client->sess.latchPlayerWeapon2; - if( update ) { + if( update || teamChange ) { ClientUserinfoChanged( index ); } } @@ -2065,8 +2153,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 +2179,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(!teamChange) { + 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 +2304,8 @@ return; } -#ifdef USEXPSTORAGE - G_AddXPBackup( ent ); -#endif // USEXPSTORAGE + + if(g_XPSave.integer) G_AddXPBackup( ent ); G_RemoveClientFromFireteams( clientNum, qtrue, qfalse ); G_RemoveFromAllIgnoreLists( clientNum ); diff -urN et.orig/src/game/g_cmds.c et/src/game/g_cmds.c --- et.orig/src/game/g_cmds.c 2003-08-28 15:43:22.000000000 -0500 +++ et/src/game/g_cmds.c 2005-02-12 13:22:36.000000000 -0600 @@ -5,6 +5,82 @@ qboolean G_IsOnFireteam(int entityNum, fireteamData_t** teamNum); /* + * G_PlayDead + */ +void G_PlayDead(gentity_t *ent) +{ + + if(!ent->client) return; + if(!g_playDead.integer) return; + if(ent->health < 0) return; + if(ent->client->ps.eFlags & EF_PLAYDEAD) { + + ent->client->ps.stats[STAT_HEALTH] = ent->health; + ent->client->ps.pm_type = PM_PLAYDEAD; + CP("cp \"SURPRISE!\" 1"); + /* + BG_AnimScriptEvent( + &ent->client->ps, + ent->client->pers.character->animModelInfo, + ANIM_ET_JUMP, + qfalse, + qtrue); + */ + } + else { + ent->client->ps.pm_type = PM_PLAYDEAD; + /* + BG_AnimScriptEvent( + &ent->client->ps, + ent->client->pers.character->animModelInfo, + ANIM_ET_DEATH, + qfalse, + qtrue); + */ + } +} + +void G_PrivateMessage(gentity_t *ent) +{ + int pids[MAX_CLIENTS]; + char name[MAX_NAME_LENGTH]; + char *msg; + int pcount; + int i; + gentity_t *tmpent; + qboolean sent = qfalse; + + if(!g_privateMessages.integer) return; + if(trap_Argc() < 3) { + CP("print \"usage: /m [name|slot#] [message]\n\""); + return; + } + trap_Argv(1, name, sizeof(name)); + msg = ConcatArgs(2); + pcount = ClientNumbersFromString(name, pids); + for(i=0; i %s^7: (%d recipients^7): ^3%s^7\"", + ent->client->pers.netname, + tmpent->client->pers.netname, + pcount, + msg)); + CPx(pids[i], va("cp \"^3private message from ^7%s^7\"", + ent->client->pers.netname)); + CP(va("print \"private message sent to %s: ^3%s^7\n\"", + tmpent->client->pers.netname, + msg + )); + } + if(!sent) { + CP("print \"player not found\n\""); + } +} + +/* ================== G_SendScore @@ -171,6 +247,29 @@ /* ================== +DecolorString + +Remove color characters +================== +*/ +void DecolorString( char *in, char *out) +{ + while(*in) { + if(*in == 27 || *in == '^') { + in++; // skip color code + if(*in) in++; + continue; + } + *out++ = *in++; + } + *out = 0; +} + +/* +================== + +/* +================== SanitizeString Remove case and control characters @@ -198,6 +297,62 @@ /* ================== +ClientNumbersFromString + +Sets plist to an array of integers that represent client numbers that have +names that are a partial match for s. List is terminated by a -1. + +Returns number of matching clientids. +================== +*/ +int ClientNumbersFromString( char *s, int *plist) { + gclient_t *p; + int i, found = 0; + char s2[MAX_STRING_CHARS]; + char n2[MAX_STRING_CHARS]; + char *m; + qboolean is_slot = qtrue; + + *plist = -1; + + // if a number is provided, it might be a slot # + for(i=0; i '9') { + is_slot = qfalse; + break; + } + } + if(is_slot) { + i = atoi(s); + if(i >= 0 && i < level.maxclients) { + p = &level.clients[i]; + if(p->pers.connected == CON_CONNECTED) { + *plist++ = i; + *plist = -1; + return 1; + } + } + } + + // now look for name matches + SanitizeString(s, s2, qtrue); + if(strlen(s2) < 1) return 0; + for(i=0; i < level.maxclients; i++) { + p = &level.clients[i]; + if(p->pers.connected != CON_CONNECTED) continue; + SanitizeString(p->pers.netname, n2, qtrue); + m = strstr(n2, s2); + if(m != NULL) { + *plist++ = i; + found++; + } + } + *plist = -1; + return found; +} + +/* +================== ClientNumberFromString Returns a player number for either a number or name string @@ -672,6 +827,38 @@ } } + +void G_DropItems(gentity_t *self) +{ + gitem_t *item= NULL; + + // drop flag regardless + if (self->client->ps.powerups[PW_REDFLAG]) { + item = BG_FindItem("Red Flag"); + if (!item) + item = BG_FindItem("Objective"); + + self->client->ps.powerups[PW_REDFLAG] = 0; + } + if (self->client->ps.powerups[PW_BLUEFLAG]) { + item = BG_FindItem("Blue Flag"); + if (!item) + item = BG_FindItem("Objective"); + + self->client->ps.powerups[PW_BLUEFLAG] = 0; + } + + if (item) { + vec3_t launchvel = { 0, 0, 0 }; + gentity_t *flag = LaunchItem(item, self->r.currentOrigin, launchvel, self->s.number); + + flag->s.modelindex2 = self->s.otherEntityNum2;// JPW NERVE FIXME set player->otherentitynum2 with old modelindex2 from flag and restore here + flag->message = self->message; // DHM - Nerve :: also restore item name + // Clear out player's temp copies + self->s.otherEntityNum2 = 0; + self->message = NULL; + } +} /* ================= SetTeam @@ -685,6 +872,11 @@ int specClient; int respawnsLeft; + // if the team changing player is a shrubbot admin with the + // '5' flag, they can switch teams regardless of balance + if(G_shrubbot_permission(ent, SBF_FORCETEAMCHANGE)) + force = qtrue; + // // see what change is requested // @@ -788,14 +980,22 @@ client->pers.teamState.state = TEAM_BEGIN; } - if ( oldTeam != TEAM_SPECTATOR ) { - if ( !(ent->client->ps.pm_flags & PMF_LIMBO) ) { - // Kill him (makes sure he loses flags, etc) - ent->flags &= ~FL_GODMODE; + + if(oldTeam != TEAM_SPECTATOR && + !(ent->client->ps.pm_flags & PMF_LIMBO) ) { + + ent->flags &= ~FL_GODMODE; + // tjw: if they're not dead, don't kill + if(g_teamChangeKills.integer || ent->health <= 0) { ent->client->ps.stats[STAT_HEALTH] = ent->health = 0; player_die (ent, ent, ent, 100000, MOD_SWITCHTEAM); } + else { + ent->client->ps.stats[STAT_HEALTH] = ent->health; + G_DropItems(ent); + } } + // they go to the end of the line for tournements if ( team == TEAM_SPECTATOR ) { client->sess.spectatorTime = level.time; @@ -866,10 +1066,12 @@ G_verifyMatchState(oldTeam); BotRecordTeamChange( clientNum ); + /* // Reset stats when changing teams if(team != oldTeam) { G_deleteStats(clientNum); } + */ G_UpdateSpawnCounts(); @@ -910,7 +1112,7 @@ } } } - + ent->client->pers.lastTeamChangeTime = level.time; return qtrue; } @@ -1036,17 +1238,51 @@ return qtrue; } - if( !G_IsHeavyWeapon( weapon ) ) { - return qfalse; - } - count = G_TeamCount( ent, -1 ); wcount = G_TeamCount( ent, weapon ); - if( wcount >= ceil( count * g_heavyWeaponRestriction.integer * 0.01f ) ) { - return qtrue; + if(G_IsHeavyWeapon(weapon)) { + if( wcount >= ceil( count * g_heavyWeaponRestriction.integer * 0.01f ) ) { + return qtrue; + } + } + + switch(weapon) { + case WP_PANZERFAUST: + if(team_maxPanzers.integer == -1) return qfalse; + if(wcount >= team_maxPanzers.integer) { + //CP("cp \"^1PANZERFAUST not available^7\" 1"); + return qtrue; + } + case WP_MOBILE_MG42: + if(team_maxMG42s.integer == -1) return qfalse; + if(wcount >= team_maxMG42s.integer) { + //CP("cp \"^1MG42 not available^7\" 1"); + return qtrue; + } + case WP_FLAMETHROWER: + if(team_maxFlamers.integer == -1) return qfalse; + if(wcount >= team_maxFlamers.integer) { + //CP("cp \"^1FLAMETHROWER not available^7\" 1"); + return qtrue; + } + case WP_MORTAR: + if(team_maxMortars.integer == -1) return qfalse; + if(wcount >= team_maxMortars.integer) { + //CP("cp \"^1MORTAR not available^7\" 1"); + return qtrue; + } + case WP_GPG40: + case WP_CARBINE: + if(team_maxGrenLaunchers.integer == -1) return qfalse; + if(wcount >= team_maxGrenLaunchers.integer) { + //CP("cp \"^1GRENADE LAUNCHER not available^7\" 1"); + return qtrue; + } } + + return qfalse; } @@ -1064,6 +1300,8 @@ changed = qtrue; } } else { + if(updateclient) + CP("cp \"^1Weapon is not available\" 1"); if( ent->client->sess.latchPlayerWeapon != 0 ) { ent->client->sess.latchPlayerWeapon = 0; changed = qtrue; @@ -1489,6 +1727,7 @@ G_SayTo( ent, other, mode, color, name, text, localize ); } } + G_shrubbot_cmd_check(ent); } @@ -2414,55 +2653,272 @@ // Rafael /* ================== -Cmd_Activate_f +G_UniformSteal formerly known as Cmd_Activate2_f ================== */ -qboolean Do_Activate2_f(gentity_t *ent, gentity_t *traceEnt) { +qboolean G_UniformSteal(gentity_t *ent, gentity_t *traceEnt) +{ qboolean found = qfalse; + if(traceEnt->activator) return qfalse; // already used corpse + if(ent->client->sess.playerType != PC_COVERTOPS) + return qfalse; + if(ent->client->ps.powerups[PW_OPS_DISGUISED]) + return qfalse; + if(ent->client->ps.powerups[PW_BLUEFLAG] || + ent->client->ps.powerups[PW_REDFLAG]) + return qfalse; + if(BODY_TEAM(traceEnt) == ent->client->sess.sessionTeam) + return qfalse; + if( BODY_VALUE(traceEnt) < 250 ) { + BODY_VALUE(traceEnt) += 5; + return qfalse; + } - if( ent->client->sess.playerType == PC_COVERTOPS && !ent->client->ps.powerups[PW_OPS_DISGUISED] ) { - if( !ent->client->ps.powerups[PW_BLUEFLAG] && !ent->client->ps.powerups[PW_REDFLAG] ) { - if( traceEnt->s.eType == ET_CORPSE ) { - if( BODY_TEAM(traceEnt) < 4 && BODY_TEAM(traceEnt) != ent->client->sess.sessionTeam ) { - found = qtrue; + traceEnt->nextthink = traceEnt->timestamp + BODY_TIME(BODY_TEAM(traceEnt)); - if( BODY_VALUE(traceEnt) >= 250 ) { + //BG_AnimScriptEvent( &ent->client->ps, ent->client->pers.character->animModelInfo, ANIM_ET_PICKUPGRENADE, qfalse, qtrue ); + //ent->client->ps.pm_flags |= PMF_TIME_LOCKPLAYER; + //ent->client->ps.pm_time = 2100; - traceEnt->nextthink = traceEnt->timestamp + BODY_TIME(BODY_TEAM(traceEnt)); - -// BG_AnimScriptEvent( &ent->client->ps, ent->client->pers.character->animModelInfo, ANIM_ET_PICKUPGRENADE, qfalse, qtrue ); -// ent->client->ps.pm_flags |= PMF_TIME_LOCKPLAYER; -// ent->client->ps.pm_time = 2100; + ent->client->ps.powerups[PW_OPS_DISGUISED] = 1; + ent->client->ps.powerups[PW_OPS_CLASS_1] = BODY_CLASS(traceEnt) & 1; + ent->client->ps.powerups[PW_OPS_CLASS_2] = BODY_CLASS(traceEnt) & 2; + ent->client->ps.powerups[PW_OPS_CLASS_3] = BODY_CLASS(traceEnt) & 4; - ent->client->ps.powerups[PW_OPS_DISGUISED] = 1; - ent->client->ps.powerups[PW_OPS_CLASS_1] = BODY_CLASS(traceEnt) & 1; - ent->client->ps.powerups[PW_OPS_CLASS_2] = BODY_CLASS(traceEnt) & 2; - ent->client->ps.powerups[PW_OPS_CLASS_3] = BODY_CLASS(traceEnt) & 4; + BODY_TEAM(traceEnt) += 4; + traceEnt->activator = ent; - BODY_TEAM(traceEnt) += 4; - traceEnt->activator = ent; + traceEnt->s.time2 = 1; - traceEnt->s.time2 = 1; + // sound effect + G_AddEvent( ent, EV_DISGUISE_SOUND, 0 ); - // sound effect - G_AddEvent( ent, EV_DISGUISE_SOUND, 0 ); + G_AddSkillPoints( ent, SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS, 5.f ); + G_DebugAddSkillPoints( ent, SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS, 5, "stealing uniform" ); - G_AddSkillPoints( ent, SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS, 5.f ); - G_DebugAddSkillPoints( ent, SK_MILITARY_INTELLIGENCE_AND_SCOPED_WEAPONS, 5, "stealing uniform" ); + Q_strncpyz(ent->client->disguiseNetname, + g_entities[traceEnt->s.clientNum].client->pers.netname, + sizeof(ent->client->disguiseNetname)); + ent->client->disguiseRank = g_entities[traceEnt->s.clientNum].client ? + g_entities[traceEnt->s.clientNum].client->sess.rank : 0; - Q_strncpyz( ent->client->disguiseNetname, g_entities[traceEnt->s.clientNum].client->pers.netname, sizeof(ent->client->disguiseNetname) ); - ent->client->disguiseRank = g_entities[traceEnt->s.clientNum].client ? g_entities[traceEnt->s.clientNum].client->sess.rank : 0; + ClientUserinfoChanged( ent->s.clientNum ); - ClientUserinfoChanged( ent->s.clientNum ); - } else { - BODY_VALUE( traceEnt ) += 5; - } - } - } + + return qtrue; +} + +/* + G_ClassStealFixWeapons() //tjw + a helper for handling weapons for G_ClassSteal + */ +void G_ClassStealFixWeapons(gclient_t *client) +{ + // tools + COM_BitClear(client->ps.weapons, WP_DYNAMITE); + COM_BitClear(client->ps.weapons, WP_PLIERS); + COM_BitClear(client->ps.weapons, WP_LANDMINE); + COM_BitClear(client->ps.weapons, WP_SMOKE_BOMB); + COM_BitClear(client->ps.weapons, WP_SATCHEL); + COM_BitClear(client->ps.weapons, WP_SATCHEL_DET); + COM_BitClear(client->ps.weapons, WP_SMOKE_MARKER); + COM_BitClear(client->ps.weapons, WP_AMMO); + COM_BitClear(client->ps.weapons, WP_MEDKIT); + COM_BitClear(client->ps.weapons, WP_MEDIC_SYRINGE); + COM_BitClear(client->ps.weapons, WP_MEDIC_ADRENALINE); + + // primary weapons + COM_BitClear(client->ps.weapons, WP_PANZERFAUST); + COM_BitClear(client->ps.weapons, WP_FLAMETHROWER); + COM_BitClear(client->ps.weapons, WP_MOBILE_MG42); + COM_BitClear(client->ps.weapons, WP_MOBILE_MG42_SET); + COM_BitClear(client->ps.weapons, WP_MORTAR); + COM_BitClear(client->ps.weapons, WP_MORTAR_SET); + COM_BitClear(client->ps.weapons, WP_FG42); + COM_BitClear(client->ps.weapons, WP_FG42SCOPE); + COM_BitClear(client->ps.weapons, WP_K43); + COM_BitClear(client->ps.weapons, WP_GARAND); + COM_BitClear(client->ps.weapons, WP_K43_SCOPE); + COM_BitClear(client->ps.weapons, WP_GARAND_SCOPE); + COM_BitClear(client->ps.weapons, WP_STEN); + COM_BitClear(client->ps.weapons, WP_MP40); + COM_BitClear(client->ps.weapons, WP_THOMPSON); + COM_BitClear(client->ps.weapons, WP_KAR98); + COM_BitClear(client->ps.weapons, WP_GPG40); + COM_BitClear(client->ps.weapons, WP_CARBINE); + COM_BitClear(client->ps.weapons, WP_M7); + + + // silence pistols are different than regular pistols so swap them + if(client->sess.playerType == PC_COVERTOPS) { + client->pmext.silencedSideArm = 1; + COM_BitSet(client->ps.weapons, WP_SILENCER); + COM_BitSet(client->ps.weapons, WP_SILENCED_COLT); + if(COM_BitCheck(client->ps.weapons, WP_AKIMBO_LUGER)) { + COM_BitClear(client->ps.weapons, WP_AKIMBO_LUGER); + COM_BitSet(client->ps.weapons, WP_AKIMBO_SILENCEDLUGER); + } + if(COM_BitCheck(client->ps.weapons, WP_AKIMBO_COLT)) { + COM_BitClear(client->ps.weapons, WP_AKIMBO_COLT); + COM_BitSet(client->ps.weapons, WP_AKIMBO_SILENCEDCOLT); + } + } + else { + client->pmext.silencedSideArm = 0; + COM_BitClear(client->ps.weapons, WP_SILENCER); + COM_BitClear(client->ps.weapons, WP_SILENCED_COLT); + if(COM_BitCheck(client->ps.weapons, WP_AKIMBO_SILENCEDLUGER)) { + COM_BitClear(client->ps.weapons, WP_AKIMBO_SILENCEDLUGER); + COM_BitSet(client->ps.weapons, WP_AKIMBO_LUGER); + } + if(COM_BitCheck(client->ps.weapons, WP_AKIMBO_SILENCEDCOLT)) { + COM_BitClear(client->ps.weapons, WP_AKIMBO_SILENCEDCOLT); + COM_BitSet(client->ps.weapons, WP_AKIMBO_COLT); } } - return found; + // give them all the tool-like weapons for the class + G_AddClassSpecificTools(client); +} + + +/* + G_ClassSteal //tjw + */ + +qboolean G_ClassSteal(gentity_t *stealer, gentity_t *deadguy) +{ + if(!g_classChange.integer) return qfalse; + if(deadguy->activator) return qfalse; // already used corpse + if( BODY_VALUE(deadguy) < 250 ) { + if(BODY_VALUE(deadguy) == 0) { + CPx(stealer->s.number, "cp \"Switching Classes\" 1"); + } + BODY_VALUE(deadguy) += 5; + return qtrue; + } + + deadguy->nextthink = deadguy->timestamp + BODY_TIME(BODY_TEAM(deadguy)); + + deadguy->activator = stealer; + deadguy->s.time2 = 1; + + // sound effect + G_AddEvent(stealer, EV_DISGUISE_SOUND, 0); + + // I guess this disables future use of the corpse + BODY_TEAM(deadguy) += 4; + + stealer->client->sess.playerType = BODY_CLASS(deadguy); + + + G_ClassStealFixWeapons(stealer->client); + + // team_max[weapon] and g_heavyWeaponRestriction checks + if(!G_IsWeaponDisabled(stealer, BODY_WEAPON(deadguy))) { + COM_BitSet(stealer->client->ps.weapons, BODY_WEAPON(deadguy)); + stealer->client->ps.weapon = BODY_WEAPON(deadguy); + } + else if(BODY_TEAM(deadguy) == TEAM_AXIS) { + COM_BitSet(stealer->client->ps.weapons, WP_MP40); + stealer->client->ps.weapon = WP_MP40; + } + else { + COM_BitSet(stealer->client->ps.weapons, WP_THOMPSON); + stealer->client->ps.weapon = WP_THOMPSON; + } + + // if they took a gren launching weapon, they can have the + // grenade launcher too I guess + if(COM_BitCheck(stealer->client->ps.weapons, WP_GARAND)) { + COM_BitSet(stealer->client->ps.weapons, WP_CARBINE); + } + else if(COM_BitCheck(stealer->client->ps.weapons, WP_K43)) { + COM_BitSet(stealer->client->ps.weapons, WP_GPG40); + } + + // make sure they lose disguise when changing classes + stealer->client->ps.powerups[PW_OPS_DISGUISED] = 0; + + ClientUserinfoChanged(stealer->s.clientNum); + + return qtrue; +} + +/* + G_PushPlayer //tjw + mostly lifted from WolfRevivePushEnt + */ +qboolean G_PushPlayer(gentity_t *pusher, gentity_t *victim) +{ + vec3_t dir, push; + int force; + float dist; + + if(!g_shove.integer) return qfalse; + + VectorSubtract(pusher->r.currentOrigin, victim->r.currentOrigin, dir); + dir[2] = 0; + dist = VectorNormalize(dir); + /* + dir[2] = 0; + VectorNormalizeFast(dir); + */ + + if(dist > 50) return qfalse; + + force = g_shove.integer; + + /* + VectorScale(dir, force, push); + + VectorAdd(pusher->s.pos.trDelta, push, pusher->s.pos.trDelta); + VectorAdd(pusher->client->ps.velocity, push, pusher->client->ps.velocity); + */ + VectorScale(dir, -force, push); + /* + // add a little hop, if not pushing up + if(push[2] >= 0) + push[2] = force/2; + */ + + VectorAdd(victim->s.pos.trDelta, push, victim->s.pos.trDelta); + VectorAdd(victim->client->ps.velocity, push, victim->client->ps.velocity); + + return qtrue; +} + + +/* + G_DragCorpse //tjw + */ +qboolean G_DragCorpse(gentity_t *dragger, gentity_t *corpse) +{ + vec3_t dir, pull, res; + float dist; + + if(!g_dragCorpse.integer) return qfalse; + VectorSubtract(dragger->r.currentOrigin, corpse->r.currentOrigin, dir); + dir[2] = 0; + dist = VectorNormalize(dir); + + // don't pull corpses past the dragger's head and don't start dragging + // until both players look like they're in contact + if(dist > 85 || dist < 40) return qfalse; + + VectorScale(dir, 100, pull); + + // prevent some zipping around when the corpse doesn't have + // much friction + VectorSubtract(pull, corpse->client->ps.velocity, res); + + // no dragging into the ground + res[2] = 0; + + VectorAdd(corpse->s.pos.trDelta, res, corpse->s.pos.trDelta ); + VectorAdd(corpse->client->ps.velocity, res, corpse->client->ps.velocity); + + return qtrue; // no checks yet } // TAT 1/14/2003 - extracted out the functionality of Cmd_Activate_f from finding the object to use @@ -2744,45 +3200,87 @@ } } - +/* +Cmd_Activate2_f() is only for: + uniform stealing + shove + dragging + class switch + */ void Cmd_Activate2_f( gentity_t *ent ) { trace_t tr; vec3_t end; gentity_t *traceEnt; vec3_t forward, right, up, offset; -// int activatetime = level.time; qboolean found = qfalse; qboolean pass2 = qfalse; - if( ent->client->sess.playerType != PC_COVERTOPS ) { - return; - } + + //if( ent->client->sess.playerType != PC_COVERTOPS ) { + // return; + //} AngleVectors (ent->client->ps.viewangles, forward, right, up); CalcMuzzlePointForActivate (ent, forward, right, up, offset); VectorMA (offset, 96, forward, end); - trap_Trace (&tr, offset, NULL, NULL, end, ent->s.number, (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE)); - - if ( tr.surfaceFlags & SURF_NOIMPACT || tr.entityNum == ENTITYNUM_WORLD) { - trap_Trace (&tr, offset, NULL, NULL, end, ent->s.number, (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_TRIGGER)); - pass2 = qtrue; - } -tryagain: - if ( tr.surfaceFlags & SURF_NOIMPACT || tr.entityNum == ENTITYNUM_WORLD) { - return; + // look for a corpse to drag + trap_Trace(&tr, + offset, + NULL, + NULL, + end, + ent->s.number, + CONTENTS_CORPSE); + if(tr.entityNum >= 0) { + traceEnt = &g_entities[tr.entityNum]; + if(traceEnt->client) { + G_DragCorpse(ent, traceEnt); + return; + } } - traceEnt = &g_entities[ tr.entityNum ]; - - found = Do_Activate2_f(ent, traceEnt); + // look for a guy to push + trap_Trace(&tr, + offset, + NULL, + NULL, + end, + ent->s.number, + CONTENTS_BODY); + if(tr.entityNum >= 0) { + traceEnt = &g_entities[tr.entityNum]; + if(traceEnt->client) { + if(traceEnt->client->ps.eFlags & EF_PLAYDEAD) + G_DragCorpse(ent, traceEnt); + else + G_PushPlayer(ent, traceEnt); + return; + } + } - if(!found && !pass2) { - pass2 = qtrue; - trap_Trace (&tr, offset, NULL, NULL, end, ent->s.number, (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE|CONTENTS_TRIGGER)); - goto tryagain; + // look for a gibbed corpse + trap_Trace(&tr, + offset, + NULL, + NULL, + end, + ent->s.number, + (CONTENTS_SOLID|CONTENTS_BODY|CONTENTS_CORPSE)); + if(tr.entityNum >= 0) { + traceEnt = &g_entities[tr.entityNum]; + if(traceEnt->s.eType == ET_CORPSE && BODY_TEAM(traceEnt)) { + if(BODY_TEAM(traceEnt) == ent->client->sess.sessionTeam ) { + G_ClassSteal(ent, traceEnt); + return; + } + else if(ent->client->sess.playerType == PC_COVERTOPS) { + G_UniformSteal(ent, traceEnt); + return; + } + } } } @@ -3282,7 +3780,7 @@ client->sess.playerWeapon = ent->client->sess.latchPlayerWeapon = cl.sess.playerWeapon; client->sess.playerWeapon2 = ent->client->sess.latchPlayerWeapon2 = cl.sess.playerWeapon2; // spawn them in - ClientSpawn(ent, qtrue); + ClientSpawn(ent, qtrue, qfalse); // restore items client->pers = saved; memcpy( ent->client->ps.persistant, persistant, sizeof(persistant) ); @@ -3397,7 +3895,7 @@ return; } - if( ent->client->ps.stats[STAT_HEALTH] <= 0 && ( ent->client->sess.sessionTeam == TEAM_AXIS || ent->client->sess.sessionTeam == TEAM_ALLIES ) ) { + if( ent->health <= 0 && ( ent->client->sess.sessionTeam == TEAM_AXIS || ent->client->sess.sessionTeam == TEAM_ALLIES ) ) { limbo( ent, qtrue ); } @@ -3474,8 +3972,14 @@ // OSP } else if(G_commandCheck(ent, cmd, qfalse)) { return; - - } else { + } + else if (!Q_stricmp (cmd, "playdead")) { + G_PlayDead(ent); + } + else if (!Q_stricmp (cmd, "m")) { + G_PrivateMessage(ent); + } + else { trap_SendServerCommand( clientNum, va("print \"unknown cmd[lof] %s\n\"", cmd ) ); } } diff -urN et.orig/src/game/g_combat.c et/src/game/g_combat.c --- et.orig/src/game/g_combat.c 2003-07-22 17:05:56.000000000 -0500 +++ et/src/game/g_combat.c 2005-02-12 13:03:30.000000000 -0600 @@ -349,6 +349,9 @@ //float timeLived; weapon_t weap = BG_WeaponForMOD( meansOfDeath ); + // the player really died this time + self->client->ps.eFlags &= ~EF_PLAYDEAD; + // G_Printf( "player_die\n" ); if(attacker == self) { @@ -507,7 +510,10 @@ if ( attacker == self || OnSameTeam (self, attacker ) ) { // DHM - Nerve :: Complaint lodging - if( attacker != self && level.warmupTime <= 0 && g_gamestate.integer == GS_PLAYING) { + if(attacker != self && + level.warmupTime <= 0 && + g_gamestate.integer == GS_PLAYING && + !G_shrubbot_permission(attacker, SBF_IMMUNITY)) { if( attacker->client->pers.localClient ) { trap_SendServerCommand( self-g_entities, "complaint -4" ); } else { @@ -558,32 +564,7 @@ // Add team bonuses Team_FragBonuses(self, inflictor, attacker); - // drop flag regardless - if (self->client->ps.powerups[PW_REDFLAG]) { - item = BG_FindItem("Red Flag"); - if (!item) - item = BG_FindItem("Objective"); - - self->client->ps.powerups[PW_REDFLAG] = 0; - } - if (self->client->ps.powerups[PW_BLUEFLAG]) { - item = BG_FindItem("Blue Flag"); - if (!item) - item = BG_FindItem("Objective"); - - self->client->ps.powerups[PW_BLUEFLAG] = 0; - } - - if (item) { - vec3_t launchvel = { 0, 0, 0 }; - gentity_t *flag = LaunchItem(item, self->r.currentOrigin, launchvel, self->s.number); - - flag->s.modelindex2 = self->s.otherEntityNum2;// JPW NERVE FIXME set player->otherentitynum2 with old modelindex2 from flag and restore here - flag->message = self->message; // DHM - Nerve :: also restore item name - // Clear out player's temp copies - self->s.otherEntityNum2 = 0; - self->message = NULL; - } + G_DropItems(self); // send a fancy "MEDIC!" scream. Sissies, ain' they? if (self->client != NULL) { @@ -622,8 +603,13 @@ //VectorCopy( self->s.angles, self->client->ps.viewangles ); // trap_UnlinkEntity( self ); - self->r.maxs[2] = self->client->ps.crouchMaxZ; //% 0; // ydnar: so bodies don't clip into world - self->client->ps.maxs[2] = self->client->ps.crouchMaxZ; //% 0; // ydnar: so bodies don't clip into world + + // ydnar: so bodies don't clip into world + self->r.maxs[2] = self->client->ps.crouchMaxZ; + self->client->ps.maxs[2] = self->client->ps.crouchMaxZ; + //self->r.maxs[2] = self->client->ps.maxs[2] = 1; + + trap_LinkEntity( self ); // don't allow respawn until the death anim is done @@ -1024,6 +1010,7 @@ int take; int save; int knockback; + int limbo_health; qboolean headShot; qboolean wasAlive; hitRegion_t hr = HR_NUM_HITREGIONS; @@ -1061,6 +1048,10 @@ // xkan, 12/23/2002 - was the bot alive before applying any damage? wasAlive = (targ->health > 0); + + limbo_health = FORCE_LIMBO_HEALTH; + if(g_forceLimboHealth.integer == 1) limbo_health = -150; + // Arnout: combatstate if( targ->client && attacker && attacker->client && attacker != targ ) { /*vec_t dist = -1.f; @@ -1348,15 +1339,6 @@ } } - // add to the attacker's hit counter - if ( attacker->client && targ != attacker && targ->health > 0 ) { - if ( OnSameTeam( targ, attacker ) ) { - attacker->client->ps.persistant[PERS_HITS] -= damage; - } else { - attacker->client->ps.persistant[PERS_HITS] += damage; - } - } - if ( damage < 1 ) { damage = 1; } @@ -1425,7 +1407,8 @@ } } - targ->client->ps.eFlags |= EF_HEADSHOT; + // moved this to below hitsounds so it can still be checked + //targ->client->ps.eFlags |= EF_HEADSHOT; // OSP - Record the headshot if(client && attacker && attacker->client @@ -1491,6 +1474,59 @@ // See if it's the player hurting the emeny flag carrier // Team_CheckHurtCarrier(targ, attacker); + + + // add to the attacker's hit counter + if ( attacker->client && + targ->client && + targ != attacker && + targ->health > limbo_health ) { + + if ( OnSameTeam( targ, attacker ) ) { + attacker->client->ps.persistant[PERS_HITS] -= damage; + } else { + attacker->client->ps.persistant[PERS_HITS] += damage; + } + + // tjw - hitsounds for a server side only mod + if(g_hitsounds.integer && attacker->client->pers.hitsounds) { + + gentity_t *hs_ent; + int snd; + + hs_ent = G_TempEntity(attacker->client->ps.origin, + EV_GLOBAL_CLIENT_SOUND); + hs_ent->s.teamNum = (attacker->client - level.clients); + + // default hitsound + snd = G_SoundIndex("sound/weapons/impact/flesh2.wav"); + + // vsay "hold your fire" on the first hit of a teammate + // only applies if the player has been hurt before + // and the match is not in warmup. + if(OnSameTeam(targ, attacker) && + (!client->lasthurt_mod || client->lasthurt_client != attacker->s.number) && + g_gamestate.integer == GS_PLAYING && + (targ->health - take) > limbo_health) { + + if(client->sess.sessionTeam == TEAM_AXIS) + snd = G_SoundIndex("sound/chat/axis/26a.wav"); + else + snd = G_SoundIndex("sound/chat/allies/26a.wav"); + } + else if(headShot) { + if(!(targ->client->ps.eFlags & EF_HEADSHOT)) + snd = G_SoundIndex("sound/weapons/impact/metal4.wav"); + else + snd = G_SoundIndex("sound/weapons/impact/flesh4.wav"); + } + hs_ent->s.eventParm = snd; + } + } + + // remember that this player has no helmet + if(headShot) targ->client->ps.eFlags |= EF_HEADSHOT; + if (targ->client) { // set the last client who damaged the target @@ -1536,7 +1572,7 @@ G_addStats(targ, attacker, take, mod); } - if( (targ->health < FORCE_LIMBO_HEALTH) && (targ->health > GIB_HEALTH) ) { + if( (targ->health < limbo_health) && (targ->health > GIB_HEALTH) ) { limbo(targ, qtrue); } @@ -1626,7 +1662,7 @@ } // Ridah, this needs to be done last, incase the health is altered in one of the event calls - if ( targ->client ) { + if ( targ->client && !(targ->client->ps.eFlags & EF_PLAYDEAD)) { targ->client->ps.stats[STAT_HEALTH] = targ->health; } } diff -urN et.orig/src/game/g_items.c et/src/game/g_items.c --- et.orig/src/game/g_items.c 2003-07-22 17:05:56.000000000 -0500 +++ et/src/game/g_items.c 2005-02-06 15:29:03.000000000 -0600 @@ -520,6 +520,9 @@ return qfalse; } + // tjw: check heavy weapon restrictions + if(G_IsWeaponDisabled(ent, weapon)) return qfalse; + return BG_WeaponIsPrimaryForClassAndTeam( ent->client->sess.playerType, ent->client->sess.sessionTeam, weapon ); } diff -urN et.orig/src/game/g_local.h et/src/game/g_local.h --- et.orig/src/game/g_local.h 2003-09-02 15:50:26.000000000 -0500 +++ et/src/game/g_local.h 2005-02-10 09:42:38.000000000 -0600 @@ -12,7 +12,7 @@ // the "gameversion" client command will print this plus compile date #ifndef PRE_RELEASE_DEMO -#define GAMEVERSION "etmain" +#define GAMEVERSION "etpub" #else //#define GAMEVERSION "You look like you need a monkey!" #define GAMEVERSION "ettest" @@ -31,7 +31,8 @@ // SP : Axis: 20 seconds // Allies: 30 seconds // MP : Both 10 seconds -#define BODY_TIME(t) ((g_gametype.integer != GT_SINGLE_PLAYER || g_gametype.integer == GT_COOP) ? 10000 : (t) == TEAM_AXIS ? 20000 : 30000) +//#define BODY_TIME(t) ((g_gametype.integer != GT_SINGLE_PLAYER || g_gametype.integer == GT_COOP) ? 10000 : (t) == TEAM_AXIS ? 20000 : 30000) +#define BODY_TIME(t) ((t) == TEAM_AXIS ? 20000 : 20000) #define MAX_MG42_HEAT 1500.f @@ -481,6 +482,7 @@ qboolean runthisframe; g_constructible_stats_t constructibleStats; + }; // Ridah @@ -613,11 +615,12 @@ unsigned compare; } ipFilter_t; -typedef struct ipXPStorage_s { - ipFilter_t filter; - float skills[ SK_NUM_SKILLS ]; +typedef struct XPStorage_s { + char guid[33]; + float skills[SK_NUM_SKILLS]; int timeadded; -} ipXPStorage_t; + int lives; +} XPStorage_t; #define MAX_COMPLAINTIPS 5 @@ -675,6 +678,7 @@ int lastCCPulseTime; int lastSpawnTime; + int lastTeamChangeTime; char botScriptName[MAX_NETNAME]; @@ -698,6 +702,10 @@ int characterIndex; ipFilter_t complaintips[MAX_COMPLAINTIPS]; + + // tjw + int hitsounds; + } clientPersistant_t; typedef struct { @@ -856,6 +864,7 @@ qboolean hasaward; qboolean wantsscore; qboolean maxlivescalced; + int flametime; }; typedef struct { @@ -1124,6 +1133,7 @@ // // g_cmds.c // +void G_PlayDead(gentity_t *ent); void Cmd_Score_f (gentity_t *ent); void StopFollowing( gentity_t *ent ); //void BroadcastTeamChange( gclient_t *client, int oldTeam ); @@ -1135,8 +1145,12 @@ void Cmd_SwapPlacesWithBot_f( gentity_t *ent, int botNum ); void G_EntitySound( gentity_t *ent, const char *soundId, int volume ); void G_EntitySoundNoCut( gentity_t *ent, const char *soundId, int volume ); +int ClientNumbersFromString( char *s, int *plist ); int ClientNumberFromString( gentity_t *to, char *s ); +char *ConcatArgs( int start ); +void DecolorString( char *in, char *out ); void SanitizeString( char *in, char *out, qboolean fToLower ); +void G_DropItems(gentity_t *self); // // g_items.c @@ -1340,7 +1354,7 @@ void InitClientPersistant (gclient_t *client); void InitClientResp (gclient_t *client); void InitBodyQue (void); -void ClientSpawn( gentity_t *ent, qboolean revived ); +void ClientSpawn( gentity_t *ent, qboolean revived, qboolean teamChange ); void player_die (gentity_t *self, gentity_t *inflictor, gentity_t *attacker, int damage, int mod); void AddScore( gentity_t *ent, int score ); void AddKillScore( gentity_t *ent, int score ); @@ -1348,6 +1362,7 @@ qboolean SpotWouldTelefrag( gentity_t *spot ); qboolean G_CheckForExistingModelInfo( bg_playerclass_t* classInfo, const char *modelName, animModelInfo_t **modelInfo ); void G_StartPlayerAppropriateSound(gentity_t *ent, char* soundType); +void G_AddClassSpecificTools(gclient_t *client); void SetWolfSpawnWeapons( gclient_t *client ); void limbo( gentity_t *ent, qboolean makeCorpse ); // JPW NERVE void reinforce(gentity_t *ent); // JPW NERVE @@ -1369,11 +1384,12 @@ qboolean G_FilterIPBanPacket( char *from ); qboolean G_FilterMaxLivesPacket (char *from); qboolean G_FilterMaxLivesIPPacket( char *from ); -ipXPStorage_t* G_FindXPBackup( char *from ); +XPStorage_t* G_FindXPBackup( char *from ); void G_AddXPBackup( gentity_t* ent ); void G_StoreXPBackup( void ); void G_ClearXPBackup( void ); void G_ReadXPBackup( void ); +void G_ClearXPBackupLives(void); void AddMaxLivesGUID( char *str ); void AddMaxLivesBan( const char *str ); void ClearMaxLivesBans(); @@ -1386,7 +1402,7 @@ // g_weapon.c // void FireWeapon( gentity_t *ent ); -void G_BurnMeGood( gentity_t *self, gentity_t *body ); +void G_BurnMeGood( gentity_t *self, gentity_t *body, gentity_t *chunk ); // // IsSilencedWeapon @@ -1463,6 +1479,7 @@ qboolean ReadyToThrowSmoke(gentity_t *ent); // Are we ready to construct? Optionally, will also update the time while we are constructing qboolean ReadyToConstruct(gentity_t *ent, gentity_t *constructible, qboolean updateState); +// tjw: move the player to other team if necessary @@ -1471,7 +1488,7 @@ // qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 ); int Team_ClassForString( char *string ); - +void G_ActiveTeamBalance(); // // g_mem.c @@ -1739,6 +1756,10 @@ extern vmCvar_t server_motd4; extern vmCvar_t server_motd5; extern vmCvar_t team_maxPanzers; +extern vmCvar_t team_maxMortars; +extern vmCvar_t team_maxMG42s; +extern vmCvar_t team_maxFlamers; +extern vmCvar_t team_maxGrenLaunchers; extern vmCvar_t team_maxplayers; extern vmCvar_t team_nocontrols; // @@ -1777,6 +1798,24 @@ extern vmCvar_t g_disableComplaints; +// tjw +extern vmCvar_t g_shrubbot; +extern vmCvar_t g_hitsounds; +extern vmCvar_t g_playDead; +extern vmCvar_t g_shove; +extern vmCvar_t g_dragCorpse; +extern vmCvar_t g_classChange; +extern vmCvar_t g_forceLimboHealth; +extern vmCvar_t g_privateMessages; +extern vmCvar_t g_XPSave; +extern vmCvar_t g_weapons; // see WPF_* defines +extern vmCvar_t g_goomba; +extern vmCvar_t g_spawnInvul; +extern vmCvar_t g_spinCorpse; +extern vmCvar_t g_teamChangeKills; +extern vmCvar_t g_activeTeamBalance; +extern vmCvar_t g_logAdmin; + extern vmCvar_t bot_debug; // if set, draw "thought bubbles" for crosshair-selected bot extern vmCvar_t bot_debug_curAINode; // the text of the current ainode for the bot begin debugged extern vmCvar_t bot_debug_alertState; // alert state of the bot being debugged @@ -2053,6 +2092,7 @@ #define BODY_TEAM(ENT) ENT->s.modelindex #define BODY_CLASS(ENT) ENT->s.modelindex2 #define BODY_CHARACTER(ENT) ENT->s.onFireStart +#define BODY_WEAPON(ENT) ENT->s.nextWeapon //g_buddy_list.c @@ -2273,6 +2313,7 @@ // void G_UpdateCvars(void); void G_wipeCvars(void); +void CheckVote(void); @@ -2492,3 +2533,21 @@ qboolean G_CanPickupWeapon( weapon_t weapon, gentity_t* ent ); qboolean G_LandmineSnapshotCallback( int entityNum, int clientNum ); + + +#include "g_shrubbot.h" +extern g_shrubbot_level_t *g_shrubbot_levels[MAX_SHRUBBOT_LEVELS]; +extern g_shrubbot_admin_t *g_shrubbot_admins[MAX_SHRUBBOT_ADMINS]; +extern g_shrubbot_ban_t *g_shrubbot_bans[MAX_SHRUBBOT_BANS]; + +#define CH_KNIFE_DIST 48 // from g_weapon.c +#define CH_LADDER_DIST 100 +#define CH_WATER_DIST 100 +#define CH_BREAKABLE_DIST 64 +#define CH_DOOR_DIST 96 +#define CH_ACTIVATE_DIST 96 +#define CH_EXIT_DIST 256 +#define CH_FRIENDLY_DIST 1024 +#define CH_REVIVE_DIST 72 // tjw: used to be 48 before smaller hitbox +#define CH_MAX_DIST 1024 // use the largest value from above +#define CH_MAX_DIST_ZOOM 8192 // max dist for zooming hints diff -urN et.orig/src/game/g_main.c et/src/game/g_main.c --- et.orig/src/game/g_main.c 2003-09-02 15:50:26.000000000 -0500 +++ et/src/game/g_main.c 2005-02-11 20:39:40.000000000 -0600 @@ -127,6 +127,10 @@ vmCvar_t match_warmupDamage; vmCvar_t server_autoconfig; vmCvar_t team_maxPanzers; +vmCvar_t team_maxMG42s; +vmCvar_t team_maxFlamers; +vmCvar_t team_maxMortars; +vmCvar_t team_maxGrenLaunchers; vmCvar_t team_maxplayers; vmCvar_t team_nocontrols; vmCvar_t server_motd0; @@ -203,6 +207,23 @@ vmCvar_t g_disableComplaints; +// tjw +vmCvar_t g_shrubbot; +vmCvar_t g_hitsounds; +vmCvar_t g_playDead; +vmCvar_t g_shove; +vmCvar_t g_dragCorpse; +vmCvar_t g_classChange; +vmCvar_t g_forceLimboHealth; +vmCvar_t g_privateMessages; +vmCvar_t g_XPSave; +vmCvar_t g_weapons; // see WPF_ defines +vmCvar_t g_goomba; +vmCvar_t g_spawnInvul; +vmCvar_t g_spinCorpse; +vmCvar_t g_teamChangeKills; +vmCvar_t g_activeTeamBalance; +vmCvar_t g_logAdmin; cvarTable_t gameCvarTable[] = { // don't override the cheat state set by the system @@ -348,6 +369,10 @@ { &server_motd4, "server_motd4", "", 0, 0, qfalse, qfalse }, { &server_motd5, "server_motd5", "", 0, 0, qfalse, qfalse }, { &team_maxPanzers, "team_maxPanzers", "-1", 0, 0, qfalse, qfalse }, + { &team_maxMG42s, "team_maxMG42s", "-1", 0, 0, qfalse, qfalse }, + { &team_maxFlamers, "team_maxFlamers", "-1", 0, 0, qfalse, qfalse }, + { &team_maxMortars, "team_maxMortars", "-1", 0, 0, qfalse, qfalse }, + { &team_maxGrenLaunchers, "team_maxGrenLaunchers", "-1", 0, 0, qfalse, qfalse }, { &team_maxplayers, "team_maxplayers", "0", 0, 0, qfalse, qfalse }, { &team_nocontrols, "team_nocontrols", "1", 0, 0, qfalse, qfalse }, { &vote_allow_comp, "vote_allow_comp", "1", 0, 0, qfalse, qfalse }, @@ -422,6 +447,23 @@ { &g_nextcampaign, "nextcampaign", "", CVAR_TEMP }, { &g_disableComplaints, "g_disableComplaints", "0", CVAR_ARCHIVE }, + // tjw + { &g_shrubbot, "g_shrubbot", "", CVAR_ARCHIVE, 0, qfalse }, + { &g_hitsounds, "g_hitsounds", "0", CVAR_ARCHIVE }, + { &g_playDead, "g_playDead", "0", CVAR_ARCHIVE }, + { &g_shove, "g_shove", "0", CVAR_ARCHIVE }, + { &g_dragCorpse, "g_dragCorpse", "0", CVAR_ARCHIVE }, + { &g_classChange, "g_classChange", "0", CVAR_ARCHIVE }, + { &g_forceLimboHealth, "g_forceLimboHealth", "1", CVAR_ARCHIVE }, + { &g_privateMessages, "g_privateMessages", "0", CVAR_ARCHIVE }, + { &g_XPSave, "g_XPSave", "0", CVAR_ARCHIVE }, + { &g_weapons, "g_weapons", "0", CVAR_ARCHIVE }, + { &g_goomba, "g_goomba", "0", CVAR_ARCHIVE }, + { &g_spawnInvul, "g_spawnInvul", "3", CVAR_ARCHIVE }, + { &g_spinCorpse, "g_spinCorpse", "0", CVAR_ARCHIVE }, + { &g_teamChangeKills, "g_teamChangeKills", "1", CVAR_ARCHIVE }, + { &g_activeTeamBalance, "g_activeTeamBalance", "0", CVAR_ARCHIVE }, + { &g_logAdmin, "g_logAdmin", "", CVAR_ARCHIVE }, }; // bk001129 - made static to avoid aliasing @@ -550,19 +592,6 @@ trap_Error( text ); } - -#define CH_KNIFE_DIST 48 // from g_weapon.c -#define CH_LADDER_DIST 100 -#define CH_WATER_DIST 100 -#define CH_BREAKABLE_DIST 64 -#define CH_DOOR_DIST 96 -#define CH_ACTIVATE_DIST 96 -#define CH_EXIT_DIST 256 -#define CH_FRIENDLY_DIST 1024 - -#define CH_MAX_DIST 1024 // use the largest value from above -#define CH_MAX_DIST_ZOOM 8192 // max dist for zooming hints - /* ============== G_CursorHintIgnoreEnt: returns whether the ent should be ignored @@ -778,8 +807,11 @@ // Show medics a syringe if they can revive someone if ( traceEnt->client && traceEnt->client->sess.sessionTeam == ent->client->sess.sessionTeam) { - if(ps->stats[ STAT_PLAYER_CLASS ] == PC_MEDIC && traceEnt->client->ps.pm_type == PM_DEAD && !( traceEnt->client->ps.pm_flags & PMF_LIMBO ) ) { - hintDist = 48; // JPW NERVE matches weapon_syringe in g_weapon.c + if(ps->stats[ STAT_PLAYER_CLASS ] == PC_MEDIC && + traceEnt->client->ps.pm_type == PM_DEAD && + !(traceEnt->client->ps.pm_flags & PMF_LIMBO) && + !(traceEnt->client->ps.eFlags & EF_PLAYDEAD)) { + hintDist = CH_REVIVE_DIST; hintType = HINT_REVIVE; } } else if(traceEnt->client && traceEnt->client->isCivilian) { @@ -831,6 +863,12 @@ // TDF This entire function could be the poster boy for converting to OO programming!!! // I'm making this into a switch in a vain attempt to make this readable so I can find which // brackets don't match!!! + // + // tjw: OO programming won't help those who refuse to use + // procedures and insist on stretching lines and tab depth + // into oblivion. It amazes me how people think that using + // objects are going to magically enable them write readable + // code. switch (checkEnt->s.eType) { case ET_CORPSE: @@ -843,9 +881,20 @@ if( hintVal > 255 ) { hintVal = 255; } + break; } } } + // class stealing + if(!g_classChange.integer) break; + if(BODY_TEAM(traceEnt) == ent->client->sess.sessionTeam) { + hintDist = 48; + hintType = HINT_UNIFORM; + hintVal = BODY_VALUE(traceEnt); + if( hintVal > 255 ) { + hintVal = 255; + } + } break; case ET_GENERAL: case ET_MG42_BARREL: @@ -1635,6 +1684,9 @@ } else { G_Printf( "Not logging to disk.\n" ); } + if ( g_shrubbot.string[0] ) { + G_shrubbot_readconfig(NULL, 0); + } G_InitWorldSession(); @@ -1694,12 +1746,17 @@ numSplinePaths = 0 ; numPathCorners = 0; -#ifdef USEXPSTORAGE - G_ClearXPBackup(); - if( g_gametype.integer == GT_WOLF_CAMPAIGN && !level.newCampaign ) { - G_ReadXPBackup(); + + if(g_XPSave.integer) { + G_ClearXPBackup(); + if(g_gametype.integer == GT_WOLF_CAMPAIGN && + !(g_campaigns[level.currentCampaign].current == 0 || + level.newCampaign)) { + G_ReadXPBackup(); + } + G_ClearXPBackupLives(); } -#endif // USEXPSTORAGE + // START Mad Doctor I changes, 8/21/2002 // This needs to be called before G_SpawnEntitiesFromString, or the @@ -3621,6 +3678,7 @@ G_RunEntity( &g_entities[ i ], msec ); } + G_ActiveTeamBalance(); for( i = 0; i < level.numConnectedClients; i++ ) { ClientEndFrame(&g_entities[level.sortedClients[i]]); diff -urN et.orig/src/game/g_missile.c et/src/game/g_missile.c --- et.orig/src/game/g_missile.c 2003-09-02 15:50:26.000000000 -0500 +++ et/src/game/g_missile.c 2005-02-07 11:00:30.000000000 -0600 @@ -973,7 +973,7 @@ body->flameQuota = 0; } - G_BurnMeGood( self, body ); + G_BurnMeGood( self->parent, body, self ); } void G_FlameDamage( gentity_t *self, gentity_t *ignoreent ) { diff -urN et.orig/src/game/g_session.c et/src/game/g_session.c --- et.orig/src/game/g_session.c 2003-07-31 23:08:56.000000000 -0500 +++ et/src/game/g_session.c 2005-01-30 20:03:40.000000000 -0600 @@ -463,9 +463,9 @@ char strServerInfo[MAX_INFO_STRING]; int j; -#ifdef USEXPSTORAGE - G_StoreXPBackup(); -#endif // USEXPSTORAGE + + if(g_XPSave.integer) G_StoreXPBackup(); + trap_GetServerinfo(strServerInfo, sizeof(strServerInfo)); trap_Cvar_Set("session", va("%i %i %s", g_gametype.integer, diff -urN et.orig/src/game/g_shrubbot.c et/src/game/g_shrubbot.c --- et.orig/src/game/g_shrubbot.c 1969-12-31 18:00:00.000000000 -0600 +++ et/src/game/g_shrubbot.c 2005-02-11 17:17:05.000000000 -0600 @@ -0,0 +1,1056 @@ +/* + * g_shrubbot.c + * + * This code is the original work of Tony J. White + * + * The functionality of this code mimics the behaviour of the currently + * inactive project shrubmod (http://www.etstats.com/shrubet/index.php?ver=2) + * by Ryan Mannion. However, shrubmod was a closed-source project and + * none of it's code has been copied, only it's functionality. + * + * You are free to use this implementation of shrubbot in you're + * own mod project if you wish. + * + */ + +#include "g_local.h" + +static const struct g_shrubbot_cmd g_shrubbot_cmds[] = { + {"readconfig", G_shrubbot_readconfig, 'G'}, + {"time", G_shrubbot_time, 'C'}, + {"setlevel", G_shrubbot_setlevel, 's'}, + {"kick", G_shrubbot_kick, 'k'}, + {"ban", G_shrubbot_ban, 'b'}, + {"unban", G_shrubbot_unban, 'b'}, + {"putteam", G_shrubbot_putteam, 'p'}, + {"pause", G_shrubbot_pause, 'z'}, + {"unpause", G_shrubbot_unpause, 'z'}, + {"listplayers", G_shrubbot_listplayers, 'i'}, + {"mute", G_shrubbot_mute, 'm'}, + {"unmute", G_shrubbot_mute, 'm'}, + {"showbans", G_shrubbot_showbans, 'B'}, + {"help", G_shrubbot_help, 'h'}, + {"admintest", G_shrubbot_admintest, 'a'}, + {"cancelvote", G_shrubbot_cancelvote, 'c'}, + {"spec999", G_shrubbot_spec999, 'P'}, + {"shuffle", G_shrubbot_shuffle, 'S'}, + {"", NULL, '\0'} +}; + +g_shrubbot_level_t *g_shrubbot_levels[MAX_SHRUBBOT_LEVELS]; +g_shrubbot_admin_t *g_shrubbot_admins[MAX_SHRUBBOT_ADMINS]; +g_shrubbot_ban_t *g_shrubbot_bans[MAX_SHRUBBOT_BANS]; + +qboolean G_shrubbot_permission(gentity_t *ent, char flag) +{ + int i; + int l = 0; + char *guid; + qboolean found = qfalse; + char userinfo[MAX_INFO_STRING]; + + // console always wins + if(!ent) return qtrue; + + trap_GetUserinfo(ent-g_entities, userinfo, sizeof(userinfo)); + guid = Info_ValueForKey(userinfo, "cl_guid"); + for(i=0; g_shrubbot_admins[i]; i++) { + if(!Q_stricmp(guid, g_shrubbot_admins[i]->guid)) { + if(strchr(g_shrubbot_admins[i]->flags, flag)) + return qtrue; + l = g_shrubbot_admins[i]->level; + } + } + for(i=0; g_shrubbot_levels[i]; i++) { + if(g_shrubbot_levels[i]->level == l) { + if(strchr(g_shrubbot_levels[i]->flags, flag)) + return qtrue; + } + } + return qfalse; +} + + +qboolean _shrubbot_admin_higher(gentity_t *admin, gentity_t *victim) +{ + int i; + int alevel = 0; + char *guid; + char userinfo[MAX_INFO_STRING]; + + // console always wins + if(!admin) return qtrue; + // just in case + if(!victim) return qtrue; + + trap_GetUserinfo(admin-g_entities, userinfo, sizeof(userinfo)); + guid = Info_ValueForKey(userinfo, "cl_guid"); + for(i=0; g_shrubbot_admins[i]; i++) { + if(!Q_stricmp(guid, g_shrubbot_admins[i]->guid)) { + alevel = g_shrubbot_admins[i]->level; + } + } + trap_GetUserinfo(victim-g_entities, userinfo, sizeof(userinfo)); + guid = Info_ValueForKey(userinfo, "cl_guid"); + for(i=0; g_shrubbot_admins[i]; i++) { + if(!Q_stricmp(guid, g_shrubbot_admins[i]->guid)) { + if(alevel < g_shrubbot_admins[i]->level) + return qfalse; + } + } + return qtrue; +} + +void _shrubbot_writeconfig_string(char *s, fileHandle_t f) +{ + char buf[MAX_STRING_CHARS]; + + buf[0] = '\0'; + if(s[0]) { + Q_strcat(buf, sizeof(buf), s); + trap_FS_Write(buf, strlen(buf), f); + } + trap_FS_Write("\n", 1, f); +} + +void _shrubbot_writeconfig_int(int v, fileHandle_t f) +{ + char buf[32]; + + sprintf(buf, "%d", v); + if(buf[0]) trap_FS_Write(buf, strlen(buf), f); + trap_FS_Write("\n", 1, f); +} + +void _shrubbot_writeconfig() +{ + fileHandle_t f; + int len, i; + time_t t; + + if(!g_shrubbot.string[0]) return; + time(&t); + t = t - SHRUBBOT_BAN_EXPIRE_OFFSET; + len = trap_FS_FOpenFile(g_shrubbot.string, &f, FS_WRITE); + if(len < 0) { + G_Printf("_shrubbot_writeconfig: could not open %s\n", + g_shrubbot.string); + } + for(i=0; g_shrubbot_levels[i]; i++) { + trap_FS_Write("[level]\n", 8, f); + trap_FS_Write("level = ", 10, f); + _shrubbot_writeconfig_int(g_shrubbot_levels[i]->level, f); + trap_FS_Write("name = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_levels[i]->name, f); + trap_FS_Write("flags = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_levels[i]->flags, f); + trap_FS_Write("\n", 1, f); + } + for(i=0; g_shrubbot_admins[i]; i++) { + // don't write level 0 users + if(g_shrubbot_admins[i]->level < 1) continue; + + trap_FS_Write("[admin]\n", 8, f); + trap_FS_Write("name = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_admins[i]->name, f); + trap_FS_Write("guid = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_admins[i]->guid, f); + trap_FS_Write("level = ", 10, f); + _shrubbot_writeconfig_int(g_shrubbot_admins[i]->level, f); + trap_FS_Write("flags = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_admins[i]->flags, f); + trap_FS_Write("\n", 1, f); + } + for(i=0; g_shrubbot_bans[i]; i++) { + // don't write expired bans + if((g_shrubbot_bans[i]->expires - t) < 1) continue; + + trap_FS_Write("[ban]\n", 6, f); + trap_FS_Write("name = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_bans[i]->name, f); + trap_FS_Write("guid = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_bans[i]->guid, f); + trap_FS_Write("ip = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_bans[i]->ip, f); + trap_FS_Write("reason = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_bans[i]->reason, f); + trap_FS_Write("made = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_bans[i]->made, f); + trap_FS_Write("expires = ", 10, f); + _shrubbot_writeconfig_int(g_shrubbot_bans[i]->expires, f); + trap_FS_Write("banner = ", 10, f); + _shrubbot_writeconfig_string(g_shrubbot_bans[i]->banner, f); + trap_FS_Write("\n", 1, f); + } + trap_FS_FCloseFile(f); +} + +void _shrubbot_readconfig_string(char **cnf, char *s, int size) +{ + char *t; + + COM_MatchToken(cnf, "="); + t = COM_ParseExt(cnf, qfalse); + s[0] = '\0'; + while(t[0]) { + if((s[0] == '\0' && strlen(t) <= size) || + (strlen(t)+strlen(s) < size)) { + + Q_strcat(s, size, t); + Q_strcat(s, size, " "); + } + t = COM_ParseExt(cnf, qfalse); + } + // trim the trailing space + if(strlen(s) > 0 && s[strlen(s)-1] == ' ') + s[strlen(s)-1] = '\0'; +} + +void _shrubbot_readconfig_int(char **cnf, int *v) +{ + char *t; + + COM_MatchToken(cnf, "="); + t = COM_ParseExt(cnf, qfalse); + *v = atoi(t); +} + +/* + if we can't parse any levels from readconfig, set up default + ones to make new installs easier for admins +*/ +void _shrubbot_default_levels() { + g_shrubbot_level_t *l; + int i; + + for(i=0; g_shrubbot_levels[i]; i++) { + free(g_shrubbot_levels[i]); + g_shrubbot_levels[i] = NULL; + } + for(i=0; i<=5; i++) { + l = malloc(sizeof(g_shrubbot_level_t)); + l->level = i; + *l->name = '\0'; + *l->flags = '\0'; + g_shrubbot_levels[i] = l; + } + Q_strcat(g_shrubbot_levels[0]->flags, 5, "iahCp"); + Q_strcat(g_shrubbot_levels[1]->flags, 5, "iahCp"); + Q_strcat(g_shrubbot_levels[2]->flags, 6, "iahCpP"); + Q_strcat(g_shrubbot_levels[3]->flags, 9, "i1ahCpPkm"); + Q_strcat(g_shrubbot_levels[4]->flags, 11, "i1ahCpPkmBb"); + Q_strcat(g_shrubbot_levels[5]->flags, 12, "i1ahCpPkmBbs"); +} + +void _shrubbot_log(gentity_t *admin, char *cmd, int skiparg) +{ + fileHandle_t f; + int len, i, j; + char string[MAX_STRING_CHARS]; + int min, tens, sec; + char userinfo[MAX_INFO_STRING]; + char *vguid, *aguid; + g_shrubbot_admin_t *a; + g_shrubbot_level_t *l; + char flags[MAX_SHRUBBOT_FLAGS*2]; + gentity_t *victim = NULL; + int pids[MAX_CLIENTS]; + char name[MAX_NAME_LENGTH]; + + if(!g_logAdmin.string[0]) return; + + + len = trap_FS_FOpenFile(g_logAdmin.string, &f, FS_APPEND); + if(len < 0) { + G_Printf("_shrubbot_log: error could not open %s\n", + g_logAdmin.string); + return; + } + + sec = level.time / 1000; + min = sec / 60; + sec -= min * 60; + tens = sec / 10; + sec -= tens * 10; + + *flags = '\0'; + if(admin) { + trap_GetUserinfo(admin->s.clientNum, userinfo, sizeof(userinfo)); + aguid = Info_ValueForKey(userinfo, "cl_guid"); + for(i=0; g_shrubbot_admins[i]; i++) { + if(!Q_stricmp(g_shrubbot_admins[i]->guid , aguid)) { + a = g_shrubbot_admins[i]; + Q_strncpyz(flags, a->flags, sizeof(flags)); + for(j=0; g_shrubbot_levels[j]; j++) { + if(g_shrubbot_levels[j]->level == a->level) { + l = g_shrubbot_levels[j]; + Q_strcat(flags, sizeof(flags), l->flags); + break; + } + } + break; + } + } + trap_GetUserinfo(admin->s.clientNum, userinfo, sizeof(userinfo)); + aguid = Info_ValueForKey(userinfo, "cl_guid"); + } + + if(trap_Argc() > 1+skiparg) { + trap_Argv(1+skiparg, name, sizeof(name)); + if(ClientNumbersFromString(name, pids) == 1) { + victim = &g_entities[pids[0]]; + } + } + + if(victim && Q_stricmp(cmd, "attempted")) { + trap_GetUserinfo(victim->s.clientNum, userinfo, sizeof(userinfo)); + vguid = Info_ValueForKey(userinfo, "cl_guid"); + Com_sprintf(string, sizeof(string), + "%3i:%i%i: %i: %s: %s: %s: %s: %s: %s: \"%s\"\n", + min, + tens, + sec, + (admin) ? admin->s.clientNum : -1, + (admin) ? aguid : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + (admin) ? admin->client->pers.netname : "console", + flags, + cmd, + vguid, + victim->client->pers.netname, + ConcatArgs(2+skiparg)); + } + else { + Com_sprintf(string, sizeof(string), + "%3i:%i%i: %i: %s: %s: %s: %s: \"%s\"\n", + min, + tens, + sec, + (admin) ? admin->s.clientNum : -1, + (admin) ? aguid : "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", + (admin) ? admin->client->pers.netname : "console", + flags, + cmd, + ConcatArgs(1+skiparg)); + } + trap_FS_Write(string, strlen(string), f); + trap_FS_FCloseFile(f); +} + +qboolean G_shrubbot_ban_check(char *userinfo) +{ + char *guid, *ip; + int i; + time_t t; + + if(!time(&t)) return qfalse; + t = t - SHRUBBOT_BAN_EXPIRE_OFFSET; + if(!*userinfo) return qfalse; + ip = Info_ValueForKey(userinfo, "ip"); + if(!*ip) return qfalse; + guid = Info_ValueForKey(userinfo, "cl_guid"); + if(!*guid) return qfalse; + for(i=0; g_shrubbot_bans[i]; i++) { + if((g_shrubbot_bans[i]->expires - t) < 1) continue; + if(strstr(g_shrubbot_bans[i]->ip, ip)) return qtrue; + if(!Q_stricmp(g_shrubbot_bans[i]->guid, guid)) return qtrue; + } + return qfalse; +} + +qboolean G_shrubbot_cmd_check(gentity_t *ent) +{ + int i; + char command[16]; + int skip = 0; + + if(sscanf(ConcatArgs(0), "say !%16s", command) == 1) { + skip = 1; + } + else if(sscanf(ConcatArgs(0), "!%16s", command) != 1) { + return qfalse; + } + for (i=0; g_shrubbot_cmds[i].keyword[0]; i++) { + if(!Q_stricmp(command, g_shrubbot_cmds[i].keyword)) { + if(G_shrubbot_permission(ent, g_shrubbot_cmds[i].flag)){ + g_shrubbot_cmds[i].handler(ent, skip); + _shrubbot_log(ent, command, skip); + return qtrue; + } + else { + SP(va("%s: permission denied\n", + g_shrubbot_cmds[i].keyword)); + _shrubbot_log(ent, "attempted", skip-1); + } + } + } + return qfalse; +} + +qboolean G_shrubbot_readconfig(gentity_t *ent, int skiparg) +{ + g_shrubbot_level_t *l; + g_shrubbot_admin_t *a; + g_shrubbot_ban_t *b; + int lc = 0, ac = 0, bc = 0; + fileHandle_t f; + int i; + int len; + char *cnf, *cnf2; + char *t; + qboolean level_open, admin_open, ban_open; + + if(!g_shrubbot.string[0]) return qfalse; + len = trap_FS_FOpenFile(g_shrubbot.string, &f, FS_READ) ; + if(len < 0) { + SP(va("readconfig: could not open shrubbot config file %s\n", + g_shrubbot.string)); + _shrubbot_default_levels(); + return qfalse; + } + cnf = malloc(len+1); + cnf2 = cnf; + trap_FS_Read(cnf, len, f); + *(cnf + len) = '\0'; + trap_FS_FCloseFile(f); + + for(i=0; g_shrubbot_levels[i]; i++) { + free(g_shrubbot_levels[i]); + g_shrubbot_levels[i] = NULL; + } + for(i=0; g_shrubbot_admins[i]; i++) { + free(g_shrubbot_admins[i]); + g_shrubbot_admins[i] = NULL; + } + for(i=0; g_shrubbot_bans[i]; i++) { + free(g_shrubbot_bans[i]); + g_shrubbot_bans[i] = NULL; + } + + t = COM_Parse(&cnf); + level_open = admin_open = ban_open = qfalse; + while(*t) { + if(!Q_stricmp(t, "[level]") || + !Q_stricmp(t, "[admin]") || + !Q_stricmp(t, "[ban]")) { + + if(level_open) g_shrubbot_levels[lc++] = l; + else if(admin_open) g_shrubbot_admins[ac++] = a; + else if(ban_open) g_shrubbot_bans[bc++] = b; + level_open = admin_open = ban_open = qfalse; + } + + if(level_open) { + if(!Q_stricmp(t, "level")) { + _shrubbot_readconfig_int(&cnf, &l->level); + } + else if(!Q_stricmp(t, "name")) { + _shrubbot_readconfig_string(&cnf, + l->name, sizeof(l->name)); + } + else if(!Q_stricmp(t, "flags")) { + _shrubbot_readconfig_string(&cnf, + l->flags, sizeof(l->flags)); + } + else { + SP(va("readconfig: [level] parse error near " + "%s on line %d\n", + t, + COM_GetCurrentParseLine())); + } + } + else if(admin_open) { + if(!Q_stricmp(t, "name")) { + _shrubbot_readconfig_string(&cnf, + a->name, sizeof(a->name)); + } + else if(!Q_stricmp(t, "guid")) { + _shrubbot_readconfig_string(&cnf, + a->guid, sizeof(a->guid)); + } + else if(!Q_stricmp(t, "level")) { + _shrubbot_readconfig_int(&cnf, &a->level); + } + else if(!Q_stricmp(t, "flags")) { + _shrubbot_readconfig_string(&cnf, + a->flags, sizeof(a->flags)); + } + else { + SP(va("readconfig: [admin] parse error near " + "%s on line %d\n", + t, + COM_GetCurrentParseLine())); + } + + } + else if(ban_open) { + if(!Q_stricmp(t, "name")) { + _shrubbot_readconfig_string(&cnf, + b->name, sizeof(b->name)); + } + else if(!Q_stricmp(t, "guid")) { + _shrubbot_readconfig_string(&cnf, + b->guid, sizeof(b->guid)); + } + else if(!Q_stricmp(t, "ip")) { + _shrubbot_readconfig_string(&cnf, + b->ip, sizeof(b->ip)); + } + else if(!Q_stricmp(t, "reason")) { + _shrubbot_readconfig_string(&cnf, + b->reason, sizeof(b->reason)); + } + else if(!Q_stricmp(t, "made")) { + _shrubbot_readconfig_string(&cnf, + b->made, sizeof(b->made)); + } + else if(!Q_stricmp(t, "expires")) { + _shrubbot_readconfig_int(&cnf, &b->expires); + } + else if(!Q_stricmp(t, "banner")) { + _shrubbot_readconfig_string(&cnf, + b->banner, sizeof(b->banner)); + } + else { + SP(va("readconfig: [ban] parse error near " + "%s on line %d\n", + t, + COM_GetCurrentParseLine())); + } + } + + if(!Q_stricmp(t, "[level]")) { + if(lc >= MAX_SHRUBBOT_LEVELS) return qfalse; + l = malloc(sizeof(g_shrubbot_level_t)); + l->level = 0; + *l->name = '\0'; + *l->flags = '\0'; + level_open = qtrue; + } + else if(!Q_stricmp(t, "[admin]")) { + if(ac >= MAX_SHRUBBOT_ADMINS) return qfalse; + a = malloc(sizeof(g_shrubbot_admin_t)); + *a->name = '\0'; + *a->guid = '\0'; + a->level = 0; + *a->flags = '\0'; + admin_open = qtrue; + } + else if(!Q_stricmp(t, "[ban]")) { + if(bc >= MAX_SHRUBBOT_BANS) return qfalse; + b = malloc(sizeof(g_shrubbot_ban_t)); + *b->name = '\0'; + *b->guid = '\0'; + *b->ip = '\0'; + *b->made = '\0'; + b->expires = 0; + *b->reason = '\0'; + ban_open = qtrue; + } + t = COM_Parse(&cnf); + } + if(level_open) g_shrubbot_levels[lc++] = l; + if(admin_open) g_shrubbot_admins[ac++] = a; + if(ban_open) g_shrubbot_bans[bc++] = b; + free(cnf2); + SP(va("readconfig: loaded %d levels, %d admins, %d bans\n", + lc, ac, bc)); + if(lc == 0) _shrubbot_default_levels(); + return qtrue; +} + +qboolean G_shrubbot_time(gentity_t *ent, int skiparg) +{ + time_t t; + struct tm *lt; + char s[32]; + + if(!time(&t)) return qfalse; + lt = localtime(&t); + strftime(s, sizeof(s), "%I:%M%p %Z", lt); + AP(va("print \"Local Time: %s\n\"", s)); + return qtrue; +} + +qboolean G_shrubbot_setlevel(gentity_t *ent, int skiparg) +{ + int pids[MAX_CLIENTS]; + char name[MAX_NAME_LENGTH], err[MAX_STRING_CHARS], lstr[2]; + char userinfo[MAX_INFO_STRING]; + char *guid; + char n2[MAX_NAME_LENGTH]; + int l, i; + gentity_t *vic; + qboolean updated = qfalse; + g_shrubbot_admin_t *a; + + + if(trap_Argc() != 3+skiparg) { + SP("setlevel usage: !setlevel [name|slot#] [level]\n"); + return qfalse; + } + trap_Argv(1+skiparg, name, sizeof(name)); + trap_Argv(2+skiparg, lstr, sizeof(lstr)); + l = atoi(lstr); + if(l < 0) { + SP("setlevel: level must be an integer\n"); + return qfalse; + } + if(ClientNumbersFromString(name, pids) != 1) { + G_shrubbot_match_one_player(pids, err, sizeof(err)); + SP(va("setlevel: %s\n", err)); + return qfalse; + } + if(!_shrubbot_admin_higher(ent, &g_entities[pids[0]])) { + SP("setlevel: sorry, but your intended victim has a higher" + " admin level than you do.\n"); + return qfalse; + } + vic = &g_entities[pids[0]]; + trap_GetUserinfo(pids[0], userinfo, sizeof(userinfo)); + guid = Info_ValueForKey(userinfo, "cl_guid"); + SanitizeString(vic->client->pers.netname, n2, qtrue); + + for(i=0;g_shrubbot_admins[i];i++) { + if(!Q_stricmp(g_shrubbot_admins[i]->guid, guid)) { + g_shrubbot_admins[i]->level = l; + Q_strncpyz(g_shrubbot_admins[i]->name, n2, + sizeof(g_shrubbot_admins[i]->name)); + updated = qtrue; + } + } + if(!updated) { + if(i == MAX_SHRUBBOT_ADMINS) { + SP("setlevel: too man admins\n"); + return qfalse; + } + a = malloc(sizeof(g_shrubbot_admin_t)); + a->level = l; + Q_strncpyz(a->name, n2, sizeof(a->name)); + Q_strncpyz(a->guid, guid, sizeof(a->guid)); + *a->flags = '\0'; + g_shrubbot_admins[i] = a; + } + + SP(va("setlevel: %s^7 is now a level %d admin\n", + vic->client->pers.netname, + l)); + _shrubbot_writeconfig(); + return qtrue; +} + +qboolean G_shrubbot_kick(gentity_t *ent, int skiparg) +{ + int pids[MAX_CLIENTS]; + char name[MAX_NAME_LENGTH], *reason, err[MAX_STRING_CHARS]; + + if(trap_Argc() < 3+skiparg) { + SP("kick usage: !kick [name] [reason]\n"); + return qfalse; + } + trap_Argv(1+skiparg, name, sizeof(name)); + reason = ConcatArgs(2+skiparg); + if(ClientNumbersFromString(name, pids) != 1) { + G_shrubbot_match_one_player(pids, err, sizeof(err)); + SP(va("kick: %s\n", err)); + return qfalse; + } + if(!_shrubbot_admin_higher(ent, &g_entities[pids[0]])) { + SP("kick: sorry, but your intended victim has a higher admin" + " level than you do.\n"); + return qfalse; + } + //ClientDisconnect(pids[0]); + trap_SendConsoleCommand( EXEC_APPEND, va( + "clientkick %d\n", pids[0] )); + return qtrue; +} + +qboolean G_shrubbot_ban(gentity_t *ent, int skiparg) +{ + int pids[MAX_CLIENTS]; + int seconds; + char name[MAX_NAME_LENGTH], secs[7], *reason, err[MAX_STRING_CHARS]; + char userinfo[MAX_INFO_STRING]; + char *guid, *ip; + char tmp[MAX_NAME_LENGTH]; + int i; + g_shrubbot_ban_t *b; + time_t t; + struct tm *lt; + gentity_t *vic; + + if(!time(&t)) return qfalse; + if(trap_Argc() < 4+skiparg) { + SP("ban usage: !ban [name] [seconds] [reason]\n"); + return qfalse; + } + trap_Argv(1+skiparg, name, sizeof(name)); + trap_Argv(2+skiparg, secs, sizeof(secs)); + reason = ConcatArgs(3+skiparg); + seconds = atoi(secs); + if(seconds < 0) { + SP("ban: seconds must be a positive integer\n"); + return qfalse; + } + if(ClientNumbersFromString(name, pids) != 1) { + G_shrubbot_match_one_player(pids, err, sizeof(err)); + SP(va("ban: %s\n", err)); + return qfalse; + } + if(!_shrubbot_admin_higher(ent, &g_entities[pids[0]])) { + SP("ban: sorry, but your intended victim has a higher admin" + " level than you do.\n"); + return qfalse; + } + + trap_GetUserinfo(pids[0], userinfo, sizeof(userinfo)); + guid = Info_ValueForKey(userinfo, "cl_guid"); + ip = Info_ValueForKey(userinfo, "ip"); + vic = &g_entities[pids[0]]; + b = malloc(sizeof(g_shrubbot_ban_t)); + + SanitizeString(vic->client->pers.netname, tmp, qtrue); + Q_strncpyz(b->name, tmp, sizeof(b->name)); + Q_strncpyz(b->guid, guid, sizeof(b->guid)); + + // strip port off of ip + for(i=0; *ip; *ip++) { + if(i >= sizeof(tmp) || *ip == ':') break; + tmp[i++] = *ip; + } + tmp[i] = '\0'; + Q_strncpyz(b->ip, tmp, sizeof(b->ip)); + + lt = localtime(&t); + strftime(b->made, sizeof(b->made), "%D %T", lt); + if(ent) { + SanitizeString(ent->client->pers.netname, tmp, qtrue); + Q_strncpyz(b->banner, tmp, sizeof(b->banner)); + } + else Q_strncpyz(b->banner, "console", sizeof(b->banner)); + b->expires = t - SHRUBBOT_BAN_EXPIRE_OFFSET + seconds; + Q_strncpyz(b->reason, reason, sizeof(b->reason)); + for(i=0; g_shrubbot_bans[i]; i++); + if(i == MAX_SHRUBBOT_BANS) { + SP("ban: too many bans\n"); + free(b); + return qfalse; + } + g_shrubbot_bans[i] = b; + SP(va("ban: %s^7 is now banned\n", vic->client->pers.netname)); + _shrubbot_writeconfig(); + ClientDisconnect(pids[0]); + return qtrue; +} + +qboolean G_shrubbot_unban(gentity_t *ent, int skiparg) +{ + int bnum; + char bs[4]; + time_t t; + + if(!time(&t)) return qfalse; + if(trap_Argc() != 2+skiparg) { + SP("unban usage: !unban [ban #]\n"); + return qfalse; + } + trap_Argv(1+skiparg, bs, sizeof(bs)); + bnum = atoi(bs); + if(bnum < 1) { + SP("unban: invalid ban #\n"); + return qfalse; + } + if(!g_shrubbot_bans[bnum-1]) { + SP("unban: invalid ban #\n"); + return qfalse; + } + g_shrubbot_bans[bnum-1]->expires = t - SHRUBBOT_BAN_EXPIRE_OFFSET; + SP(va("unban: ban #%d removed\n", bnum)); + _shrubbot_writeconfig(); + return q